Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support editor rtl mode without wrapped lines #4665

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 27 additions & 1 deletion lib/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-rtl .CodeMirror-gutters {
border-right: none;
border-left: 1px solid #ddd;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
Expand Down Expand Up @@ -165,10 +169,22 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}

.CodeMirror-rtl .CodeMirror-scroll {
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-left: -30px;
margin-right: 0;
}

.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
.CodeMirror-rtl .CodeMirror-sizer {
border-right: 0;
border-left: 30px solid transparent;
}

/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
Expand All @@ -183,6 +199,9 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-rtl .CodeMirror-vscrollbar {
left: 0; right: initial;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
Expand All @@ -191,15 +210,22 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-rtl .CodeMirror-scrollbar-filler {
right: initial; left: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-rtl .CodeMirror-gutter-filler {
left: initial; right: 0;
}

.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-rtl .CodeMirror-gutters { left: initial }
.CodeMirror-gutter {
white-space: normal;
height: 100%;
Expand Down Expand Up @@ -269,7 +295,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}

.CodeMirror-widget {}

.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-rtl { direction: rtl; }

.CodeMirror-code {
outline: none;
Expand Down
51 changes: 41 additions & 10 deletions src/display/line_numbers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,52 @@ import { updateGutterSpace } from "./update_display"
export function alignHorizontally(cm) {
let display = cm.display, view = display.view
if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return
let comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft
let gutterW = display.gutters.offsetWidth, left = comp + "px"
let isLtr = cm.doc.direction == "ltr"
let scroll = display.scroller.scrollLeft - cm.doc.scrollLeft
let comp = compensateForHScroll(display, isLtr) + (isLtr ? -scroll : scroll)
let offset = comp + "px"
let side = isLtr ? "left" : "right"
let otherSide = isLtr ? "right" : "left"
for (let i = 0; i < view.length; i++) if (!view[i].hidden) {
if (cm.options.fixedGutter) {
if (view[i].gutter)
view[i].gutter.style.left = left
if (view[i].gutterBackground)
view[i].gutterBackground.style.left = left
if (view[i].gutter) {
view[i].gutter.style[side] = offset
view[i].gutter.style[otherSide] = null
}
if (view[i].gutterBackground) {
view[i].gutterBackground.style[side] = offset
view[i].gutterBackground.style[otherSide] = null
}
}
let align = view[i].alignable
if (align) for (let j = 0; j < align.length; j++)
align[j].style.left = left
if (align) for (let j = 0; j < align.length; j++) {
align[j].style[side] = offset
align[j].style[otherSide] = null
}
}
if (cm.options.fixedGutter)
display.gutters.style.left = (comp + gutterW) + "px"
setGutterOffset(cm)
}

function setGutterOffset(cm, fixed = cm.options.fixedGutter, alsoIfNotFixed = false) {
let isLtr = cm.doc.direction == "ltr"
let side = isLtr ? "left" : "right"
let display = cm.display
if (!fixed) {
if (alsoIfNotFixed) {
display.gutters.style[side] = "0"
display.gutters.style[isLtr ? "right" : "left"] = null
}
return
}
let scroll = display.scroller.scrollLeft - cm.doc.scrollLeft
let comp = compensateForHScroll(display, isLtr) + (isLtr ? -scroll : scroll)
let gutterW = display.gutters.offsetWidth
display.gutters.style[side] = (comp + gutterW) + "px"
display.gutters.style[isLtr ? "right" : "left"] = null
}

export function updateFixedGutter(cm, val) {
setGutterOffset(cm, val, true)
}

// Used to ensure that the line number gutter is still the right
Expand Down
3 changes: 2 additions & 1 deletion src/display/operations.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { clipPos } from "../line/pos"
import { findMaxLine } from "../line/spans"
import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement"
import { gecko } from "../util/browser"
import { signal } from "../util/event"
import { activeElt } from "../util/dom"
import { finishOperation, pushOperation } from "../util/operation_group"
Expand Down Expand Up @@ -100,7 +101,7 @@ function endOperation_R2(op) {
cm.display.sizerWidth = op.adjustWidthTo
op.barMeasure.scrollWidth =
Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth)
op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
op.maxScrollLeft = Math[gecko && cm.doc.direction == "rtl" ? "min" : "max"](0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
}

if (op.updatedDisplay || op.selectionChanged)
Expand Down
7 changes: 4 additions & 3 deletions src/display/scrollbars.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ class NativeScrollbars {

if (needsH) {
this.horiz.style.display = "block"
this.horiz.style.right = needsV ? sWidth + "px" : "0"
this.horiz.style.left = measure.barLeft + "px"
this.horiz.style[this.cm.doc.direction == "ltr" ? "right" : "left"] = needsV ? sWidth + "px" : "0"
this.horiz.style[this.cm.doc.direction == "ltr" ? "left" : "right"] = measure.barLeft + "px"
let totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
this.horiz.firstChild.style.width =
Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
Expand Down Expand Up @@ -150,7 +150,8 @@ function updateScrollbarsInner(cm, measure) {
let d = cm.display
let sizes = d.scrollbars.update(measure)

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

Expand Down
5 changes: 3 additions & 2 deletions src/display/scrolling.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ export function calculateScrollPos(cm, rect) {
let screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0)
let tooWide = rect.right - rect.left > screenw
if (tooWide) rect.right = rect.left + screenw
if (rect.left < 10)
let rtl = cm.doc.direction == "rtl"
if (Math.abs(rect.left) < 10 || (rtl ? rect.left > 0 : rect.left < 0))
result.scrollLeft = 0
else if (rect.left < screenleft)
result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10))
result.scrollLeft = Math[rtl ? "min" : "max"](0, rect.left - (tooWide ? 0 : 10))
else if (rect.right > screenw + screenleft - 3)
result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw
return result
Expand Down
2 changes: 1 addition & 1 deletion src/display/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function drawSelectionRange(cm, range, output) {
start = leftPos
if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
end = rightPos
if (left < leftSide + 1) left = leftSide
if (left < leftSide + 1 && cm.doc.direction != "rtl") left = leftSide
add(left, rightPos.top, right - left, rightPos.bottom)
})
return {start: start, end: end}
Expand Down
6 changes: 4 additions & 2 deletions src/display/update_display.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export function maybeClipScrollbars(cm) {
display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth
display.heightForcer.style.height = scrollGap(cm) + "px"
display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
display.sizer.style[cm.doc.direction == "ltr" ? "borderLeftWidth" : "borderRightWidth"] = null
display.sizer.style[cm.doc.direction == "ltr" ? "borderRightWidth" : "borderLeftWidth"] = scrollGap(cm) + "px"
display.scrollbarsClipped = true
}
}
Expand Down Expand Up @@ -219,7 +220,8 @@ function patchDisplay(cm, updateNumbersFrom, dims) {

export function updateGutterSpace(cm) {
let width = cm.display.gutters.offsetWidth
cm.display.sizer.style.marginLeft = width + "px"
cm.display.sizer.style[cm.doc.direction == "ltr" ? "marginRight" : "marginLeft"] = null
cm.display.sizer.style[cm.doc.direction == "ltr" ? "marginLeft" : "marginRight"] = width + "px"
}

export function setDocumentHeight(cm, measure) {
Expand Down
9 changes: 5 additions & 4 deletions src/display/update_line.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,18 @@ function updateLineGutter(cm, lineView, lineN, dims) {
lineView.node.removeChild(lineView.gutterBackground)
lineView.gutterBackground = null
}
let side = (cm.doc.direction == "ltr" ? "left" : "right")
if (lineView.line.gutterClass) {
let wrap = ensureLineWrapped(lineView)
lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
`left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`)
`${side}: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`)
cm.display.input.setUneditable(lineView.gutterBackground)
wrap.insertBefore(lineView.gutterBackground, lineView.text)
}
let markers = lineView.line.gutterMarkers
if (cm.options.lineNumbers || markers) {
let wrap = ensureLineWrapped(lineView)
let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`)
let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `${side}: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`)
cm.display.input.setUneditable(gutterWrap)
wrap.insertBefore(gutterWrap, lineView.text)
if (lineView.line.gutterClass)
Expand All @@ -112,12 +113,12 @@ function updateLineGutter(cm, lineView, lineN, dims) {
lineView.lineNumber = gutterWrap.appendChild(
elt("div", lineNumberFor(cm.options, lineN),
"CodeMirror-linenumber CodeMirror-gutter-elt",
`left: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`))
`${side}: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`))
if (markers) for (let k = 0; k < cm.options.gutters.length; ++k) {
let id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]
if (found)
gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt",
`left: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`))
`${side}: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`))
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/edit/mouse_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,10 @@ function leftButtonSelect(cm, e, start, type, addNew) {
// handlers for the corresponding event.
function gutterEvent(cm, e, type, prevent) {
let mX, mY
let check = cm.doc.direction == "ltr" ? dom => mX >= Math.floor(dom.getBoundingClientRect().right) : dom => mX <= Math.floor(dom.getBoundingClientRect().left)
try { mX = e.clientX; mY = e.clientY }
catch(e) { return false }
if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false
if (check(cm.display.gutters)) return false
if (prevent) e_preventDefault(e)

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

for (let i = 0; i < cm.options.gutters.length; ++i) {
let g = display.gutters.childNodes[i]
if (g && g.getBoundingClientRect().right >= mX) {
if (g && check(g)) {
let line = lineAtHeight(cm.doc, mY)
let gutter = cm.options.gutters[i]
signal(cm, type, cm, line, gutter, e)
Expand Down
15 changes: 11 additions & 4 deletions src/edit/options.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { onBlur } from "../display/focus"
import { setGuttersForLineNumbers, updateGutters } from "../display/gutters"
import { alignHorizontally } from "../display/line_numbers"
import { alignHorizontally, updateFixedGutter } from "../display/line_numbers"
import { loadMode, resetModeState } from "../display/mode_state"
import { initScrollbars, updateScrollbars } from "../display/scrollbars"
import { setScrollLeft } from "../display/scroll_events"
import { updateSelection } from "../display/selection"
import { regChange } from "../display/view_tracking"
import { getKeyMap } from "../input/keymap"
import { defaultSpecialCharPlaceholder } from "../line/line_data"
import { Pos } from "../line/pos"
import { findMaxLine } from "../line/spans"
import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement"
import { clearCaches, estimateLineHeights } from "../measurement/position_measurement"
import { replaceRange } from "../model/changes"
import { mobile, windows } from "../util/browser"
import { addClass, rmClass } from "../util/dom"
Expand Down Expand Up @@ -99,7 +100,7 @@ export function defineOptions(CodeMirror) {
guttersChanged(cm)
}, true)
option("fixedGutter", true, (cm, val) => {
cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"
updateFixedGutter(cm, val)
cm.refresh()
}, true)
option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true)
Expand Down Expand Up @@ -153,7 +154,13 @@ export function defineOptions(CodeMirror) {

option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "")
option("autofocus", null)
option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true)
option("direction", "ltr", (cm, val) => {
cm.doc.setDirection(val)
cm.display.scroller.scrollLeft = 0 // Chromium needs this
alignHorizontally(cm)
setScrollLeft(cm, 0)
guttersChanged(cm)
}, true)
}

function guttersChanged(cm) {
Expand Down
10 changes: 6 additions & 4 deletions src/measurement/position_measurement.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ export function coordsChar(cm, x, y) {
let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1
if (lineN > last)
return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1)
if (x < 0) x = 0
if (x < 0 && cm.doc.direction != "rtl") x = 0

let lineObj = getLine(doc, lineN)
for (;;) {
Expand Down Expand Up @@ -537,7 +537,7 @@ export function getDimensions(cm) {
left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft
width[cm.options.gutters[i]] = n.clientWidth
}
return {fixedPos: compensateForHScroll(d),
return {fixedPos: compensateForHScroll(d, cm.doc.direction == "ltr"),
gutterTotalWidth: d.gutters.offsetWidth,
gutterLeft: left,
gutterWidth: width,
Expand All @@ -547,8 +547,10 @@ export function getDimensions(cm) {
// Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
// but using getBoundingClientRect to get a sub-pixel-accurate
// result.
export function compensateForHScroll(display) {
return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left
export function compensateForHScroll(display, isLtr) {
let side = isLtr ? "left" : "right"
let diff = display.scroller.getBoundingClientRect()[side] - display.sizer.getBoundingClientRect()[side]
return isLtr ? diff : -diff
}

// Returns a function that estimates the height of a line, to use as
Expand Down
2 changes: 1 addition & 1 deletion src/model/document_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function attachDoc(cm, doc) {
}

function setDirectionClass(cm) {
;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl")
;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.wrapper, "CodeMirror-rtl")
}

export function directionChanged(cm) {
Expand Down