Support editor rtl mode without wrapped lines

This commit is contained in:
Adrian Heine 2017-03-30 10:33:44 +02:00
parent 66766a612d
commit 6cce022c5e
12 changed files with 108 additions and 35 deletions

View File

@ -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;
@ -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
@ -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;
@ -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%;
@ -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;

View File

@ -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

View File

@ -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"
@ -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)

View File

@ -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"
@ -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"

View File

@ -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

View File

@ -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}

View File

@ -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
}
}
@ -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) {

View File

@ -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)
@ -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`))
}
}
}

View File

@ -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
@ -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)

View File

@ -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"
@ -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)
@ -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) {

View File

@ -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 (;;) {
@ -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,
@ -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

View File

@ -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) {