codemirror
Advanced tools
Comparing version 5.29.0 to 5.30.0
@@ -26,2 +26,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
if (val) { | ||
ensureBound(getOption(val, "pairs")) | ||
cm.state.closeBrackets = val; | ||
@@ -38,6 +39,10 @@ cm.addKeyMap(keyMap); | ||
var bind = defaults.pairs + "`"; | ||
var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; | ||
for (var i = 0; i < bind.length; i++) | ||
keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i)); | ||
function ensureBound(chars) { | ||
for (var i = 0; i < chars.length; i++) { | ||
var ch = chars.charAt(i), key = "'" + ch + "'" | ||
if (!keyMap[key]) keyMap[key] = handler(ch) | ||
} | ||
} | ||
ensureBound(defaults.pairs + "`") | ||
@@ -44,0 +49,0 @@ function handler(ch) { |
@@ -305,3 +305,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
CodeMirror.signal(data, "select", completions[0], hints.firstChild); | ||
CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]); | ||
return true; | ||
@@ -308,0 +308,0 @@ } |
@@ -18,3 +18,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
CodeMirror.registerHelper("lint", "css", function(text) { | ||
CodeMirror.registerHelper("lint", "css", function(text, options) { | ||
var found = []; | ||
@@ -27,3 +27,3 @@ if (!window.CSSLint) { | ||
} | ||
var results = CSSLint.verify(text), messages = results.messages, message = null; | ||
var results = CSSLint.verify(text, options), messages = results.messages, message = null; | ||
for ( var i = 0; i < messages.length; i++) { | ||
@@ -30,0 +30,0 @@ message = messages[i]; |
@@ -141,3 +141,7 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
var state = cm.state.lint, options = state.options; | ||
var passOptions = options.options || options; // Support deprecated passing of `options` property in options | ||
/* | ||
* Passing rules in `options` property prevents JSHint (and other linters) from complaining | ||
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc. | ||
*/ | ||
var passOptions = options.options || options; | ||
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint"); | ||
@@ -144,0 +148,0 @@ if (!getAnnotations) return; |
@@ -139,3 +139,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
if (token && token.apply) token = token(matches) | ||
if (matches.length > 2) { | ||
if (matches.length > 2 && rule.token && typeof rule.token != "string") { | ||
state.pending = []; | ||
@@ -142,0 +142,0 @@ for (var j = 2; j < matches.length; j++) |
@@ -120,2 +120,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
var q = cm.getSelection() || state.lastQuery; | ||
if (q instanceof RegExp && q.source == "x^") q = null | ||
if (persistent && cm.openDialog) { | ||
@@ -122,0 +123,0 @@ var hiding = null |
@@ -89,3 +89,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
var coverStart = array[0].find(), coverEnd = array[array.length - 1].find(); | ||
if (!coverStart || !coverEnd || to.line - from.line < CHUNK_SIZE || | ||
if (!coverStart || !coverEnd || to.line - from.line <= CHUNK_SIZE || | ||
cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0) | ||
@@ -92,0 +92,0 @@ return reset(cm); |
@@ -574,3 +574,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
offsetLines: from.line, | ||
text: doc.getRange(from, Pos(endLine, 0))}; | ||
text: doc.getRange(from, Pos(endLine, end.line == endLine ? null : 0))}; | ||
} | ||
@@ -577,0 +577,0 @@ |
@@ -84,3 +84,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
if (modeConfig.gitHubSpice !== false) { | ||
if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) { | ||
if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?=.{0,6}\d)(?:[a-f0-9]{7,40}\b)/)) { | ||
// User/Project@SHA | ||
@@ -87,0 +87,0 @@ // User@SHA |
@@ -31,3 +31,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, | ||
"return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, | ||
"return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "void": C, "throw": C, "debugger": C, | ||
"var": kw("var"), "const": kw("var"), "let": kw("var"), | ||
@@ -402,3 +402,3 @@ "function": kw("function"), "catch": kw("catch"), | ||
var body = noComma ? arrowBodyNoComma : arrowBody; | ||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); | ||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext); | ||
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); | ||
@@ -448,2 +448,7 @@ } | ||
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) } | ||
if (type == "regexp") { | ||
cx.state.lastType = cx.marked = "operator" | ||
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1) | ||
return cont(expr) | ||
} | ||
} | ||
@@ -497,2 +502,5 @@ function quasi(type, value) { | ||
if (value == "get" || value == "set") return cont(getterSetter); | ||
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params | ||
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false))) | ||
cx.state.fatArrowAt = cx.stream.pos + m[0].length | ||
return cont(afterprop); | ||
@@ -655,3 +663,4 @@ } else if (type == "number" || type == "string") { | ||
} | ||
function funarg(type) { | ||
function funarg(type, value) { | ||
if (value == "@") cont(expression, funarg) | ||
if (type == "spread" || type == "modifier") return cont(funarg); | ||
@@ -682,3 +691,3 @@ return pass(pattern, maybetype, maybeAssign); | ||
} | ||
if (type == "variable") { | ||
if (type == "variable" || cx.style == "keyword") { | ||
cx.marked = "property"; | ||
@@ -745,3 +754,3 @@ return cont(isTS ? classfield : functiondef, classBody); | ||
return state.tokenize == tokenBase && | ||
/^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(state.lastType) || | ||
/^(?:operator|sof|keyword [bc]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) || | ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) | ||
@@ -748,0 +757,0 @@ } |
@@ -88,3 +88,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
, listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/ | ||
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow listRE | ||
, taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE | ||
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/ | ||
@@ -407,3 +407,3 @@ , setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/ | ||
if (state.taskList) { | ||
var taskOpen = stream.match(taskListRE, true)[1] !== "x"; | ||
var taskOpen = stream.match(taskListRE, true)[1] === " "; | ||
if (taskOpen) state.taskOpen = true; | ||
@@ -844,4 +844,4 @@ else state.taskClosed = true; | ||
indent: function(state, textAfter, line) { | ||
if (state.block == htmlBlock) return htmlMode.indent(state.htmlState, textAfter, line) | ||
if (state.localState) return state.localMode.indent(state.localState, textAfter, line) | ||
if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line) | ||
if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line) | ||
return CodeMirror.Pass | ||
@@ -848,0 +848,0 @@ }, |
@@ -50,2 +50,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
{name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, | ||
{name: "Esper", mime: "text/x-esper", mode: "sql"}, | ||
{name: "Factor", mime: "text/x-factor", mode: "factor", ext: ["factor"]}, | ||
@@ -52,0 +53,0 @@ {name: "FCL", mime: "text/x-fcl", mode: "fcl"}, |
@@ -289,3 +289,4 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
electricInput: /^\s*(?:end|rescue|elsif|else|\})$/, | ||
lineComment: "#" | ||
lineComment: "#", | ||
fold: "indent" | ||
}; | ||
@@ -292,0 +293,0 @@ }); |
@@ -90,3 +90,3 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
templates: null, | ||
variables: null, | ||
variables: prepend(null, 'ij'), | ||
scopes: null, | ||
@@ -132,2 +132,9 @@ indent: 0, | ||
} | ||
if (!state.scopes) { | ||
var paramRe = /@param\??\s+(\S+)/g; | ||
var current = stream.current(); | ||
for (var match; (match = paramRe.exec(current)); ) { | ||
state.variables = prepend(state.variables, match[1]); | ||
} | ||
} | ||
return "comment"; | ||
@@ -192,2 +199,3 @@ | ||
popscope(state); | ||
state.variables = prepend(null, 'ij'); | ||
state.indent = 0; | ||
@@ -254,4 +262,10 @@ } else { | ||
state.soyState.push("comment"); | ||
if (!state.scopes) { | ||
state.variables = prepend(null, 'ij'); | ||
} | ||
return "comment"; | ||
} else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) { | ||
if (!state.scopes) { | ||
state.variables = prepend(null, 'ij'); | ||
} | ||
return "comment"; | ||
@@ -281,14 +295,14 @@ } else if (stream.match(/^\{literal}/)) { | ||
state.soyState.push("templ-def"); | ||
} | ||
if (state.tag == "call" || state.tag == "delcall") { | ||
} else if (state.tag == "call" || state.tag == "delcall") { | ||
state.soyState.push("templ-ref"); | ||
} | ||
if (state.tag == "let") { | ||
} else if (state.tag == "let") { | ||
state.soyState.push("var-def"); | ||
} | ||
if (state.tag == "for" || state.tag == "foreach") { | ||
} else if (state.tag == "for" || state.tag == "foreach") { | ||
state.scopes = prepend(state.scopes, state.variables); | ||
state.soyState.push("var-def"); | ||
} | ||
if (state.tag.match(/^@(?:param\??|inject)/)) { | ||
} else if (state.tag == "namespace") { | ||
if (!state.scopes) { | ||
state.variables = prepend(null, 'ij'); | ||
} | ||
} else if (state.tag.match(/^@(?:param\??|inject)/)) { | ||
state.soyState.push("param-def"); | ||
@@ -295,0 +309,0 @@ } |
@@ -444,2 +444,15 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
}); | ||
// Esper | ||
CodeMirror.defineMIME("text/x-esper", { | ||
name: "sql", | ||
client: set("source"), | ||
// http://www.espertech.com/esper/release-5.5.0/esper-reference/html/appendix_keywords.html | ||
keywords: set("alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit after all and as at asc avedev avg between by case cast coalesce count create current_timestamp day days delete define desc distinct else end escape events every exists false first from full group having hour hours in inner insert instanceof into irstream is istream join last lastweekday left limit like max match_recognize matches median measures metadatasql min minute minutes msec millisecond milliseconds not null offset on or order outer output partition pattern prev prior regexp retain-union retain-intersection right rstream sec second seconds select set some snapshot sql stddev sum then true unidirectional until update variable weekday when where window"), | ||
builtin: {}, | ||
atoms: set("false true null"), | ||
operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, | ||
dateSQL: set("time"), | ||
support: set("decimallessFloat zerolessFloat binaryNumber hexNumber") | ||
}); | ||
}()); | ||
@@ -446,0 +459,0 @@ |
@@ -35,3 +35,6 @@ // CodeMirror, copyright (c) by Marijn Haverbeke and others | ||
["lang", /coffee(script)?/, "coffeescript"], | ||
["type", /^(?:text|application)\/(?:x-)?coffee(?:script)?$/, "coffeescript"] | ||
["type", /^(?:text|application)\/(?:x-)?coffee(?:script)?$/, "coffeescript"], | ||
["lang", /^babel$/, "javascript"], | ||
["type", /^text\/babel$/, "javascript"], | ||
["type", /^text\/ecmascript-\d+$/, "javascript"] | ||
], | ||
@@ -41,4 +44,8 @@ style: [ | ||
["lang", /^sass$/i, "sass"], | ||
["lang", /^less$/i, "text/x-less"], | ||
["lang", /^scss$/i, "text/x-scss"], | ||
["type", /^(text\/)?(x-)?styl(us)?$/i, "stylus"], | ||
["type", /^text\/sass/i, "sass"] | ||
["type", /^text\/sass/i, "sass"], | ||
["type", /^(text\/)?(x-)?scss$/i, "text/x-scss"], | ||
["type", /^(text\/)?(x-)?less$/i, "text/x-less"] | ||
], | ||
@@ -45,0 +52,0 @@ template: [ |
{ | ||
"name": "codemirror", | ||
"version": "5.29.0", | ||
"version": "5.30.0", | ||
"main": "lib/codemirror.js", | ||
@@ -5,0 +5,0 @@ "style": "lib/codemirror.css", |
@@ -10,3 +10,4 @@ # CodeMirror | ||
100 language modes and various addons that implement more advanced | ||
editing functionality. | ||
editing functionality. Every language comes with fully-featured code | ||
and syntax highlighting to help with reading and editing complex code. | ||
@@ -13,0 +14,0 @@ A rich programming API and a CSS theming system are available for |
@@ -105,3 +105,3 @@ import { clipPos } from "../line/pos" | ||
if (op.updatedDisplay || op.selectionChanged) | ||
op.preparedSelection = display.input.prepareSelection(op.focus) | ||
op.preparedSelection = display.input.prepareSelection() | ||
} | ||
@@ -119,3 +119,3 @@ | ||
let takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()) | ||
let takeFocus = op.focus && op.focus == activeElt() | ||
if (op.preparedSelection) | ||
@@ -122,0 +122,0 @@ cm.display.input.showSelection(op.preparedSelection, takeFocus) |
import { Pos } from "../line/pos" | ||
import { visualLine } from "../line/spans" | ||
import { getLine } from "../line/utils_line" | ||
import { charCoords, cursorCoords, displayWidth, paddingH } from "../measurement/position_measurement" | ||
import { charCoords, cursorCoords, displayWidth, paddingH, wrappedLineExtentChar } from "../measurement/position_measurement" | ||
import { getOrder, iterateBidiSections } from "../util/bidi" | ||
@@ -12,3 +12,3 @@ import { elt } from "../util/dom" | ||
export function prepareSelection(cm, primary) { | ||
export function prepareSelection(cm, primary = true) { | ||
let doc = cm.doc, result = {} | ||
@@ -19,3 +19,3 @@ let curFragment = result.cursors = document.createDocumentFragment() | ||
for (let i = 0; i < doc.sel.ranges.length; i++) { | ||
if (primary === false && i == doc.sel.primIndex) continue | ||
if (!primary && i == doc.sel.primIndex) continue | ||
let range = doc.sel.ranges[i] | ||
@@ -51,2 +51,4 @@ if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue | ||
function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } | ||
// Draws the given range as a highlighted selection | ||
@@ -76,26 +78,44 @@ function drawSelectionRange(cm, range, output) { | ||
iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir) => { | ||
let leftPos = coords(from, "left"), rightPos, left, right | ||
if (from == to) { | ||
rightPos = leftPos | ||
left = right = leftPos.left | ||
} else { | ||
rightPos = coords(to - 1, "right") | ||
if (dir == "rtl") { let tmp = leftPos; leftPos = rightPos; rightPos = tmp } | ||
left = leftPos.left | ||
right = rightPos.right | ||
let order = getOrder(lineObj, doc.direction) | ||
iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => { | ||
let fromPos = coords(from, dir == "ltr" ? "left" : "right") | ||
let toPos = coords(to - 1, dir == "ltr" ? "right" : "left") | ||
if (dir == "ltr") { | ||
let fromLeft = fromArg == null && from == 0 ? leftSide : fromPos.left | ||
let toRight = toArg == null && to == lineLen ? rightSide : toPos.right | ||
if (toPos.top - fromPos.top <= 3) { // Single line | ||
add(fromLeft, toPos.top, toRight - fromLeft, toPos.bottom) | ||
} else { // Multiple lines | ||
add(fromLeft, fromPos.top, null, fromPos.bottom) | ||
if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top) | ||
add(leftSide, toPos.top, toPos.right, toPos.bottom) | ||
} | ||
} else if (from < to) { // RTL | ||
let fromRight = fromArg == null && from == 0 ? rightSide : fromPos.right | ||
let toLeft = toArg == null && to == lineLen ? leftSide : toPos.left | ||
if (toPos.top - fromPos.top <= 3) { // Single line | ||
add(toLeft, toPos.top, fromRight - toLeft, toPos.bottom) | ||
} else { // Multiple lines | ||
let topLeft = leftSide | ||
if (i) { | ||
let topEnd = wrappedLineExtentChar(cm, lineObj, null, from).end | ||
// The coordinates returned for an RTL wrapped space tend to | ||
// be complete bogus, so try to skip that here. | ||
topLeft = coords(topEnd - (/\s/.test(lineObj.text.charAt(topEnd - 1)) ? 2 : 1), "left").left | ||
} | ||
add(topLeft, fromPos.top, fromRight - topLeft, fromPos.bottom) | ||
if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top) | ||
let botWidth = null | ||
if (i < order.length - 1 || true) { | ||
let botStart = wrappedLineExtentChar(cm, lineObj, null, to).begin | ||
botWidth = coords(botStart, "right").right - toLeft | ||
} | ||
add(toLeft, toPos.top, botWidth, toPos.bottom) | ||
} | ||
} | ||
if (fromArg == null && from == 0) left = leftSide | ||
if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part | ||
add(left, leftPos.top, null, leftPos.bottom) | ||
left = leftSide | ||
if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top) | ||
} | ||
if (toArg == null && to == lineLen) right = rightSide | ||
if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) | ||
start = leftPos | ||
if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) | ||
end = rightPos | ||
if (left < leftSide + 1) left = leftSide | ||
add(left, rightPos.top, right - left, rightPos.bottom) | ||
if (!start || cmpCoords(fromPos, start) < 0) start = fromPos | ||
if (cmpCoords(toPos, start) < 0) start = toPos | ||
if (!end || cmpCoords(fromPos, end) < 0) end = fromPos | ||
if (cmpCoords(toPos, end) < 0) end = toPos | ||
}) | ||
@@ -102,0 +122,0 @@ return {start: start, end: end} |
@@ -146,3 +146,3 @@ import { Display } from "../display/Display" | ||
on(d.scroller, "touchstart", e => { | ||
if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { | ||
if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { | ||
d.input.ensurePolled() | ||
@@ -149,0 +149,0 @@ clearTimeout(touchFinished) |
@@ -69,2 +69,2 @@ // EDITOR CONSTRUCTOR | ||
CodeMirror.version = "5.29.0" | ||
CodeMirror.version = "5.30.0" |
@@ -11,2 +11,3 @@ import { delayBlurEvent, ensureFocus } from "../display/focus" | ||
import { captureRightClick, chromeOS, ie, ie_version, mac, webkit } from "../util/browser" | ||
import { getOrder, getBidiPartAt } from "../util/bidi" | ||
import { activeElt } from "../util/dom" | ||
@@ -273,3 +274,3 @@ import { e_button, e_defaultPrevented, e_preventDefault, e_target, hasHandler, off, on, signal, signalDOMEvent } from "../util/event" | ||
let ranges = startSel.ranges.slice(0) | ||
ranges[ourIndex] = new Range(clipPos(doc, anchor), head) | ||
ranges[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) | ||
setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse) | ||
@@ -326,3 +327,35 @@ } | ||
// Used when mouse-selecting to adjust the anchor to the proper side | ||
// of a bidi jump depending on the visual position of the head. | ||
function bidiSimplify(cm, range) { | ||
let {anchor, head} = range, anchorLine = getLine(cm.doc, anchor.line) | ||
if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) return range | ||
let order = getOrder(anchorLine) | ||
if (!order) return range | ||
let index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] | ||
if (part.from != anchor.ch && part.to != anchor.ch) return range | ||
let boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1) | ||
if (boundary == 0 || boundary == order.length) return range | ||
// Compute the relative visual position of the head compared to the | ||
// anchor (<0 is to the left, >0 to the right) | ||
let leftSide | ||
if (head.line != anchor.line) { | ||
leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 | ||
} else { | ||
let headIndex = getBidiPartAt(order, head.ch, head.sticky) | ||
let dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1) | ||
if (headIndex == boundary - 1 || headIndex == boundary) | ||
leftSide = dir < 0 | ||
else | ||
leftSide = dir > 0 | ||
} | ||
let usePart = order[boundary + (leftSide ? -1 : 0)] | ||
let from = leftSide == (usePart.level == 1) | ||
let ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before" | ||
return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) | ||
} | ||
// Determines whether an event happened in the gutter, and fires the | ||
@@ -332,4 +365,9 @@ // handlers for the corresponding event. | ||
let mX, mY | ||
try { mX = e.clientX; mY = e.clientY } | ||
catch(e) { return false } | ||
if (e.touches) { | ||
mX = e.touches[0].clientX | ||
mY = e.touches[0].clientY | ||
} else { | ||
try { mX = e.clientX; mY = e.clientY } | ||
catch(e) { return false } | ||
} | ||
if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false | ||
@@ -336,0 +374,0 @@ if (prevent) e_preventDefault(e) |
@@ -1,2 +0,1 @@ | ||
import { moveVisually } from "../input/movement" | ||
import { buildLineContent, LineView } from "../line/line_data" | ||
@@ -296,2 +295,9 @@ import { clipPos, Pos } from "../line/pos" | ||
function widgetTopHeight(lineObj) { | ||
let height = 0 | ||
if (lineObj.widgets) for (let i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) | ||
height += widgetHeight(lineObj.widgets[i]) | ||
return height | ||
} | ||
// Converts a {top, bottom, left, right} box from line-local | ||
@@ -302,5 +308,5 @@ // coordinates into another coordinate system. Context may be one of | ||
export function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { | ||
if (!includeWidgets && lineObj.widgets) for (let i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { | ||
let size = widgetHeight(lineObj.widgets[i]) | ||
rect.top += size; rect.bottom += size | ||
if (!includeWidgets) { | ||
let height = widgetTopHeight(lineObj) | ||
rect.top += height; rect.bottom += height | ||
} | ||
@@ -381,3 +387,3 @@ if (context == "line") return rect | ||
function getBidi(ch, partPos, invert) { | ||
let part = order[partPos], right = (part.level % 2) != 0 | ||
let part = order[partPos], right = part.level == 1 | ||
return get(invert ? ch - 1 : ch, right != invert) | ||
@@ -440,6 +446,6 @@ } | ||
function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { | ||
let measure = ch => intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line") | ||
y -= widgetTopHeight(lineObj) | ||
let end = lineObj.text.length | ||
let begin = findFirst(ch => measure(ch - 1).bottom <= y, end, 0) | ||
end = findFirst(ch => measure(ch).top > y, begin, end) | ||
let begin = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y, end, 0) | ||
end = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch).top > y, begin, end) | ||
return {begin, end} | ||
@@ -449,2 +455,3 @@ } | ||
export function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { | ||
if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) | ||
let targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top | ||
@@ -454,62 +461,129 @@ return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) | ||
// Returns true if the given side of a box is after the given | ||
// coordinates, in top-to-bottom, left-to-right order. | ||
function boxIsAfter(box, x, y, left) { | ||
return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x | ||
} | ||
function coordsCharInner(cm, lineObj, lineNo, x, y) { | ||
// Move y into line-local coordinate space | ||
y -= heightAtLine(lineObj) | ||
let begin = 0, end = lineObj.text.length | ||
let preparedMeasure = prepareMeasureForLine(cm, lineObj) | ||
let pos | ||
// When directly calling `measureCharPrepared`, we have to adjust | ||
// for the widgets at this line. | ||
let widgetHeight = widgetTopHeight(lineObj) | ||
let begin = 0, end = lineObj.text.length, ltr = true | ||
let order = getOrder(lineObj, cm.doc.direction) | ||
// If the line isn't plain left-to-right text, first figure out | ||
// which bidi section the coordinates fall into. | ||
if (order) { | ||
if (cm.options.lineWrapping) { | ||
;({begin, end} = wrappedLineExtent(cm, lineObj, preparedMeasure, y)) | ||
let part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) | ||
(cm, lineObj, lineNo, preparedMeasure, order, x, y) | ||
ltr = part.level != 1 | ||
// The awkward -1 offsets are needed because findFirst (called | ||
// on these below) will treat its first bound as inclusive, | ||
// second as exclusive, but we want to actually address the | ||
// characters in the part's range | ||
begin = ltr ? part.from : part.to - 1 | ||
end = ltr ? part.to : part.from - 1 | ||
} | ||
// A binary search to find the first character whose bounding box | ||
// starts after the coordinates. If we run across any whose box wrap | ||
// the coordinates, store that. | ||
let chAround = null, boxAround = null | ||
let ch = findFirst(ch => { | ||
let box = measureCharPrepared(cm, preparedMeasure, ch) | ||
box.top += widgetHeight; box.bottom += widgetHeight | ||
if (!boxIsAfter(box, x, y, false)) return false | ||
if (box.top <= y && box.left <= x) { | ||
chAround = ch | ||
boxAround = box | ||
} | ||
pos = new Pos(lineNo, Math.floor(begin + (end - begin) / 2)) | ||
let beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left | ||
let dir = beginLeft < x ? 1 : -1 | ||
let prevDiff, diff = beginLeft - x, prevPos | ||
let steps = Math.ceil((end - begin) / 4) | ||
outer: do { | ||
prevDiff = diff | ||
prevPos = pos | ||
let i = 0 | ||
for (; i < steps; ++i) { | ||
let prevPos = pos | ||
pos = moveVisually(cm, lineObj, pos, dir) | ||
if (pos == null || pos.ch < begin || end <= (pos.sticky == "before" ? pos.ch - 1 : pos.ch)) { | ||
pos = prevPos | ||
break outer | ||
} | ||
} | ||
diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x | ||
if (steps > 1) { | ||
let diff_change_per_step = Math.abs(diff - prevDiff) / steps | ||
steps = Math.min(steps, Math.ceil(Math.abs(diff) / diff_change_per_step)) | ||
dir = diff < 0 ? 1 : -1 | ||
} | ||
} while (diff != 0 && (steps > 1 || ((dir < 0) != (diff < 0) && (Math.abs(diff) <= Math.abs(prevDiff))))) | ||
if (Math.abs(diff) > Math.abs(prevDiff)) { | ||
if ((diff < 0) == (prevDiff < 0)) throw new Error("Broke out of infinite loop in coordsCharInner") | ||
pos = prevPos | ||
} | ||
return true | ||
}, begin, end) | ||
let baseX, sticky, outside = false | ||
// If a box around the coordinates was found, use that | ||
if (boxAround) { | ||
// Distinguish coordinates nearer to the left or right side of the box | ||
let atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr | ||
ch = chAround + (atStart ? 0 : 1) | ||
sticky = atStart ? "after" : "before" | ||
baseX = atLeft ? boxAround.left : boxAround.right | ||
} else { | ||
let ch = findFirst(ch => { | ||
let box = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line") | ||
if (box.top > y) { | ||
// For the cursor stickiness | ||
end = Math.min(ch, end) | ||
return true | ||
} | ||
else if (box.bottom <= y) return false | ||
else if (box.left > x) return true | ||
else if (box.right < x) return false | ||
else return (x - box.left < box.right - x) | ||
}, begin, end) | ||
ch = skipExtendingChars(lineObj.text, ch, 1) | ||
pos = new Pos(lineNo, ch, ch == end ? "before" : "after") | ||
// (Adjust for extended bound, if necessary.) | ||
if (!ltr && (ch == end || ch == begin)) ch++ | ||
// To determine which side to associate with, get the box to the | ||
// left of the character and compare it's vertical position to the | ||
// coordinates | ||
sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : | ||
(measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? | ||
"after" : "before" | ||
// Now get accurate coordinates for this place, in order to get a | ||
// base X position | ||
let coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) | ||
baseX = coords.left | ||
outside = y < coords.top || y >= coords.bottom | ||
} | ||
let coords = cursorCoords(cm, pos, "line", lineObj, preparedMeasure) | ||
if (y < coords.top || coords.bottom < y) pos.outside = true | ||
pos.xRel = x < coords.left ? -1 : (x > coords.right ? 1 : 0) | ||
return pos | ||
ch = skipExtendingChars(lineObj.text, ch, 1) | ||
return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) | ||
} | ||
function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { | ||
// Bidi parts are sorted left-to-right, and in a non-line-wrapping | ||
// situation, we can take this ordering to correspond to the visual | ||
// ordering. This finds the first part whose end is after the given | ||
// coordinates. | ||
let index = findFirst(i => { | ||
let part = order[i], ltr = part.level != 1 | ||
return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), | ||
"line", lineObj, preparedMeasure), x, y, true) | ||
}, 0, order.length - 1) | ||
let part = order[index] | ||
// If this isn't the first part, the part's start is also after | ||
// the coordinates, and the coordinates aren't on the same line as | ||
// that start, move one part back. | ||
if (index > 0) { | ||
let ltr = part.level != 1 | ||
let start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), | ||
"line", lineObj, preparedMeasure) | ||
if (boxIsAfter(start, x, y, true) && start.top > y) | ||
part = order[index - 1] | ||
} | ||
return part | ||
} | ||
function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { | ||
// In a wrapped line, rtl text on wrapping boundaries can do things | ||
// that don't correspond to the ordering in our `order` array at | ||
// all, so a binary search doesn't work, and we want to return a | ||
// part that only spans one line so that the binary search in | ||
// coordsCharInner is safe. As such, we first find the extent of the | ||
// wrapped line, and then do a flat search in which we discard any | ||
// spans that aren't on the line. | ||
let {begin, end} = wrappedLineExtent(cm, lineObj, preparedMeasure, y) | ||
let part = null, closestDist = null | ||
for (let i = 0; i < order.length; i++) { | ||
let p = order[i] | ||
if (p.from >= end || p.to <= begin) continue | ||
let ltr = p.level != 1 | ||
let endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right | ||
// Weigh against spans ending before this, so that they are only | ||
// picked if nothing ends after | ||
let dist = endX < x ? x - endX + 1e9 : endX - x | ||
if (!part || closestDist > dist) { | ||
part = p | ||
closestDist = dist | ||
} | ||
} | ||
if (!part) part = order[order.length - 1] | ||
// Clip the part to the wrapped line. | ||
if (part.from < begin) part = {from: begin, to: part.to, level: part.level} | ||
if (part.to > end) part = {from: part.from, to: end, level: part.level} | ||
return part | ||
} | ||
let measureText | ||
@@ -516,0 +590,0 @@ // Compute the default text height. |
@@ -263,5 +263,5 @@ import { retreatFrontier } from "../line/highlight" | ||
if (!to) to = from | ||
if (cmp(to, from) < 0) { let tmp = to; to = from; from = tmp } | ||
if (cmp(to, from) < 0) [from, to] = [to, from] | ||
if (typeof code == "string") code = doc.splitLines(code) | ||
makeChange(doc, {from: from, to: to, text: code, origin: origin}) | ||
makeChange(doc, {from, to, text: code, origin}) | ||
} | ||
@@ -268,0 +268,0 @@ |
@@ -6,3 +6,3 @@ import { lst } from "./misc" | ||
export function iterateBidiSections(order, from, to, f) { | ||
if (!order) return f(from, to, "ltr") | ||
if (!order) return f(from, to, "ltr", 0) | ||
let found = false | ||
@@ -12,3 +12,3 @@ for (let i = 0; i < order.length; ++i) { | ||
if (part.from < to && part.to > from || from == to && part.to == from) { | ||
f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr") | ||
f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i) | ||
found = true | ||
@@ -15,0 +15,0 @@ } |
@@ -137,10 +137,15 @@ export function bind(f) { | ||
// Returns the value from the range [`from`; `to`] that satisfies | ||
// `pred` and is closest to `from`. Assumes that at least `to` satisfies `pred`. | ||
// `pred` and is closest to `from`. Assumes that at least `to` | ||
// satisfies `pred`. Supports `from` being greater than `to`. | ||
export function findFirst(pred, from, to) { | ||
// At any point we are certain `to` satisfies `pred`, don't know | ||
// whether `from` does. | ||
let dir = from > to ? -1 : 1 | ||
for (;;) { | ||
if (Math.abs(from - to) <= 1) return pred(from) ? from : to | ||
let mid = Math.floor((from + to) / 2) | ||
if (from == to) return from | ||
let midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF) | ||
if (mid == from) return pred(mid) ? from : to | ||
if (pred(mid)) to = mid | ||
else from = mid | ||
else from = mid + dir | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
2677929
61434
36