Skip to content

Commit 0844945

Browse files
committed
Support editor rtl mode without wrapped lines
1 parent 66766a6 commit 0844945

File tree

12 files changed

+107
-35
lines changed

12 files changed

+107
-35
lines changed

lib/codemirror.css

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
background-color: #f7f7f7;
2828
white-space: nowrap;
2929
}
30+
.CodeMirror-rtl .CodeMirror-gutters {
31+
border-right: none;
32+
border-left: 1px solid #ddd;
33+
}
3034
.CodeMirror-linenumbers {}
3135
.CodeMirror-linenumber {
3236
padding: 0 3px 0 5px;
@@ -165,10 +169,22 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
165169
outline: none; /* Prevent dragging from highlighting the element */
166170
position: relative;
167171
}
172+
173+
.CodeMirror-rtl .CodeMirror-scroll {
174+
/* 30px is the magic margin used to hide the element's real scrollbars */
175+
/* See overflow: hidden in .CodeMirror */
176+
margin-left: -30px;
177+
margin-right: 0;
178+
}
179+
168180
.CodeMirror-sizer {
169181
position: relative;
170182
border-right: 30px solid transparent;
171183
}
184+
.CodeMirror-rtl .CodeMirror-sizer {
185+
border-right: 0;
186+
border-left: 30px solid transparent;
187+
}
172188

173189
/* The fake, visible scrollbars. Used to force redraw during scrolling
174190
before actual scrolling happens, thus preventing shaking and
@@ -183,6 +199,9 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
183199
overflow-x: hidden;
184200
overflow-y: scroll;
185201
}
202+
.CodeMirror-rtl .CodeMirror-vscrollbar {
203+
left: 0; right: initial;
204+
}
186205
.CodeMirror-hscrollbar {
187206
bottom: 0; left: 0;
188207
overflow-y: hidden;
@@ -191,15 +210,22 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
191210
.CodeMirror-scrollbar-filler {
192211
right: 0; bottom: 0;
193212
}
213+
.CodeMirror-rtl .CodeMirror-scrollbar-filler {
214+
right: initial; left: 0;
215+
}
194216
.CodeMirror-gutter-filler {
195217
left: 0; bottom: 0;
196218
}
219+
.CodeMirror-rtl .CodeMirror-gutter-filler {
220+
left: initial; right: 0;
221+
}
197222

198223
.CodeMirror-gutters {
199224
position: absolute; left: 0; top: 0;
200225
min-height: 100%;
201226
z-index: 3;
202227
}
228+
.CodeMirror-rtl .CodeMirror-gutters { left: initial }
203229
.CodeMirror-gutter {
204230
white-space: normal;
205231
height: 100%;
@@ -269,7 +295,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
269295

270296
.CodeMirror-widget {}
271297

272-
.CodeMirror-rtl pre { direction: rtl; }
298+
.CodeMirror-rtl { direction: rtl; }
273299

274300
.CodeMirror-code {
275301
outline: none;

src/display/line_numbers.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,52 @@ import { updateGutterSpace } from "./update_display"
99
export function alignHorizontally(cm) {
1010
let display = cm.display, view = display.view
1111
if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return
12-
let comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft
13-
let gutterW = display.gutters.offsetWidth, left = comp + "px"
12+
let isLtr = cm.doc.direction == "ltr"
13+
let scroll = display.scroller.scrollLeft - cm.doc.scrollLeft
14+
let comp = compensateForHScroll(display, isLtr) + (isLtr ? -scroll : scroll)
15+
let offset = comp + "px"
16+
let side = isLtr ? "left" : "right"
17+
let otherSide = isLtr ? "right" : "left"
1418
for (let i = 0; i < view.length; i++) if (!view[i].hidden) {
1519
if (cm.options.fixedGutter) {
16-
if (view[i].gutter)
17-
view[i].gutter.style.left = left
18-
if (view[i].gutterBackground)
19-
view[i].gutterBackground.style.left = left
20+
if (view[i].gutter) {
21+
view[i].gutter.style[side] = offset
22+
view[i].gutter.style[otherSide] = null
23+
}
24+
if (view[i].gutterBackground) {
25+
view[i].gutterBackground.style[side] = offset
26+
view[i].gutterBackground.style[otherSide] = null
27+
}
2028
}
2129
let align = view[i].alignable
22-
if (align) for (let j = 0; j < align.length; j++)
23-
align[j].style.left = left
30+
if (align) for (let j = 0; j < align.length; j++) {
31+
align[j].style[side] = offset
32+
align[j].style[otherSide] = null
33+
}
2434
}
25-
if (cm.options.fixedGutter)
26-
display.gutters.style.left = (comp + gutterW) + "px"
35+
setGutterOffset(cm)
36+
}
37+
38+
function setGutterOffset(cm, fixed = cm.options.fixedGutter, alsoIfNotFixed = false) {
39+
let isLtr = cm.doc.direction == "ltr"
40+
let side = isLtr ? "left" : "right"
41+
let display = cm.display
42+
if (!fixed) {
43+
if (alsoIfNotFixed) {
44+
display.gutters.style[side] = "0"
45+
display.gutters.style[isLtr ? "right" : "left"] = null
46+
}
47+
return
48+
}
49+
let scroll = display.scroller.scrollLeft - cm.doc.scrollLeft
50+
let comp = compensateForHScroll(display, isLtr) + (isLtr ? -scroll : scroll)
51+
let gutterW = display.gutters.offsetWidth
52+
display.gutters.style[side] = (comp + gutterW) + "px"
53+
display.gutters.style[isLtr ? "right" : "left"] = null
54+
}
55+
56+
export function updateFixedGutter(cm, val) {
57+
setGutterOffset(cm, val, true)
2758
}
2859

2960
// Used to ensure that the line number gutter is still the right

src/display/operations.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { clipPos } from "../line/pos"
22
import { findMaxLine } from "../line/spans"
33
import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement"
4+
import { gecko } from "../util/browser"
45
import { signal } from "../util/event"
56
import { activeElt } from "../util/dom"
67
import { finishOperation, pushOperation } from "../util/operation_group"
@@ -100,7 +101,7 @@ function endOperation_R2(op) {
100101
cm.display.sizerWidth = op.adjustWidthTo
101102
op.barMeasure.scrollWidth =
102103
Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth)
103-
op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
104+
op.maxScrollLeft = Math[gecko && cm.doc.direction == "rtl" ? "min" : "max"](0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
104105
}
105106

106107
if (op.updatedDisplay || op.selectionChanged)

src/display/scrollbars.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ class NativeScrollbars {
6565

6666
if (needsH) {
6767
this.horiz.style.display = "block"
68-
this.horiz.style.right = needsV ? sWidth + "px" : "0"
69-
this.horiz.style.left = measure.barLeft + "px"
68+
this.horiz.style[this.cm.doc.direction == "ltr" ? "right" : "left"] = needsV ? sWidth + "px" : "0"
69+
this.horiz.style[this.cm.doc.direction == "ltr" ? "left" : "right"] = measure.barLeft + "px"
7070
let totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
7171
this.horiz.firstChild.style.width =
7272
Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
@@ -150,7 +150,8 @@ function updateScrollbarsInner(cm, measure) {
150150
let d = cm.display
151151
let sizes = d.scrollbars.update(measure)
152152

153-
d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"
153+
d.sizer.style[cm.doc.direction == "ltr" ? "paddingRight" : "paddingLeft"] = (d.barWidth = sizes.right) + "px"
154+
d.sizer.style[cm.doc.direction == "ltr" ? "paddingLeft" : "paddingRight"] = 0
154155
d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"
155156
d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
156157

src/display/scrolling.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,11 @@ export function calculateScrollPos(cm, rect) {
8686
let screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0)
8787
let tooWide = rect.right - rect.left > screenw
8888
if (tooWide) rect.right = rect.left + screenw
89-
if (rect.left < 10)
89+
let rtl = cm.doc.direction == "rtl"
90+
if (Math.abs(rect.left) < 10 || (rtl ? rect.left > 0 : rect.left < 0))
9091
result.scrollLeft = 0
9192
else if (rect.left < screenleft)
92-
result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10))
93+
result.scrollLeft = Math[rtl ? "min" : "max"](0, rect.left - (tooWide ? 0 : 10))
9394
else if (rect.right > screenw + screenleft - 3)
9495
result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw
9596
return result

src/display/selection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function drawSelectionRange(cm, range, output) {
9292
start = leftPos
9393
if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
9494
end = rightPos
95-
if (left < leftSide + 1) left = leftSide
95+
if (left < leftSide + 1 && cm.doc.direction != "rtl") left = leftSide
9696
add(left, rightPos.top, right - left, rightPos.bottom)
9797
})
9898
return {start: start, end: end}

src/display/update_display.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export function maybeClipScrollbars(cm) {
4949
display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth
5050
display.heightForcer.style.height = scrollGap(cm) + "px"
5151
display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
52-
display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
52+
display.sizer.style[cm.doc.direction == "ltr" ? "borderLeftWidth" : "borderRightWidth"] = null
53+
display.sizer.style[cm.doc.direction == "ltr" ? "borderRightWidth" : "borderLeftWidth"] = scrollGap(cm) + "px"
5354
display.scrollbarsClipped = true
5455
}
5556
}
@@ -219,7 +220,8 @@ function patchDisplay(cm, updateNumbersFrom, dims) {
219220

220221
export function updateGutterSpace(cm) {
221222
let width = cm.display.gutters.offsetWidth
222-
cm.display.sizer.style.marginLeft = width + "px"
223+
cm.display.sizer.style[cm.doc.direction == "ltr" ? "marginRight" : "marginLeft"] = null
224+
cm.display.sizer.style[cm.doc.direction == "ltr" ? "marginLeft" : "marginRight"] = width + "px"
223225
}
224226

225227
export function setDocumentHeight(cm, measure) {

src/display/update_line.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,18 @@ function updateLineGutter(cm, lineView, lineN, dims) {
9393
lineView.node.removeChild(lineView.gutterBackground)
9494
lineView.gutterBackground = null
9595
}
96+
let side = (cm.doc.direction == "ltr" ? "left" : "right")
9697
if (lineView.line.gutterClass) {
9798
let wrap = ensureLineWrapped(lineView)
9899
lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
99-
`left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`)
100+
`${side}: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`)
100101
cm.display.input.setUneditable(lineView.gutterBackground)
101102
wrap.insertBefore(lineView.gutterBackground, lineView.text)
102103
}
103104
let markers = lineView.line.gutterMarkers
104105
if (cm.options.lineNumbers || markers) {
105106
let wrap = ensureLineWrapped(lineView)
106-
let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`)
107+
let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `${side}: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`)
107108
cm.display.input.setUneditable(gutterWrap)
108109
wrap.insertBefore(gutterWrap, lineView.text)
109110
if (lineView.line.gutterClass)
@@ -112,12 +113,12 @@ function updateLineGutter(cm, lineView, lineN, dims) {
112113
lineView.lineNumber = gutterWrap.appendChild(
113114
elt("div", lineNumberFor(cm.options, lineN),
114115
"CodeMirror-linenumber CodeMirror-gutter-elt",
115-
`left: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`))
116+
`${side}: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`))
116117
if (markers) for (let k = 0; k < cm.options.gutters.length; ++k) {
117118
let id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]
118119
if (found)
119120
gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt",
120-
`left: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`))
121+
`${side}: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`))
121122
}
122123
}
123124
}

src/edit/mouse_events.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,10 @@ function leftButtonSelect(cm, e, start, type, addNew) {
278278
// handlers for the corresponding event.
279279
function gutterEvent(cm, e, type, prevent) {
280280
let mX, mY
281+
let check = cm.doc.direction == "ltr" ? dom => mX >= Math.floor(dom.getBoundingClientRect().right) : dom => mX <= Math.floor(dom.getBoundingClientRect().left)
281282
try { mX = e.clientX; mY = e.clientY }
282283
catch(e) { return false }
283-
if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false
284+
if (check(cm.display.gutters)) return false
284285
if (prevent) e_preventDefault(e)
285286

286287
let display = cm.display
@@ -291,7 +292,7 @@ function gutterEvent(cm, e, type, prevent) {
291292

292293
for (let i = 0; i < cm.options.gutters.length; ++i) {
293294
let g = display.gutters.childNodes[i]
294-
if (g && g.getBoundingClientRect().right >= mX) {
295+
if (g && check(g)) {
295296
let line = lineAtHeight(cm.doc, mY)
296297
let gutter = cm.options.gutters[i]
297298
signal(cm, type, cm, line, gutter, e)

src/edit/options.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { onBlur } from "../display/focus"
22
import { setGuttersForLineNumbers, updateGutters } from "../display/gutters"
3-
import { alignHorizontally } from "../display/line_numbers"
3+
import { alignHorizontally, updateFixedGutter } from "../display/line_numbers"
44
import { loadMode, resetModeState } from "../display/mode_state"
55
import { initScrollbars, updateScrollbars } from "../display/scrollbars"
6+
import { setScrollLeft } from "../display/scroll_events"
67
import { updateSelection } from "../display/selection"
78
import { regChange } from "../display/view_tracking"
89
import { getKeyMap } from "../input/keymap"
910
import { defaultSpecialCharPlaceholder } from "../line/line_data"
1011
import { Pos } from "../line/pos"
1112
import { findMaxLine } from "../line/spans"
12-
import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement"
13+
import { clearCaches, estimateLineHeights } from "../measurement/position_measurement"
1314
import { replaceRange } from "../model/changes"
1415
import { mobile, windows } from "../util/browser"
1516
import { addClass, rmClass } from "../util/dom"
@@ -99,7 +100,7 @@ export function defineOptions(CodeMirror) {
99100
guttersChanged(cm)
100101
}, true)
101102
option("fixedGutter", true, (cm, val) => {
102-
cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"
103+
updateFixedGutter(cm, val)
103104
cm.refresh()
104105
}, true)
105106
option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true)
@@ -153,7 +154,12 @@ export function defineOptions(CodeMirror) {
153154

154155
option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "")
155156
option("autofocus", null)
156-
option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true)
157+
option("direction", "ltr", (cm, val) => {
158+
cm.doc.setDirection(val)
159+
alignHorizontally(cm)
160+
setScrollLeft(cm, 0)
161+
guttersChanged(cm)
162+
}, true)
157163
}
158164

159165
function guttersChanged(cm) {

0 commit comments

Comments
 (0)