NTriples mode
+N-Triples mode
+The N-Triples mode also works well with on + N-Quad documents. +
diff --git a/gui/app/components/section/code/type-editor.js b/gui/app/components/section/code/type-editor.js index 980d484c..db53131a 100644 --- a/gui/app/components/section/code/type-editor.js +++ b/gui/app/components/section/code/type-editor.js @@ -101,7 +101,7 @@ export default Component.extend(TooltipMixin, { this.set('codeEditor', null); } - this.removeT0oltips(); + this.removeTooltips(); }, diff --git a/gui/app/styles/section/code.scss b/gui/app/styles/section/code.scss index da710c7e..2465a231 100644 --- a/gui/app/styles/section/code.scss +++ b/gui/app/styles/section/code.scss @@ -5,8 +5,6 @@ margin-bottom: 15px; } } - - /* BASICS */ .CodeMirror { @@ -14,6 +12,7 @@ font-family: monospace; height: 300px; color: black; + direction: ltr; } /* PADDING */ @@ -67,7 +66,12 @@ .cm-fat-cursor div.CodeMirror-cursors { z-index: 1; } - +.cm-fat-cursor-mark { + background-color: rgba(20, 255, 20, 0.5); + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} .cm-animate-fat-cursor { width: auto; border: 0; @@ -128,7 +132,7 @@ .cm-s-default .cm-property, .cm-s-default .cm-operator {} .cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} .cm-s-default .cm-comment {color: #a50;} .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} @@ -148,8 +152,8 @@ /* Default styles for common addons */ -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } .CodeMirror-activeline-background {background: #e8f2ff;} @@ -232,11 +236,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} cursor: default; z-index: 4; } -.CodeMirror-gutter-wrapper { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} +.CodeMirror-gutter-wrapper ::selection { background-color: transparent } +.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } .CodeMirror-lines { cursor: text; @@ -281,6 +282,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-widget {} +.CodeMirror-rtl pre { direction: rtl; } + .CodeMirror-code { outline: none; } @@ -329,8 +332,8 @@ div.CodeMirror-dragcursors { .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } .cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); + background-color: #ffa; + background-color: rgba(255, 255, 0, .4); } /* Used to force a border model for a node */ diff --git a/gui/public/codemirror/addon/comment/comment.js b/gui/public/codemirror/addon/comment/comment.js old mode 100755 new mode 100644 index 1c987871..84c67edf --- a/gui/public/codemirror/addon/comment/comment.js +++ b/gui/public/codemirror/addon/comment/comment.js @@ -46,7 +46,7 @@ // Rough heuristic to try and detect lines that are part of multi-line string function probablyInsideString(cm, pos, line) { - return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"`]/.test(line) + return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line) } function getMode(cm, pos) { @@ -172,13 +172,11 @@ if (open == -1) return false var endLine = end == start ? startLine : self.getLine(end) var close = endLine.indexOf(endString, end == start ? open + startString.length : 0); - if (close == -1 && start != end) { - endLine = self.getLine(--end); - close = endLine.indexOf(endString); - } + var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1) if (close == -1 || - !/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) || - !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1)))) + !/comment/.test(self.getTokenTypeAt(insideStart)) || + !/comment/.test(self.getTokenTypeAt(insideEnd)) || + self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1) return false; // Avoid killing block comments completely outside the selection. diff --git a/gui/public/codemirror/addon/comment/continuecomment.js b/gui/public/codemirror/addon/comment/continuecomment.js old mode 100755 new mode 100644 index b11d51e6..d92318b3 --- a/gui/public/codemirror/addon/comment/continuecomment.js +++ b/gui/public/codemirror/addon/comment/continuecomment.js @@ -9,39 +9,32 @@ else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { - var modes = ["clike", "css", "javascript"]; - - for (var i = 0; i < modes.length; ++i) - CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "}); - function continueComment(cm) { if (cm.getOption("disableInput")) return CodeMirror.Pass; var ranges = cm.listSelections(), mode, inserts = []; for (var i = 0; i < ranges.length; i++) { - var pos = ranges[i].head, token = cm.getTokenAt(pos); - if (token.type != "comment") return CodeMirror.Pass; - var modeHere = CodeMirror.innerMode(cm.getMode(), token.state).mode; + var pos = ranges[i].head + if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass; + var modeHere = cm.getModeAt(pos) if (!mode) mode = modeHere; else if (mode != modeHere) return CodeMirror.Pass; var insert = null; if (mode.blockCommentStart && mode.blockCommentContinue) { - var end = token.string.indexOf(mode.blockCommentEnd); - var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found; - if (end != -1 && end == token.string.length - mode.blockCommentEnd.length && pos.ch >= end) { + var line = cm.getLine(pos.line).slice(0, pos.ch) + var end = line.lastIndexOf(mode.blockCommentEnd), found + if (end != -1 && end == pos.ch - mode.blockCommentEnd.length) { // Comment ended, don't continue it - } else if (token.string.indexOf(mode.blockCommentStart) == 0) { - insert = full.slice(0, token.start); - if (!/^\s*$/.test(insert)) { - insert = ""; - for (var j = 0; j < token.start; ++j) insert += " "; + } else if ((found = line.lastIndexOf(mode.blockCommentStart)) > -1 && found > end) { + insert = line.slice(0, found) + if (/\S/.test(insert)) { + insert = "" + for (var j = 0; j < found; ++j) insert += " " } - } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 && - found + mode.blockCommentContinue.length > token.start && - /^\s*$/.test(full.slice(0, found))) { - insert = full.slice(0, found); + } else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 && !/\S/.test(line.slice(0, found))) { + insert = line.slice(0, found) } - if (insert != null) insert += mode.blockCommentContinue; + if (insert != null) insert += mode.blockCommentContinue } if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) { var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment); diff --git a/gui/public/codemirror/addon/dialog/dialog.css b/gui/public/codemirror/addon/dialog/dialog.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/dialog/dialog.js b/gui/public/codemirror/addon/dialog/dialog.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/display/autorefresh.js b/gui/public/codemirror/addon/display/autorefresh.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/display/fullscreen.css b/gui/public/codemirror/addon/display/fullscreen.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/display/fullscreen.js b/gui/public/codemirror/addon/display/fullscreen.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/display/panel.js b/gui/public/codemirror/addon/display/panel.js old mode 100755 new mode 100644 index 74199ff0..f88d152b --- a/gui/public/codemirror/addon/display/panel.js +++ b/gui/public/codemirror/addon/display/panel.js @@ -66,7 +66,7 @@ Panel.prototype.changed = function(height) { var newHeight = height == null ? this.node.offsetHeight : height; var info = this.cm.state.panels; - this.cm._setSize(null, info.height += (newHeight - this.height)); + this.cm._setSize(null, info.heightLeft -= (newHeight - this.height)); this.height = newHeight; }; diff --git a/gui/public/codemirror/addon/display/placeholder.js b/gui/public/codemirror/addon/display/placeholder.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/display/rulers.js b/gui/public/codemirror/addon/display/rulers.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/edit/closebrackets.js b/gui/public/codemirror/addon/edit/closebrackets.js old mode 100755 new mode 100644 index 01fdd96c..460f662f --- a/gui/public/codemirror/addon/edit/closebrackets.js +++ b/gui/public/codemirror/addon/edit/closebrackets.js @@ -23,6 +23,7 @@ cm.state.closeBrackets = null; } if (val) { + ensureBound(getOption(val, "pairs")) cm.state.closeBrackets = val; cm.addKeyMap(keyMap); } @@ -34,10 +35,14 @@ return defaults[name]; } - 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 + "`") function handler(ch) { return function(cm) { return handleChar(cm, ch); }; @@ -79,7 +84,8 @@ if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; } cm.operation(function() { - cm.replaceSelection("\n\n", null); + var linesep = cm.lineSeparator() || "\n"; + cm.replaceSelection(linesep + linesep, null); cm.execCommand("goCharLeft"); ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { @@ -127,7 +133,8 @@ (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { curType = "addFour"; } else if (identical) { - if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both"; + var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur) + if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both"; else return CodeMirror.Pass; } else if (opening && (cm.getLine(cur.line).length == cur.ch || isClosingBracket(next, pairs) || @@ -179,24 +186,9 @@ return str.length == 2 ? str : null; } - // Project the token type that will exists after the given char is - // typed, and use it to determine whether it would cause the start - // of a string token. - function enteringString(cm, pos, ch) { - var line = cm.getLine(pos.line); - var token = cm.getTokenAt(pos); - if (/\bstring2?\b/.test(token.type) || stringStartsAfter(cm, pos)) return false; - var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); - stream.pos = stream.start = token.start; - for (;;) { - var type1 = cm.getMode().token(stream, token.state); - if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); - stream.start = stream.pos; - } - } - function stringStartsAfter(cm, pos) { var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1)) - return /\bstring/.test(token.type) && token.start == pos.ch + return /\bstring/.test(token.type) && token.start == pos.ch && + (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos))) } }); diff --git a/gui/public/codemirror/addon/edit/closetag.js b/gui/public/codemirror/addon/edit/closetag.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/edit/continuelist.js b/gui/public/codemirror/addon/edit/continuelist.js old mode 100755 new mode 100644 index 5a845907..30893965 --- a/gui/public/codemirror/addon/edit/continuelist.js +++ b/gui/public/codemirror/addon/edit/continuelist.js @@ -11,8 +11,8 @@ })(function(CodeMirror) { "use strict"; - var listRE = /^(\s*)(>[> ]*|- \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/, - emptyListRE = /^(\s*)(>[> ]*|- \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/, + var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/, + emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/, unorderedListRE = /[*+-]\s/; CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { @@ -25,7 +25,8 @@ var inQuote = eolState.quote !== 0; var line = cm.getLine(pos.line), match = listRE.exec(line); - if (!ranges[i].empty() || (!inList && !inQuote) || !match) { + var cursorBeforeBullet = /^\s*$/.test(line.slice(0, pos.ch)); + if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) { cm.execCommand("newlineAndIndent"); return; } @@ -43,9 +44,48 @@ : (parseInt(match[3], 10) + 1) + match[4]; replacements[i] = "\n" + indent + bullet + after; + + incrementRemainingMarkdownListNumbers(cm, pos); } } cm.replaceSelections(replacements); }; + + // Auto-updating Markdown list numbers when a new item is added to the + // middle of a list + function incrementRemainingMarkdownListNumbers(cm, pos) { + var startLine = pos.line, lookAhead = 0, skipCount = 0; + var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1]; + + do { + lookAhead += 1; + var nextLineNumber = startLine + lookAhead; + var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine); + + if (nextItem) { + var nextIndent = nextItem[1]; + var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount); + var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber; + + if (startIndent === nextIndent) { + if (newNumber === nextNumber) itemNumber = nextNumber + 1; + if (newNumber > nextNumber) itemNumber = newNumber + 1; + cm.replaceRange( + nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]), + { + line: nextLineNumber, ch: 0 + }, { + line: nextLineNumber, ch: nextLine.length + }); + } else { + if (startIndent.length > nextIndent.length) return; + // This doesn't run if the next line immediatley indents, as it is + // not clear of the users intention (new indented item or same level) + if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return; + skipCount += 1; + } + } + } while (nextItem); + } }); diff --git a/gui/public/codemirror/addon/edit/matchbrackets.js b/gui/public/codemirror/addon/edit/matchbrackets.js old mode 100755 new mode 100644 index 76754ed5..4d7a2308 --- a/gui/public/codemirror/addon/edit/matchbrackets.js +++ b/gui/public/codemirror/addon/edit/matchbrackets.js @@ -16,12 +16,21 @@ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; - function findMatchingBracket(cm, where, strict, config) { + function findMatchingBracket(cm, where, config) { var line = cm.getLineHandle(where.line), pos = where.ch - 1; - var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + var afterCursor = config && config.afterCursor + if (afterCursor == null) + afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className) + + // A cursor is defined as between two characters, but in in vim command mode + // (i.e. not insert mode), the cursor is visually represented as a + // highlighted box on top of the 2nd character. Otherwise, we allow matches + // from before or after the cursor. + var match = (!afterCursor && pos >= 0 && matching[line.text.charAt(pos)]) || + matching[line.text.charAt(++pos)]; if (!match) return null; var dir = match.charAt(1) == ">" ? 1 : -1; - if (strict && (dir > 0) != (pos == where.ch)) return null; + if (config && config.strict && (dir > 0) != (pos == where.ch)) return null; var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); @@ -69,7 +78,7 @@ var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; var marks = [], ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { - var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config); + var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config); if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); @@ -113,8 +122,17 @@ }); CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); - CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){ - return findMatchingBracket(this, pos, strict, config); + CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){ + // Backwards-compatibility kludge + if (oldConfig || typeof config == "boolean") { + if (!oldConfig) { + config = config ? {strict: true} : null + } else { + oldConfig.strict = config + config = oldConfig + } + } + return findMatchingBracket(this, pos, config) }); CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ return scanForBracket(this, pos, dir, style, config); diff --git a/gui/public/codemirror/addon/edit/matchtags.js b/gui/public/codemirror/addon/edit/matchtags.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/edit/trailingspace.js b/gui/public/codemirror/addon/edit/trailingspace.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/fold/brace-fold.js b/gui/public/codemirror/addon/fold/brace-fold.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/fold/comment-fold.js b/gui/public/codemirror/addon/fold/comment-fold.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/fold/foldcode.js b/gui/public/codemirror/addon/fold/foldcode.js old mode 100755 new mode 100644 index 78b36c46..826766b6 --- a/gui/public/codemirror/addon/fold/foldcode.js +++ b/gui/public/codemirror/addon/fold/foldcode.js @@ -65,6 +65,8 @@ widget = document.createElement("span"); widget.appendChild(text); widget.className = "CodeMirror-foldmarker"; + } else if (widget) { + widget = widget.cloneNode(true) } return widget; } diff --git a/gui/public/codemirror/addon/fold/foldgutter.css b/gui/public/codemirror/addon/fold/foldgutter.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/fold/foldgutter.js b/gui/public/codemirror/addon/fold/foldgutter.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/fold/indent-fold.js b/gui/public/codemirror/addon/fold/indent-fold.js old mode 100755 new mode 100644 index 743150c6..f93edec2 --- a/gui/public/codemirror/addon/fold/indent-fold.js +++ b/gui/public/codemirror/addon/fold/indent-fold.js @@ -18,7 +18,7 @@ function lineIndent(cm, lineNo) { return -1 return CodeMirror.countColumn(text, null, cm.getOption("tabSize")) } - ! + CodeMirror.registerHelper("fold", "indent", function(cm, start) { var myIndent = lineIndent(cm, start.line) if (myIndent < 0) return diff --git a/gui/public/codemirror/addon/fold/markdown-fold.js b/gui/public/codemirror/addon/fold/markdown-fold.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/fold/xml-fold.js b/gui/public/codemirror/addon/fold/xml-fold.js old mode 100755 new mode 100644 index 75a9e305..08e21495 --- a/gui/public/codemirror/addon/fold/xml-fold.js +++ b/gui/public/codemirror/addon/fold/xml-fold.js @@ -163,10 +163,10 @@ } }; - CodeMirror.findEnclosingTag = function(cm, pos, range) { + CodeMirror.findEnclosingTag = function(cm, pos, range, tag) { var iter = new Iter(cm, pos.line, pos.ch, range); for (;;) { - var open = findMatchingOpen(iter); + var open = findMatchingOpen(iter, tag); if (!open) break; var forward = new Iter(cm, pos.line, pos.ch, range); var close = findMatchingClose(forward, open.tag); diff --git a/gui/public/codemirror/addon/hint/anyword-hint.js b/gui/public/codemirror/addon/hint/anyword-hint.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/hint/css-hint.js b/gui/public/codemirror/addon/hint/css-hint.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/hint/html-hint.js b/gui/public/codemirror/addon/hint/html-hint.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/hint/javascript-hint.js b/gui/public/codemirror/addon/hint/javascript-hint.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/hint/show-hint.css b/gui/public/codemirror/addon/hint/show-hint.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/hint/show-hint.js b/gui/public/codemirror/addon/hint/show-hint.js old mode 100755 new mode 100644 index 604bd3b7..62c683cb --- a/gui/public/codemirror/addon/hint/show-hint.js +++ b/gui/public/codemirror/addon/hint/show-hint.js @@ -121,7 +121,6 @@ var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); if (this.widget) this.widget.close(); - if (data && this.data && isNewCompletion(this.data, data)) return; this.data = data; if (data && data.list.length) { @@ -135,11 +134,6 @@ } }; - function isNewCompletion(old, nw) { - var moved = CodeMirror.cmpPos(nw.from, old.from) - return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch - } - function parseOptions(cm, pos, options) { var editor = cm.options.hintOptions; var out = {}; @@ -302,7 +296,7 @@ setTimeout(function(){cm.focus();}, 20); }); - CodeMirror.signal(data, "select", completions[0], hints.firstChild); + CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]); return true; } diff --git a/gui/public/codemirror/addon/hint/sql-hint.js b/gui/public/codemirror/addon/hint/sql-hint.js old mode 100755 new mode 100644 index 1ee4f0bb..f5ec2cac --- a/gui/public/codemirror/addon/hint/sql-hint.js +++ b/gui/public/codemirror/addon/hint/sql-hint.js @@ -14,6 +14,7 @@ var tables; var defaultTable; var keywords; + var identifierQuote; var CONS = { QUERY_DIV: ";", ALIAS_KEYWORD: "AS" @@ -28,6 +29,12 @@ return CodeMirror.resolveMode(mode).keywords; } + function getIdentifierQuote(editor) { + var mode = editor.doc.modeOption; + if (mode === "sql") mode = "text/x-sql"; + return CodeMirror.resolveMode(mode).identifierQuote || "`"; + } + function getText(item) { return typeof item == "string" ? item : item.text; } @@ -86,17 +93,25 @@ } function cleanName(name) { - // Get rid name from backticks(`) and preceding dot(.) + // Get rid name from identifierQuote and preceding dot(.) if (name.charAt(0) == ".") { name = name.substr(1); } - return name.replace(/`/g, ""); + // replace doublicated identifierQuotes with single identifierQuotes + // and remove single identifierQuotes + var nameParts = name.split(identifierQuote+identifierQuote); + for (var i = 0; i < nameParts.length; i++) + nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), ""); + return nameParts.join(identifierQuote); } - function insertBackticks(name) { + function insertIdentifierQuotes(name) { var nameParts = getText(name).split("."); for (var i = 0; i < nameParts.length; i++) - nameParts[i] = "`" + nameParts[i] + "`"; + nameParts[i] = identifierQuote + + // doublicate identifierQuotes + nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) + + identifierQuote; var escaped = nameParts.join("."); if (typeof name == "string") return escaped; name = shallowClone(name); @@ -106,13 +121,13 @@ function nameCompletion(cur, token, result, editor) { // Try to complete table, column names and return start position of completion - var useBacktick = false; + var useIdentifierQuotes = false; var nameParts = []; var start = token.start; var cont = true; while (cont) { cont = (token.string.charAt(0) == "."); - useBacktick = useBacktick || (token.string.charAt(0) == "`"); + useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote); start = token.start; nameParts.unshift(cleanName(token.string)); @@ -127,12 +142,12 @@ // Try to complete table names var string = nameParts.join("."); addMatches(result, string, tables, function(w) { - return useBacktick ? insertBackticks(w) : w; + return useIdentifierQuotes ? insertIdentifierQuotes(w) : w; }); // Try to complete columns from defaultTable addMatches(result, string, defaultTable, function(w) { - return useBacktick ? insertBackticks(w) : w; + return useIdentifierQuotes ? insertIdentifierQuotes(w) : w; }); // Try to complete columns @@ -162,7 +177,7 @@ w = shallowClone(w); w.text = tableInsert + "." + w.text; } - return useBacktick ? insertBackticks(w) : w; + return useIdentifierQuotes ? insertIdentifierQuotes(w) : w; }); } @@ -170,12 +185,9 @@ } function eachWord(lineText, f) { - if (!lineText) return; - var excepted = /[,;]/g; - var words = lineText.split(" "); - for (var i = 0; i < words.length; i++) { - f(words[i]?words[i].replace(excepted, '') : ''); - } + var words = lineText.split(/\s+/) + for (var i = 0; i < words.length; i++) + if (words[i]) f(words[i].replace(/[,;]/g, '')) } function findTableByAlias(alias, editor) { @@ -232,6 +244,7 @@ var disableKeywords = options && options.disableKeywords; defaultTable = defaultTableName && getTable(defaultTableName); keywords = getKeywords(editor); + identifierQuote = getIdentifierQuote(editor); if (defaultTableName && !defaultTable) defaultTable = findTableByAlias(defaultTableName, editor); @@ -249,7 +262,7 @@ token.string = token.string.slice(0, cur.ch - token.start); } - if (token.string.match(/^[.`\w@]\w*$/)) { + if (token.string.match(/^[.`"\w@]\w*$/)) { search = token.string; start = token.start; end = token.end; @@ -257,7 +270,7 @@ start = end = cur.ch; search = ""; } - if (search.charAt(0) == "." || search.charAt(0) == "`") { + if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) { start = nameCompletion(cur, token, result, editor); } else { addMatches(result, search, tables, function(w) {return w;}); diff --git a/gui/public/codemirror/addon/hint/xml-hint.js b/gui/public/codemirror/addon/hint/xml-hint.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/lint/coffeescript-lint.js b/gui/public/codemirror/addon/lint/coffeescript-lint.js old mode 100755 new mode 100644 index 7e39428f..70621a1b --- a/gui/public/codemirror/addon/lint/coffeescript-lint.js +++ b/gui/public/codemirror/addon/lint/coffeescript-lint.js @@ -17,6 +17,12 @@ CodeMirror.registerHelper("lint", "coffeescript", function(text) { var found = []; + if (!window.coffeelint) { + if (window.console) { + window.console.error("Error: window.coffeelint not defined, CodeMirror CoffeeScript linting cannot run."); + } + return found; + } var parseError = function(err) { var loc = err.lineNumber; found.push({from: CodeMirror.Pos(loc-1, 0), diff --git a/gui/public/codemirror/addon/lint/css-lint.js b/gui/public/codemirror/addon/lint/css-lint.js old mode 100755 new mode 100644 index 1f61b479..135d031a --- a/gui/public/codemirror/addon/lint/css-lint.js +++ b/gui/public/codemirror/addon/lint/css-lint.js @@ -15,10 +15,15 @@ })(function(CodeMirror) { "use strict"; -CodeMirror.registerHelper("lint", "css", function(text) { +CodeMirror.registerHelper("lint", "css", function(text, options) { var found = []; - if (!window.CSSLint) return found; - var results = CSSLint.verify(text), messages = results.messages, message = null; + if (!window.CSSLint) { + if (window.console) { + window.console.error("Error: window.CSSLint not defined, CodeMirror CSS linting cannot run."); + } + return found; + } + var results = CSSLint.verify(text, options), messages = results.messages, message = null; for ( var i = 0; i < messages.length; i++) { message = messages[i]; var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col; diff --git a/gui/public/codemirror/addon/lint/html-lint.js b/gui/public/codemirror/addon/lint/html-lint.js old mode 100755 new mode 100644 index 1e841709..23de9bb2 --- a/gui/public/codemirror/addon/lint/html-lint.js +++ b/gui/public/codemirror/addon/lint/html-lint.js @@ -11,8 +11,8 @@ else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "htmlhint"], mod); else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { + mod(CodeMirror, window.HTMLHint); +})(function(CodeMirror, HTMLHint) { "use strict"; var defaultRules = { @@ -29,7 +29,14 @@ CodeMirror.registerHelper("lint", "html", function(text, options) { var found = []; - if (!window.HTMLHint) return found; + if (HTMLHint && !HTMLHint.verify) HTMLHint = HTMLHint.HTMLHint; + if (!HTMLHint) HTMLHint = window.HTMLHint; + if (!HTMLHint) { + if (window.console) { + window.console.error("Error: HTMLHint not found, not defined on window, or not available through define/require, CodeMirror HTML linting cannot run."); + } + return found; + } var messages = HTMLHint.verify(text, options && options.rules || defaultRules); for (var i = 0; i < messages.length; i++) { var message = messages[i]; diff --git a/gui/public/codemirror/addon/lint/javascript-lint.js b/gui/public/codemirror/addon/lint/javascript-lint.js old mode 100755 new mode 100644 index d4f2ae9a..c58f7850 --- a/gui/public/codemirror/addon/lint/javascript-lint.js +++ b/gui/public/codemirror/addon/lint/javascript-lint.js @@ -22,7 +22,12 @@ "Unclosed string", "Stopping, unable to continue" ]; function validator(text, options) { - if (!window.JSHINT) return []; + if (!window.JSHINT) { + if (window.console) { + window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run."); + } + return []; + } JSHINT(text, options, options.globals); var errors = JSHINT.data().errors, result = []; if (errors) parseErrors(errors, result); diff --git a/gui/public/codemirror/addon/lint/json-lint.js b/gui/public/codemirror/addon/lint/json-lint.js old mode 100755 new mode 100644 index 9dbb616b..849641ee --- a/gui/public/codemirror/addon/lint/json-lint.js +++ b/gui/public/codemirror/addon/lint/json-lint.js @@ -17,6 +17,12 @@ CodeMirror.registerHelper("lint", "json", function(text) { var found = []; + if (!window.jsonlint) { + if (window.console) { + window.console.error("Error: window.jsonlint not defined, CodeMirror JSON linting cannot run."); + } + return found; + } jsonlint.parseError = function(str, hash) { var loc = hash.loc; found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column), diff --git a/gui/public/codemirror/addon/lint/lint.css b/gui/public/codemirror/addon/lint/lint.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/lint/lint.js b/gui/public/codemirror/addon/lint/lint.js old mode 100755 new mode 100644 index c1f1702f..a9eb8fa6 --- a/gui/public/codemirror/addon/lint/lint.js +++ b/gui/public/codemirror/addon/lint/lint.js @@ -112,7 +112,11 @@ if (!severity) severity = "error"; var tip = document.createElement("div"); tip.className = "CodeMirror-lint-message-" + severity; - tip.appendChild(document.createTextNode(ann.message)); + if (typeof ann.messageHTML != 'undefined') { + tip.innerHTML = ann.messageHTML; + } else { + tip.appendChild(document.createTextNode(ann.message)); + } return tip; } @@ -134,13 +138,22 @@ function startLinting(cm) { 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"); if (!getAnnotations) return; if (options.async || getAnnotations.async) { lintAsync(cm, getAnnotations, passOptions) } else { - updateLinting(cm, getAnnotations(cm.getValue(), passOptions, cm)); + var annotations = getAnnotations(cm.getValue(), passOptions, cm); + if (!annotations) return; + if (annotations.then) annotations.then(function(issues) { + updateLinting(cm, issues); + }); + else updateLinting(cm, annotations); } } diff --git a/gui/public/codemirror/addon/lint/yaml-lint.js b/gui/public/codemirror/addon/lint/yaml-lint.js old mode 100755 new mode 100644 index 3f77e525..3954ed38 --- a/gui/public/codemirror/addon/lint/yaml-lint.js +++ b/gui/public/codemirror/addon/lint/yaml-lint.js @@ -17,10 +17,23 @@ CodeMirror.registerHelper("lint", "yaml", function(text) { var found = []; + if (!window.jsyaml) { + if (window.console) { + window.console.error("Error: window.jsyaml not defined, CodeMirror YAML linting cannot run."); + } + return found; + } try { jsyaml.load(text); } catch(e) { - var loc = e.mark; - found.push({ from: CodeMirror.Pos(loc.line, loc.column), to: CodeMirror.Pos(loc.line, loc.column), message: e.message }); + var loc = e.mark, + // js-yaml YAMLException doesn't always provide an accurate lineno + // e.g., when there are multiple yaml docs + // --- + // --- + // foo:bar + from = loc ? CodeMirror.Pos(loc.line, loc.column) : CodeMirror.Pos(0, 0), + to = from; + found.push({ from: from, to: to, message: e.message }); } return found; }); diff --git a/gui/public/codemirror/addon/merge/merge.css b/gui/public/codemirror/addon/merge/merge.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/merge/merge.js b/gui/public/codemirror/addon/merge/merge.js old mode 100755 new mode 100644 index 66d160f3..dc2e77c6 --- a/gui/public/codemirror/addon/merge/merge.js +++ b/gui/public/codemirror/addon/merge/merge.js @@ -49,17 +49,17 @@ if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation] this.classes.classLocation = classLocation - this.diff = getDiff(asString(orig), asString(options.value)); + this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace); this.chunks = getChunks(this.diff); this.diffOutOfDate = this.dealigned = false; this.needsScrollSync = null this.showDifferences = options.showDifferences !== false; }, - registerEvents: function() { + registerEvents: function(otherDv) { this.forceUpdate = registerUpdate(this); setScrollLock(this, true, false); - registerScroll(this); + registerScroll(this, otherDv); }, setShowDifferences: function(val) { val = val !== false; @@ -72,7 +72,7 @@ function ensureDiff(dv) { if (dv.diffOutOfDate) { - dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue()); + dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace); dv.chunks = getChunks(dv.diff); dv.diffOutOfDate = false; CodeMirror.signal(dv.edit, "updateDiff", dv.diff); @@ -128,6 +128,7 @@ } function swapDoc() { dv.diffOutOfDate = true; + dv.dealigned = true; update("full"); } dv.edit.on("change", change); @@ -144,12 +145,13 @@ return update; } - function registerScroll(dv) { + function registerScroll(dv, otherDv) { dv.edit.on("scroll", function() { syncScroll(dv, true) && makeConnections(dv); }); dv.orig.on("scroll", function() { syncScroll(dv, false) && makeConnections(dv); + if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv); }); } @@ -352,11 +354,11 @@ var result = [] for (var i = 0;; i++) { var chunk = chunks[i] - var chunkStart = !chunk ? cm.lastLine() + 1 : isOrig ? chunk.origFrom : chunk.editFrom + var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom for (; trackI < tracker.alignable.length; trackI += 2) { var n = tracker.alignable[trackI] + 1 if (n <= start) continue - if (n < chunkStart) result.push(n) + if (n <= chunkStart) result.push(n) else break } if (!chunk) break @@ -370,14 +372,22 @@ // lines that need to be aligned with each other. function mergeAlignable(result, origAlignable, chunks, setIndex) { var rI = 0, origI = 0, chunkI = 0, diff = 0 - for (;; rI++) { + outer: for (;; rI++) { var nextR = result[rI], nextO = origAlignable[origI] if (!nextR && nextO == null) break var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO while (chunkI < chunks.length) { var chunk = chunks[chunkI] - if (chunk.editTo > rLine) break + if (chunk.origFrom <= oLine && chunk.origTo > oLine) { + origI++ + rI-- + continue outer; + } + if (chunk.editTo > rLine) { + if (chunk.editFrom <= rLine) continue outer; + break + } diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom) chunkI++ } @@ -467,7 +477,7 @@ var elt = document.createElement("div"); elt.className = "CodeMirror-merge-spacer"; elt.style.height = size + "px"; elt.style.minWidth = "1px"; - return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true}); + return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true}); } function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { @@ -567,8 +577,8 @@ this.aligners = []; alignChunks(this.left || this.right, true); } - if (left) left.registerEvents() - if (right) right.registerEvents() + if (left) left.registerEvents(right) + if (right) right.registerEvents(left) var onResize = function() { @@ -634,14 +644,15 @@ } // Operations on diffs + var dmp; + function getDiff(a, b, ignoreWhitespace) { + if (!dmp) dmp = new diff_match_patch(); - var dmp = new diff_match_patch(); - function getDiff(a, b) { var diff = dmp.diff_main(a, b); // The library sometimes leaves in empty parts, which confuse the algorithm for (var i = 0; i < diff.length; ++i) { var part = diff[i]; - if (!part[1]) { + if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) { diff.splice(i--, 1); } else if (i && diff[i - 1][0] == part[0]) { diff.splice(i--, 1); @@ -658,7 +669,7 @@ for (var i = 0; i < diff.length; ++i) { var part = diff[i], tp = part[0]; if (tp == DIFF_EQUAL) { - var startOff = startOfLineClean(diff, i) ? 0 : 1; + var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0; var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; moveOver(edit, part[1], null, orig); var endOff = endOfLineClean(diff, i) ? 1 : 0; @@ -727,6 +738,9 @@ mark.clear(); cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); } + if (mark.explicitlyCleared) clear(); + CodeMirror.on(widget, "click", clear); + mark.on("clear", clear); CodeMirror.on(widget, "click", clear); return {mark: mark, clear: clear}; } @@ -826,6 +840,7 @@ function TrackAlignable(cm) { this.cm = cm this.alignable = [] + this.height = cm.doc.height var self = this cm.on("markerAdded", function(_, marker) { if (!marker.collapsed) return @@ -855,11 +870,15 @@ self.check(end, F_MARKER, self.hasMarker) if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker) }) + cm.on("viewportChange", function() { + if (self.cm.doc.height != self.height) self.signal() + }) } TrackAlignable.prototype = { signal: function() { CodeMirror.signal(this, "realign") + this.height = this.cm.doc.height }, set: function(n, flags) { diff --git a/gui/public/codemirror/addon/mode/loadmode.js b/gui/public/codemirror/addon/mode/loadmode.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/mode/multiplex.js b/gui/public/codemirror/addon/mode/multiplex.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/mode/multiplex_test.js b/gui/public/codemirror/addon/mode/multiplex_test.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/mode/overlay.js b/gui/public/codemirror/addon/mode/overlay.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/mode/simple.js b/gui/public/codemirror/addon/mode/simple.js old mode 100755 new mode 100644 index df663365..c0f80108 --- a/gui/public/codemirror/addon/mode/simple.js +++ b/gui/public/codemirror/addon/mode/simple.js @@ -77,6 +77,7 @@ function asToken(val) { if (!val) return null; + if (val.apply) return val if (typeof val == "string") return val.replace(/\./g, " "); var result = []; for (var i = 0; i < val.length; i++) @@ -133,17 +134,19 @@ state.indent.push(stream.indentation() + config.indentUnit); if (rule.data.dedent) state.indent.pop(); - if (matches.length > 2) { + var token = rule.token + if (token && token.apply) token = token(matches) + if (matches.length > 2 && rule.token && typeof rule.token != "string") { state.pending = []; for (var j = 2; j < matches.length; j++) if (matches[j]) state.pending.push({text: matches[j], token: rule.token[j - 1]}); stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); - return rule.token[0]; - } else if (rule.token && rule.token.join) { - return rule.token[0]; + return token[0]; + } else if (token && token.join) { + return token[0]; } else { - return rule.token; + return token; } } } diff --git a/gui/public/codemirror/addon/runmode/colorize.js b/gui/public/codemirror/addon/runmode/colorize.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/runmode/runmode-standalone.js b/gui/public/codemirror/addon/runmode/runmode-standalone.js old mode 100755 new mode 100644 index f4f352c8..1bd808c7 --- a/gui/public/codemirror/addon/runmode/runmode-standalone.js +++ b/gui/public/codemirror/addon/runmode/runmode-standalone.js @@ -65,7 +65,8 @@ StringStream.prototype = { this.lineStart += n; try { return inner(); } finally { this.lineStart -= n; } - } + }, + lookAhead: function() { return null } }; CodeMirror.StringStream = StringStream; diff --git a/gui/public/codemirror/addon/runmode/runmode.js b/gui/public/codemirror/addon/runmode/runmode.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/runmode/runmode.node.js b/gui/public/codemirror/addon/runmode/runmode.node.js old mode 100755 new mode 100644 index b22a5187..21c72696 --- a/gui/public/codemirror/addon/runmode/runmode.node.js +++ b/gui/public/codemirror/addon/runmode/runmode.node.js @@ -7,7 +7,7 @@ function splitLines(string){return string.split(/\r\n?|\n/);}; // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. -var countColumn = function(string, end, tabSize, startIndex, startValue) { +var countColumn = exports.countColumn = function(string, end, tabSize, startIndex, startValue) { if (end == null) { end = string.search(/[^\s\u00a0]/); if (end == -1) end = string.length; @@ -22,12 +22,13 @@ var countColumn = function(string, end, tabSize, startIndex, startValue) { } }; -function StringStream(string, tabSize) { +function StringStream(string, tabSize, context) { this.pos = this.start = 0; this.string = string; this.tabSize = tabSize || 8; this.lastColumnPos = this.lastColumnValue = 0; this.lineStart = 0; + this.context = context }; StringStream.prototype = { @@ -91,6 +92,10 @@ StringStream.prototype = { this.lineStart += n; try { return inner(); } finally { this.lineStart -= n; } + }, + lookAhead: function(n) { + var line = this.context.line + n + return line >= this.context.lines.length ? null : this.context.lines[line] } }; exports.StringStream = StringStream; @@ -158,14 +163,27 @@ exports.getMode = function(options, spec) { return modeObj; }; + +exports.innerMode = function(mode, state) { + var info; + while (mode.innerMode) { + info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; +} + exports.registerHelper = exports.registerGlobalHelper = Math.min; exports.runMode = function(string, modespec, callback, options) { var mode = exports.getMode({indentUnit: 2}, modespec); var lines = splitLines(string), state = (options && options.state) || exports.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { + var context = {lines: lines, line: 0} + for (var i = 0, e = lines.length; i < e; ++i, ++context.line) { if (i) callback("\n"); - var stream = new exports.StringStream(lines[i]); + var stream = new exports.StringStream(lines[i], 4, context); if (!stream.string && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { var style = mode.token(stream, state); diff --git a/gui/public/codemirror/addon/scroll/annotatescrollbar.js b/gui/public/codemirror/addon/scroll/annotatescrollbar.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/scroll/scrollpastend.js b/gui/public/codemirror/addon/scroll/scrollpastend.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/scroll/simplescrollbars.css b/gui/public/codemirror/addon/scroll/simplescrollbars.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/scroll/simplescrollbars.js b/gui/public/codemirror/addon/scroll/simplescrollbars.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/search/jump-to-line.js b/gui/public/codemirror/addon/search/jump-to-line.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/search/match-highlighter.js b/gui/public/codemirror/addon/search/match-highlighter.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/search/matchesonscrollbar.css b/gui/public/codemirror/addon/search/matchesonscrollbar.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/search/matchesonscrollbar.js b/gui/public/codemirror/addon/search/matchesonscrollbar.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/search/search.js b/gui/public/codemirror/addon/search/search.js old mode 100755 new mode 100644 index 753b1afe..4059ccdd --- a/gui/public/codemirror/addon/search/search.js +++ b/gui/public/codemirror/addon/search/search.js @@ -54,7 +54,7 @@ function getSearchCursor(cm, query, pos) { // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); + return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true}); } function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { @@ -99,7 +99,7 @@ } var queryDialog = - 'Search: (Use /re/ syntax for regexp search)'; + 'Search: (Use /re/ syntax for regexp search)'; function startSearch(cm, state, query) { state.queryText = query; @@ -117,6 +117,7 @@ var state = getSearchState(cm); if (state.query) return findNext(cm, rev); var q = cm.getSelection() || state.lastQuery; + if (q instanceof RegExp && q.source == "x^") q = null if (persistent && cm.openDialog) { var hiding = null var searchNext = function(query, event) { @@ -137,8 +138,7 @@ }; persistentDialog(cm, queryDialog, q, searchNext, function(event, query) { var keyName = CodeMirror.keyName(event) - var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName] - if (!cmd) cmd = cm.getOption('extraKeys')[keyName] + var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName] if (cmd == "findNext" || cmd == "findPrev" || cmd == "findPersistentNext" || cmd == "findPersistentPrev") { CodeMirror.e_stop(event); @@ -188,8 +188,8 @@ var replaceQueryDialog = ' (Use /re/ syntax for regexp search)'; - var replacementQueryDialog = 'With: '; - var doReplaceConfirm = "Replace? "; + var replacementQueryDialog = 'With: '; + var doReplaceConfirm = 'Replace? '; function replaceAll(cm, query, text) { cm.operation(function() { @@ -205,7 +205,7 @@ function replace(cm, all) { if (cm.getOption("readOnly")) return; var query = cm.getSelection() || getSearchState(cm).lastQuery; - var dialogText = all ? "Replace all:" : "Replace:" + var dialogText = '' + (all ? 'Replace all:' : 'Replace:') + ''; dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) { if (!query) return; query = parseQuery(query); diff --git a/gui/public/codemirror/addon/search/searchcursor.js b/gui/public/codemirror/addon/search/searchcursor.js old mode 100755 new mode 100644 index b70242ee..58bc47c2 --- a/gui/public/codemirror/addon/search/searchcursor.js +++ b/gui/public/codemirror/addon/search/searchcursor.js @@ -3,187 +3,287 @@ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); + mod(require("../../lib/codemirror")) else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); + define(["../../lib/codemirror"], mod) else // Plain browser env - mod(CodeMirror); + mod(CodeMirror) })(function(CodeMirror) { - "use strict"; - var Pos = CodeMirror.Pos; + "use strict" + var Pos = CodeMirror.Pos - function SearchCursor(doc, query, pos, caseFold) { - this.atOccurrence = false; this.doc = doc; - if (caseFold == null && typeof query == "string") caseFold = false; + function regexpFlags(regexp) { + var flags = regexp.flags + return flags != null ? flags : (regexp.ignoreCase ? "i" : "") + + (regexp.global ? "g" : "") + + (regexp.multiline ? "m" : "") + } - pos = pos ? doc.clipPos(pos) : Pos(0, 0); - this.pos = {from: pos, to: pos}; + function ensureGlobal(regexp) { + return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g") + } - // The matches method is filled in based on the type of query. - // It takes a position and a direction, and returns an object - // describing the next occurrence of the query, or null if no - // more matches were found. - if (typeof query != "string") { // Regexp match - if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); - this.matches = function(reverse, pos) { - if (reverse) { - query.lastIndex = 0; - var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; - for (;;) { - query.lastIndex = cutOff; - var newMatch = query.exec(line); - if (!newMatch) break; - match = newMatch; - start = match.index; - cutOff = match.index + (match[0].length || 1); - if (cutOff == line.length) break; - } - var matchLen = (match && match[0].length) || 0; - if (!matchLen) { - if (start == 0 && line.length == 0) {match = undefined;} - else if (start != doc.getLine(pos.line).length) { - matchLen++; - } - } - } else { - query.lastIndex = pos.ch; - var line = doc.getLine(pos.line), match = query.exec(line); - var matchLen = (match && match[0].length) || 0; - var start = match && match.index; - if (start + matchLen != line.length && !matchLen) matchLen = 1; - } - if (match && matchLen) - return {from: Pos(pos.line, start), - to: Pos(pos.line, start + matchLen), - match: match}; - }; - } else { // String query - var origQuery = query; - if (caseFold) query = query.toLowerCase(); - var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; - var target = query.split("\n"); - // Different methods for single-line and multi-line queries - if (target.length == 1) { - if (!query.length) { - // Empty string would match anything and never progress, so - // we define it to match nothing instead. - this.matches = function() {}; - } else { - this.matches = function(reverse, pos) { - if (reverse) { - var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); - var match = line.lastIndexOf(query); - if (match > -1) { - match = adjustPos(orig, line, match); - return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; - } - } else { - var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); - var match = line.indexOf(query); - if (match > -1) { - match = adjustPos(orig, line, match) + pos.ch; - return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; - } - } - }; - } - } else { - var origTarget = origQuery.split("\n"); - this.matches = function(reverse, pos) { - var last = target.length - 1; - if (reverse) { - if (pos.line - (target.length - 1) < doc.firstLine()) return; - if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; - var to = Pos(pos.line, origTarget[last].length); - for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) - if (target[i] != fold(doc.getLine(ln))) return; - var line = doc.getLine(ln), cut = line.length - origTarget[0].length; - if (fold(line.slice(cut)) != target[0]) return; - return {from: Pos(ln, cut), to: to}; - } else { - if (pos.line + (target.length - 1) > doc.lastLine()) return; - var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; - if (fold(line.slice(cut)) != target[0]) return; - var from = Pos(pos.line, cut); - for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) - if (target[i] != fold(doc.getLine(ln))) return; - if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; - return {from: from, to: Pos(ln, origTarget[last].length)}; - } - }; + function maybeMultiline(regexp) { + return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source) + } + + function searchRegexpForward(doc, regexp, start) { + regexp = ensureGlobal(regexp) + for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) { + regexp.lastIndex = ch + var string = doc.getLine(line), match = regexp.exec(string) + if (match) + return {from: Pos(line, match.index), + to: Pos(line, match.index + match[0].length), + match: match} + } + } + + function searchRegexpForwardMultiline(doc, regexp, start) { + if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start) + + regexp = ensureGlobal(regexp) + var string, chunk = 1 + for (var line = start.line, last = doc.lastLine(); line <= last;) { + // This grows the search buffer in exponentially-sized chunks + // between matches, so that nearby matches are fast and don't + // require concatenating the whole document (in case we're + // searching for something that has tons of matches), but at the + // same time, the amount of retries is limited. + for (var i = 0; i < chunk; i++) { + var curLine = doc.getLine(line++) + string = string == null ? curLine : string + "\n" + curLine } + chunk = chunk * 2 + regexp.lastIndex = start.ch + var match = regexp.exec(string) + if (match) { + var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n") + var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length + return {from: Pos(startLine, startCh), + to: Pos(startLine + inside.length - 1, + inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length), + match: match} + } + } + } + + function lastMatchIn(string, regexp) { + var cutOff = 0, match + for (;;) { + regexp.lastIndex = cutOff + var newMatch = regexp.exec(string) + if (!newMatch) return match + match = newMatch + cutOff = match.index + (match[0].length || 1) + if (cutOff == string.length) return match + } + } + + function searchRegexpBackward(doc, regexp, start) { + regexp = ensureGlobal(regexp) + for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) { + var string = doc.getLine(line) + if (ch > -1) string = string.slice(0, ch) + var match = lastMatchIn(string, regexp) + if (match) + return {from: Pos(line, match.index), + to: Pos(line, match.index + match[0].length), + match: match} + } + } + + function searchRegexpBackwardMultiline(doc, regexp, start) { + regexp = ensureGlobal(regexp) + var string, chunk = 1 + for (var line = start.line, first = doc.firstLine(); line >= first;) { + for (var i = 0; i < chunk; i++) { + var curLine = doc.getLine(line--) + string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string + } + chunk *= 2 + + var match = lastMatchIn(string, regexp) + if (match) { + var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n") + var startLine = line + before.length, startCh = before[before.length - 1].length + return {from: Pos(startLine, startCh), + to: Pos(startLine + inside.length - 1, + inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length), + match: match} + } + } + } + + var doFold, noFold + if (String.prototype.normalize) { + doFold = function(str) { return str.normalize("NFD").toLowerCase() } + noFold = function(str) { return str.normalize("NFD") } + } else { + doFold = function(str) { return str.toLowerCase() } + noFold = function(str) { return str } + } + + // Maps a position in a case-folded line back to a position in the original line + // (compensating for codepoints increasing in number during folding) + function adjustPos(orig, folded, pos, foldFunc) { + if (orig.length == folded.length) return pos + for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) { + if (min == max) return min + var mid = (min + max) >> 1 + var len = foldFunc(orig.slice(0, mid)).length + if (len == pos) return mid + else if (len > pos) max = mid + else min = mid + 1 + } + } + + function searchStringForward(doc, query, start, caseFold) { + // Empty string would match anything and never progress, so we + // define it to match nothing instead. + if (!query.length) return null + var fold = caseFold ? doFold : noFold + var lines = fold(query).split(/\r|\n\r?/) + + search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) { + var orig = doc.getLine(line).slice(ch), string = fold(orig) + if (lines.length == 1) { + var found = string.indexOf(lines[0]) + if (found == -1) continue search + var start = adjustPos(orig, string, found, fold) + ch + return {from: Pos(line, adjustPos(orig, string, found, fold) + ch), + to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)} + } else { + var cutFrom = string.length - lines[0].length + if (string.slice(cutFrom) != lines[0]) continue search + for (var i = 1; i < lines.length - 1; i++) + if (fold(doc.getLine(line + i)) != lines[i]) continue search + var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1] + if (endString.slice(0, lastLine.length) != lastLine) continue search + return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch), + to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))} + } + } + } + + function searchStringBackward(doc, query, start, caseFold) { + if (!query.length) return null + var fold = caseFold ? doFold : noFold + var lines = fold(query).split(/\r|\n\r?/) + + search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) { + var orig = doc.getLine(line) + if (ch > -1) orig = orig.slice(0, ch) + var string = fold(orig) + if (lines.length == 1) { + var found = string.lastIndexOf(lines[0]) + if (found == -1) continue search + return {from: Pos(line, adjustPos(orig, string, found, fold)), + to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))} + } else { + var lastLine = lines[lines.length - 1] + if (string.slice(0, lastLine.length) != lastLine) continue search + for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++) + if (fold(doc.getLine(start + i)) != lines[i]) continue search + var top = doc.getLine(line + 1 - lines.length), topString = fold(top) + if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search + return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)), + to: Pos(line, adjustPos(orig, string, lastLine.length, fold))} + } + } + } + + function SearchCursor(doc, query, pos, options) { + this.atOccurrence = false + this.doc = doc + pos = pos ? doc.clipPos(pos) : Pos(0, 0) + this.pos = {from: pos, to: pos} + + var caseFold + if (typeof options == "object") { + caseFold = options.caseFold + } else { // Backwards compat for when caseFold was the 4th argument + caseFold = options + options = null + } + + if (typeof query == "string") { + if (caseFold == null) caseFold = false + this.matches = function(reverse, pos) { + return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold) + } + } else { + query = ensureGlobal(query) + if (!options || options.multiline !== false) + this.matches = function(reverse, pos) { + return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos) + } + else + this.matches = function(reverse, pos) { + return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos) + } } } SearchCursor.prototype = { - findNext: function() {return this.find(false);}, - findPrevious: function() {return this.find(true);}, + findNext: function() {return this.find(false)}, + findPrevious: function() {return this.find(true)}, find: function(reverse) { - var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); - function savePosAndFail(line) { - var pos = Pos(line, 0); - self.pos = {from: pos, to: pos}; - self.atOccurrence = false; - return false; + var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to)) + + // Implements weird auto-growing behavior on null-matches for + // backwards-compatiblity with the vim code (unfortunately) + while (result && CodeMirror.cmpPos(result.from, result.to) == 0) { + if (reverse) { + if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1) + else if (result.from.line == this.doc.firstLine()) result = null + else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1))) + } else { + if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1) + else if (result.to.line == this.doc.lastLine()) result = null + else result = this.matches(reverse, Pos(result.to.line + 1, 0)) + } } - for (;;) { - if (this.pos = this.matches(reverse, pos)) { - this.atOccurrence = true; - return this.pos.match || true; - } - if (reverse) { - if (!pos.line) return savePosAndFail(0); - pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); - } - else { - var maxLine = this.doc.lineCount(); - if (pos.line == maxLine - 1) return savePosAndFail(maxLine); - pos = Pos(pos.line + 1, 0); - } + if (result) { + this.pos = result + this.atOccurrence = true + return this.pos.match || true + } else { + var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0) + this.pos = {from: end, to: end} + return this.atOccurrence = false } }, - from: function() {if (this.atOccurrence) return this.pos.from;}, - to: function() {if (this.atOccurrence) return this.pos.to;}, + from: function() {if (this.atOccurrence) return this.pos.from}, + to: function() {if (this.atOccurrence) return this.pos.to}, replace: function(newText, origin) { - if (!this.atOccurrence) return; - var lines = CodeMirror.splitLines(newText); - this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); + if (!this.atOccurrence) return + var lines = CodeMirror.splitLines(newText) + this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin) this.pos.to = Pos(this.pos.from.line + lines.length - 1, - lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); - } - }; - - // Maps a position in a case-folded line back to a position in the original line - // (compensating for codepoints increasing in number during folding) - function adjustPos(orig, folded, pos) { - if (orig.length == folded.length) return pos; - for (var pos1 = Math.min(pos, orig.length);;) { - var len1 = orig.slice(0, pos1).toLowerCase().length; - if (len1 < pos) ++pos1; - else if (len1 > pos) --pos1; - else return pos1; + lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)) } } CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { - return new SearchCursor(this.doc, query, pos, caseFold); - }); + return new SearchCursor(this.doc, query, pos, caseFold) + }) CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { - return new SearchCursor(this, query, pos, caseFold); - }); + return new SearchCursor(this, query, pos, caseFold) + }) CodeMirror.defineExtension("selectMatches", function(query, caseFold) { - var ranges = []; - var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); + var ranges = [] + var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold) while (cur.findNext()) { - if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; - ranges.push({anchor: cur.from(), head: cur.to()}); + if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break + ranges.push({anchor: cur.from(), head: cur.to()}) } if (ranges.length) - this.setSelections(ranges, 0); - }); + this.setSelections(ranges, 0) + }) }); diff --git a/gui/public/codemirror/addon/selection/active-line.js b/gui/public/codemirror/addon/selection/active-line.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/selection/mark-selection.js b/gui/public/codemirror/addon/selection/mark-selection.js old mode 100755 new mode 100644 index 5c42d21e..1602acc3 --- a/gui/public/codemirror/addon/selection/mark-selection.js +++ b/gui/public/codemirror/addon/selection/mark-selection.js @@ -34,11 +34,12 @@ }); function onCursorActivity(cm) { - cm.operation(function() { update(cm); }); + if (cm.state.markedSelection) + cm.operation(function() { update(cm); }); } function onChange(cm) { - if (cm.state.markedSelection.length) + if (cm.state.markedSelection && cm.state.markedSelection.length) cm.operation(function() { clear(cm); }); } @@ -85,7 +86,7 @@ if (!array.length) return coverRange(cm, from, to); 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) return reset(cm); diff --git a/gui/public/codemirror/addon/selection/selection-pointer.js b/gui/public/codemirror/addon/selection/selection-pointer.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/tern/tern.css b/gui/public/codemirror/addon/tern/tern.css old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/tern/tern.js b/gui/public/codemirror/addon/tern/tern.js old mode 100755 new mode 100644 index efdf2ed6..a80dc7e4 --- a/gui/public/codemirror/addon/tern/tern.js +++ b/gui/public/codemirror/addon/tern/tern.js @@ -334,7 +334,11 @@ tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")")); if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype)); var place = cm.cursorCoords(null, "page"); - ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip); + var tooltip = ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip) + setTimeout(function() { + tooltip.clear = onEditorActivity(cm, function() { + if (ts.activeArgHints == tooltip) closeArgHints(ts) }) + }, 20) } function parseFnType(text) { @@ -567,7 +571,7 @@ return {type: "part", name: data.name, offsetLines: from.line, - text: doc.getRange(from, Pos(endLine, 0))}; + text: doc.getRange(from, Pos(endLine, end.line == endLine ? null : 0))}; } // Generic utilities @@ -604,11 +608,8 @@ } function clear() { cm.state.ternTooltip = null; - if (!tip.parentNode) return; - cm.off("cursorActivity", clear); - cm.off('blur', clear); - cm.off('scroll', clear); - fadeOut(tip); + if (tip.parentNode) fadeOut(tip) + clearActivity() } var mouseOnTip = false, old = false; CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; }); @@ -619,9 +620,20 @@ } }); setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700); - cm.on("cursorActivity", clear); - cm.on('blur', clear); - cm.on('scroll', clear); + var clearActivity = onEditorActivity(cm, clear) + } + + function onEditorActivity(cm, f) { + cm.on("cursorActivity", f) + cm.on("blur", f) + cm.on("scroll", f) + cm.on("setDoc", f) + return function() { + cm.off("cursorActivity", f) + cm.off("blur", f) + cm.off("scroll", f) + cm.off("setDoc", f) + } } function makeTooltip(x, y, content) { @@ -650,7 +662,11 @@ } function closeArgHints(ts) { - if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; } + if (ts.activeArgHints) { + if (ts.activeArgHints.clear) ts.activeArgHints.clear() + remove(ts.activeArgHints) + ts.activeArgHints = null + } } function docValue(ts, doc) { diff --git a/gui/public/codemirror/addon/tern/worker.js b/gui/public/codemirror/addon/tern/worker.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/addon/wrap/hardwrap.js b/gui/public/codemirror/addon/wrap/hardwrap.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/lib/codemirror.js b/gui/public/codemirror/lib/codemirror.js index 80ab0653..a9fa82fa 100755 --- a/gui/public/codemirror/lib/codemirror.js +++ b/gui/public/codemirror/lib/codemirror.js @@ -76,6 +76,12 @@ function elt(tag, content, className, style) { else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]) } } return e } +// wrapper for elt, which removes the elt from the accessibility tree +function eltP(tag, content, className, style) { + var e = elt(tag, content, className, style) + e.setAttribute("role", "presentation") + return e +} var range if (document.createRange) { range = function(node, start, end, endNode) { @@ -272,13 +278,18 @@ function skipExtendingChars(str, pos, dir) { } // 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`. function findFirst(pred, from, to) { + // At any point we are certain `to` satisfies `pred`, don't know + // whether `from` does. + var dir = from > to ? -1 : 1 for (;;) { - if (Math.abs(from - to) <= 1) { return pred(from) ? from : to } - var mid = Math.floor((from + to) / 2) + if (from == to) { return from } + var 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 } } } @@ -298,7 +309,7 @@ function Display(place, doc, input) { d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") d.gutterFiller.setAttribute("cm-not-content", "true") // Will contain the actual code, positioned to cover the viewport. - d.lineDiv = elt("div", null, "CodeMirror-code") + d.lineDiv = eltP("div", null, "CodeMirror-code") // Elements are added to these to represent selection and cursors. d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") d.cursorDiv = elt("div", null, "CodeMirror-cursors") @@ -307,10 +318,11 @@ function Display(place, doc, input) { // When lines outside of the viewport are measured, they are drawn in this. d.lineMeasure = elt("div", null, "CodeMirror-measure") // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], null, "position: relative; outline: none") + var lines = eltP("div", [d.lineSpace], "CodeMirror-lines") // Moved around its parent to cover visible view. - d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative") + d.mover = elt("div", [lines], null, "position: relative") // Set to the height of the document, allowing scrolling. d.sizer = elt("div", [d.mover], "CodeMirror-sizer") d.sizerWidth = null @@ -890,12 +902,12 @@ function findMaxLine(cm) { // BIDI HELPERS function iterateBidiSections(order, from, to, f) { - if (!order) { return f(from, to, "ltr") } + if (!order) { return f(from, to, "ltr", 0) } var found = false for (var i = 0; i < order.length; ++i) { var part = order[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 } } @@ -1076,13 +1088,15 @@ var bidiOrdering = (function() { if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) } } } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length - order.unshift(new BidiSpan(0, 0, m[0].length)) - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length - order.push(new BidiSpan(0, len - m[0].length, len)) + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length + order.unshift(new BidiSpan(0, 0, m[0].length)) + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length + order.push(new BidiSpan(0, len - m[0].length, len)) + } } return direction == "rtl" ? order.reverse() : order @@ -1098,112 +1112,6 @@ function getOrder(line, direction) { return order } -function moveCharLogically(line, ch, dir) { - var target = skipExtendingChars(line.text, ch + dir, dir) - return target < 0 || target > line.text.length ? null : target -} - -function moveLogically(line, start, dir) { - var ch = moveCharLogically(line, start.ch, dir) - return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") -} - -function endOfLine(visually, cm, lineObj, lineNo, dir) { - if (visually) { - var order = getOrder(lineObj, cm.doc.direction) - if (order) { - var part = dir < 0 ? lst(order) : order[0] - var moveInStorageOrder = (dir < 0) == (part.level == 1) - var sticky = moveInStorageOrder ? "after" : "before" - var ch - // With a wrapped rtl chunk (possibly spanning multiple bidi parts), - // it could be that the last bidi part is not on the last visual line, - // since visual lines contain content order-consecutive chunks. - // Thus, in rtl, we are looking for the first (content-order) character - // in the rtl chunk that is on the last line (that is, the same line - // as the last (content-order) character). - if (part.level > 0) { - var prep = prepareMeasureForLine(cm, lineObj) - ch = dir < 0 ? lineObj.text.length - 1 : 0 - var targetTop = measureCharPrepared(cm, prep, ch).top - ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) - if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1, true) } - } else { ch = dir < 0 ? part.to : part.from } - return new Pos(lineNo, ch, sticky) - } - } - return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") -} - -function moveVisually(cm, line, start, dir) { - var bidi = getOrder(line, cm.doc.direction) - if (!bidi) { return moveLogically(line, start, dir) } - if (start.ch >= line.text.length) { - start.ch = line.text.length - start.sticky = "before" - } else if (start.ch <= 0) { - start.ch = 0 - start.sticky = "after" - } - var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] - if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { - // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, - // nothing interesting happens. - return moveLogically(line, start, dir) - } - - var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); } - var prep - var getWrappedLineExtent = function (ch) { - if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } - prep = prep || prepareMeasureForLine(cm, line) - return wrappedLineExtentChar(cm, line, prep, ch) - } - var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) - - if (cm.doc.direction == "rtl" || part.level == 1) { - var moveInStorageOrder = (part.level == 1) == (dir < 0) - var ch = mv(start, moveInStorageOrder ? 1 : -1) - if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { - // Case 2: We move within an rtl part or in an rtl editor on the same visual line - var sticky = moveInStorageOrder ? "before" : "after" - return new Pos(start.line, ch, sticky) - } - } - - // Case 3: Could not move within this bidi part in this visual line, so leave - // the current bidi part - - var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { - var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder - ? new Pos(start.line, mv(ch, 1), "before") - : new Pos(start.line, ch, "after"); } - - for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { - var part = bidi[partPos] - var moveInStorageOrder = (dir > 0) == (part.level != 1) - var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) - if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } - ch = moveInStorageOrder ? part.from : mv(part.to, -1) - if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } - } - } - - // Case 3a: Look for other bidi parts on the same visual line - var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) - if (res) { return res } - - // Case 3b: Look for other bidi parts on the next visual line - var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) - if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { - res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) - if (res) { return res } - } - - // Case 4: Nowhere to move - return null -} - // EVENT HANDLING // Lightweight event framework. on/off also work on DOM nodes, @@ -1486,12 +1394,13 @@ function startState(mode, a1, a2) { // Fed to the mode parsers, provides helper functions to make // parsers more succinct. -var StringStream = function(string, tabSize) { +var StringStream = function(string, tabSize, lineOracle) { this.pos = this.start = 0 this.string = string this.tabSize = tabSize || 8 this.lastColumnPos = this.lastColumnValue = 0 this.lineStart = 0 + this.lineOracle = lineOracle }; StringStream.prototype.eol = function () {return this.pos >= this.string.length}; @@ -1558,23 +1467,83 @@ StringStream.prototype.hideFirstChars = function (n, inner) { try { return inner() } finally { this.lineStart -= n } }; +StringStream.prototype.lookAhead = function (n) { + var oracle = this.lineOracle + return oracle && oracle.lookAhead(n) +}; +StringStream.prototype.baseToken = function () { + var oracle = this.lineOracle + return oracle && oracle.baseToken(this.pos) +}; + +var SavedContext = function(state, lookAhead) { + this.state = state + this.lookAhead = lookAhead +}; + +var Context = function(doc, state, line, lookAhead) { + this.state = state + this.doc = doc + this.line = line + this.maxLookAhead = lookAhead || 0 + this.baseTokens = null + this.baseTokenPos = 1 +}; + +Context.prototype.lookAhead = function (n) { + var line = this.doc.getLine(this.line + n) + if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n } + return line +}; + +Context.prototype.baseToken = function (n) { + var this$1 = this; + + if (!this.baseTokens) { return null } + while (this.baseTokens[this.baseTokenPos] <= n) + { this$1.baseTokenPos += 2 } + var type = this.baseTokens[this.baseTokenPos + 1] + return {type: type && type.replace(/( |^)overlay .*/, ""), + size: this.baseTokens[this.baseTokenPos] - n} +}; + +Context.prototype.nextLine = function () { + this.line++ + if (this.maxLookAhead > 0) { this.maxLookAhead-- } +}; + +Context.fromSaved = function (doc, saved, line) { + if (saved instanceof SavedContext) + { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } + else + { return new Context(doc, copyState(doc.mode, saved), line) } +}; + +Context.prototype.save = function (copy) { + var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state + return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state +}; + // Compute a style array (an array starting with a mode generation // -- for invalidation -- followed by pairs of end positions and // style strings), which is used to highlight the tokens on the // line. -function highlightLine(cm, line, state, forceToEnd) { +function highlightLine(cm, line, context, forceToEnd) { // A styles array always starts with a number identifying the // mode/overlays that it is based on (for easy invalidation). var st = [cm.state.modeGen], lineClasses = {} // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function (end, style) { return st.push(end, style); }, - lineClasses, forceToEnd) + runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, + lineClasses, forceToEnd) + var state = context.state // Run overlays, adjust style array. var loop = function ( o ) { + context.baseTokens = st var overlay = cm.state.overlays[o], i = 1, at = 0 - runMode(cm, line.text, overlay.mode, true, function (end, style) { + context.state = true + runMode(cm, line.text, overlay.mode, context, function (end, style) { var start = i // Ensure there's a token end at the current position, and that i points at it while (at < end) { @@ -1595,6 +1564,9 @@ function highlightLine(cm, line, state, forceToEnd) { } } }, lineClasses) + context.state = state + context.baseTokens = null + context.baseTokenPos = 1 }; for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); @@ -1604,43 +1576,47 @@ function highlightLine(cm, line, state, forceToEnd) { function getLineStyles(cm, line, updateFrontier) { if (!line.styles || line.styles[0] != cm.state.modeGen) { - var state = getStateBefore(cm, lineNo(line)) - var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state) - line.stateAfter = state + var context = getContextBefore(cm, lineNo(line)) + var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) + var result = highlightLine(cm, line, context) + if (resetState) { context.state = resetState } + line.stateAfter = context.save(!resetState) line.styles = result.styles if (result.classes) { line.styleClasses = result.classes } else if (line.styleClasses) { line.styleClasses = null } - if (updateFrontier === cm.doc.frontier) { cm.doc.frontier++ } + if (updateFrontier === cm.doc.highlightFrontier) + { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) } } return line.styles } -function getStateBefore(cm, n, precise) { +function getContextBefore(cm, n, precise) { var doc = cm.doc, display = cm.display - if (!doc.mode.startState) { return true } - var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter - if (!state) { state = startState(doc.mode) } - else { state = copyState(doc.mode, state) } - doc.iter(pos, n, function (line) { - processLine(cm, line.text, state) - var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo - line.stateAfter = save ? copyState(doc.mode, state) : null - ++pos + if (!doc.mode.startState) { return new Context(doc, true, n) } + var start = findStartLine(cm, n, precise) + var saved = start > doc.first && getLine(doc, start - 1).stateAfter + var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) + + doc.iter(start, n, function (line) { + processLine(cm, line.text, context) + var pos = context.line + line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null + context.nextLine() }) - if (precise) { doc.frontier = pos } - return state + if (precise) { doc.modeFrontier = context.line } + return context } // Lightweight form of highlight -- proceed over this line and // update state, but don't save a style array. Used for lines that // aren't currently visible. -function processLine(cm, text, state, startAt) { +function processLine(cm, text, context, startAt) { var mode = cm.doc.mode - var stream = new StringStream(text, cm.options.tabSize) + var stream = new StringStream(text, cm.options.tabSize, context) stream.start = stream.pos = startAt || 0 - if (text == "") { callBlankLine(mode, state) } + if (text == "") { callBlankLine(mode, context.state) } while (!stream.eol()) { - readToken(mode, stream, state) + readToken(mode, stream, context.state) stream.start = stream.pos } } @@ -1661,26 +1637,26 @@ function readToken(mode, stream, state, inner) { throw new Error("Mode " + mode.name + " failed to advance stream.") } +var Token = function(stream, type, state) { + this.start = stream.start; this.end = stream.pos + this.string = stream.current() + this.type = type || null + this.state = state +}; + // Utility for getTokenAt and getLineTokens function takeToken(cm, pos, precise, asArray) { - var getObj = function (copy) { return ({ - start: stream.start, end: stream.pos, - string: stream.current(), - type: style || null, - state: copy ? copyState(doc.mode, state) : state - }); } - var doc = cm.doc, mode = doc.mode, style pos = clipPos(doc, pos) - var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise) - var stream = new StringStream(line.text, cm.options.tabSize), tokens + var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) + var stream = new StringStream(line.text, cm.options.tabSize, context), tokens if (asArray) { tokens = [] } while ((asArray || stream.pos < pos.ch) && !stream.eol()) { stream.start = stream.pos - style = readToken(mode, stream, state) - if (asArray) { tokens.push(getObj(true)) } + style = readToken(mode, stream, context.state) + if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) } } - return asArray ? tokens : getObj() + return asArray ? tokens : new Token(stream, style, context.state) } function extractLineClasses(type, output) { @@ -1698,21 +1674,21 @@ function extractLineClasses(type, output) { } // Run the given mode's parser over a line, calling f for each token. -function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { +function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { var flattenSpans = mode.flattenSpans if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans } var curStart = 0, curStyle = null - var stream = new StringStream(text, cm.options.tabSize), style + var stream = new StringStream(text, cm.options.tabSize, context), style var inner = cm.options.addModeClass && [null] - if (text == "") { extractLineClasses(callBlankLine(mode, state), lineClasses) } + if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses) } while (!stream.eol()) { if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false - if (forceToEnd) { processLine(cm, text, state, stream.pos) } + if (forceToEnd) { processLine(cm, text, context, stream.pos) } stream.pos = text.length style = null } else { - style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses) + style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) } if (inner) { var mName = inner[0].name @@ -1747,8 +1723,9 @@ function findStartLine(cm, n, precise) { var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) for (var search = n; search > lim; --search) { if (search <= doc.first) { return doc.first } - var line = getLine(doc, search - 1) - if (line.stateAfter && (!precise || search <= doc.frontier)) { return search } + var line = getLine(doc, search - 1), after = line.stateAfter + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) + { return search } var indented = countColumn(line.text, null, cm.options.tabSize) if (minline == null || minindent > indented) { minline = search - 1 @@ -1758,6 +1735,23 @@ function findStartLine(cm, n, precise) { return minline } +function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n) + if (doc.highlightFrontier < n - 10) { return } + var start = doc.first + for (var line = n - 1; line > start; line--) { + var saved = getLine(doc, line).stateAfter + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1 + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start) +} + // LINE DATA STRUCTURE // Line objects. These hold state related to a line, including @@ -1812,14 +1806,11 @@ function buildLineContent(cm, lineView) { // The padding-right forces the element to have a 'border', which // is needed on Webkit to be able to get line-level bounding // rectangles for it (in measureChar). - var content = elt("span", null, null, webkit ? "padding-right: .1px" : null) - var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content, + var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) + var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, col: 0, pos: 0, cm: cm, trailingSpace: false, splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} - // hide from accessibility tree - content.setAttribute("role", "presentation") - builder.pre.setAttribute("role", "presentation") lineView.measure = {} // Iterate over the logical lines that make up this visual line. @@ -2637,18 +2628,34 @@ function clearCaches(cm) { cm.display.lineNumChars = null } -function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft } -function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop } +function pageScrollX() { + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 + // which causes page_Offset and bounding client rects to use + // different reference viewports and invalidate our calculations. + if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) } + return window.pageXOffset || (document.documentElement || document.body).scrollLeft +} +function pageScrollY() { + if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) } + return window.pageYOffset || (document.documentElement || document.body).scrollTop +} + +function widgetTopHeight(lineObj) { + var height = 0 + if (lineObj.widgets) { for (var 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 // coordinates into another coordinate system. Context may be one of // "line", "div" (display.lineDiv), "local"./null (editor), "window", // or "page". function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { - if (!includeWidgets && lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above) { - var size = widgetHeight(lineObj.widgets[i]) - rect.top += size; rect.bottom += size - } } } + if (!includeWidgets) { + var height = widgetTopHeight(lineObj) + rect.top += height; rect.bottom += height + } if (context == "line") { return rect } if (!context) { context = "local" } var yOff = heightAtLine(lineObj) @@ -2723,7 +2730,7 @@ function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } function getBidi(ch, partPos, invert) { - var part = order[partPos], right = (part.level % 2) != 0 + var part = order[partPos], right = part.level == 1 return get(invert ? ch - 1 : ch, right != invert) } var partPos = getBidiPartAt(order, ch, sticky) @@ -2781,67 +2788,147 @@ function coordsChar(cm, x, y) { } function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { - var measure = function (ch) { return intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, ch), "line"); } + y -= widgetTopHeight(lineObj) var end = lineObj.text.length - var begin = findFirst(function (ch) { return measure(ch - 1).bottom <= y; }, end, 0) - end = findFirst(function (ch) { return measure(ch).top > y; }, begin, end) + var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0) + end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end) return {begin: begin, end: end} } function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj) } var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top 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) - var begin = 0, end = lineObj.text.length var preparedMeasure = prepareMeasureForLine(cm, lineObj) - var pos + // When directly calling `measureCharPrepared`, we have to adjust + // for the widgets at this line. + var widgetHeight = widgetTopHeight(lineObj) + var begin = 0, end = lineObj.text.length, ltr = true + var 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) { - ;var assign; - ((assign = wrappedLineExtent(cm, lineObj, preparedMeasure, y), begin = assign.begin, end = assign.end, assign)) - } - pos = new Pos(lineNo, begin) - var beginLeft = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - var dir = beginLeft < x ? 1 : -1 - var prevDiff, diff = beginLeft - x, prevPos - do { - prevDiff = diff - 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 - } - diff = cursorCoords(cm, pos, "line", lineObj, preparedMeasure).left - x - } while ((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 - } - } else { - var ch = findFirst(function (ch) { - var 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") + var 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 } - var 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 + + // 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. + var chAround = null, boxAround = null + var ch = findFirst(function (ch) { + var 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 + } + return true + }, begin, end) + + var 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 + var 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 { + // (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 + var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) + baseX = coords.left + outside = y < coords.top || y >= coords.bottom + } + + 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. + var index = findFirst(function (i) { + var 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) + var 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) { + var ltr = part.level != 1 + var 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. + var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); + var begin = ref.begin; + var end = ref.end; + if (/\s/.test(lineObj.text.charAt(end - 1))) { end-- } + var part = null, closestDist = null + for (var i = 0; i < order.length; i++) { + var p = order[i] + if (p.from >= end || p.to <= begin) { continue } + var ltr = p.level != 1 + var 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 + var 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 } var measureText @@ -2967,12 +3054,14 @@ function updateSelection(cm) { } function prepareSelection(cm, primary) { + if ( primary === void 0 ) primary = true; + var doc = cm.doc, result = {} var curFragment = result.cursors = document.createDocumentFragment() var selFragment = result.selection = document.createDocumentFragment() for (var i = 0; i < doc.sel.ranges.length; i++) { - if (primary === false && i == doc.sel.primIndex) { continue } + if (!primary && i == doc.sel.primIndex) { continue } var range = doc.sel.ranges[i] if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } var collapsed = range.empty() @@ -3003,12 +3092,15 @@ function drawSelectionCursor(cm, head, output) { } } +function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } + // Draws the given range as a highlighted selection function drawSelectionRange(cm, range, output) { var display = cm.display, doc = cm.doc var fragment = document.createDocumentFragment() var padding = paddingH(cm.display), leftSide = padding.left var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right + var docLTR = doc.direction == "ltr" function add(left, top, width, bottom) { if (top < 0) { top = 0 } @@ -3025,30 +3117,49 @@ function drawSelectionRange(cm, range, output) { return charCoords(cm, Pos(line, ch), "div", lineObj, bias) } - iterateBidiSections(getOrder(lineObj, doc.direction), fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir) { - var 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") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp } - left = leftPos.left - right = rightPos.right + function wrapX(pos, dir, side) { + var extent = wrappedLineExtentChar(cm, lineObj, null, pos) + var prop = (dir == "ltr") == (side == "after") ? "left" : "right" + var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1) + return coords(ch, prop)[prop] + } + + var order = getOrder(lineObj, doc.direction) + iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { + var ltr = dir == "ltr" + var fromPos = coords(from, ltr ? "left" : "right") + var toPos = coords(to - 1, ltr ? "right" : "left") + + var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen + var first = i == 0, last = !order || i == order.length - 1 + if (toPos.top - fromPos.top <= 3) { // Single line + var openLeft = (docLTR ? openStart : openEnd) && first + var openRight = (docLTR ? openEnd : openStart) && last + var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left + var right = openRight ? rightSide : (ltr ? toPos : fromPos).right + add(left, fromPos.top, right - left, fromPos.bottom) + } else { // Multiple lines + var topLeft, topRight, botLeft, botRight + if (ltr) { + topLeft = docLTR && openStart && first ? leftSide : fromPos.left + topRight = docLTR ? rightSide : wrapX(from, dir, "before") + botLeft = docLTR ? leftSide : wrapX(to, dir, "after") + botRight = docLTR && openEnd && last ? rightSide : toPos.right + } else { + topLeft = !docLTR ? leftSide : wrapX(from, dir, "before") + topRight = !docLTR && openStart && first ? rightSide : fromPos.right + botLeft = !docLTR && openEnd && last ? leftSide : toPos.left + botRight = !docLTR ? rightSide : wrapX(to, dir, "after") + } + add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) + if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) } + add(botLeft, toPos.top, botRight - botLeft, 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 } }) return {start: start, end: end} } @@ -3133,6 +3244,66 @@ function onBlur(cm, e) { setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false } }, 150) } +// Read the actual heights of the rendered lines, and update their +// stored heights to match. +function updateHeightsInViewport(cm) { + var display = cm.display + var prevBottom = display.lineDiv.offsetTop + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height = (void 0) + if (cur.hidden) { continue } + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight + height = bot - prevBottom + prevBottom = bot + } else { + var box = cur.node.getBoundingClientRect() + height = box.bottom - box.top + } + var diff = cur.line.height - height + if (height < 2) { height = textHeight(display) } + if (diff > .005 || diff < -.005) { + updateLineHeight(cur.line, height) + updateWidgetHeight(cur.line) + if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) + { updateWidgetHeight(cur.rest[j]) } } + } + } +} + +// Read and store the height of line widgets associated with the +// given line. +function updateWidgetHeight(line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i], parent = w.node.parentNode + if (parent) { w.height = parent.offsetHeight } + } } +} + +// Compute the lines that are visible in a given viewport (defaults +// the the current scroll position). viewport may contain top, +// height, and ensure (see op.scrollToPos) properties. +function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop + top = Math.floor(top - paddingTop(display)) + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line + if (ensureFrom < from) { + from = ensureFrom + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) + to = ensureTo + } + } + return {from: from, to: Math.max(to, from + 1)} +} + // Re-align line numbers and gutter marks to compensate for // horizontal scrolling. function alignHorizontally(cm) { @@ -3176,197 +3347,178 @@ function maybeUpdateLineNumberWidth(cm) { return false } -// Read the actual heights of the rendered lines, and update their -// stored heights to match. -function updateHeightsInViewport(cm) { - var display = cm.display - var prevBottom = display.lineDiv.offsetTop - for (var i = 0; i < display.view.length; i++) { - var cur = display.view[i], height = (void 0) - if (cur.hidden) { continue } - if (ie && ie_version < 8) { - var bot = cur.node.offsetTop + cur.node.offsetHeight - height = bot - prevBottom - prevBottom = bot - } else { - var box = cur.node.getBoundingClientRect() - height = box.bottom - box.top - } - var diff = cur.line.height - height - if (height < 2) { height = textHeight(display) } - if (diff > .001 || diff < -.001) { - updateLineHeight(cur.line, height) - updateWidgetHeight(cur.line) - if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) - { updateWidgetHeight(cur.rest[j]) } } - } +// SCROLLING THINGS INTO VIEW + +// If an editor sits on the top or bottom of the window, partially +// scrolled out of view, this ensures that the cursor is visible. +function maybeScrollWindow(cm, rect) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null + if (rect.top + box.top < 0) { doScroll = true } + else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")) + cm.display.lineSpace.appendChild(scrollNode) + scrollNode.scrollIntoView(doScroll) + cm.display.lineSpace.removeChild(scrollNode) } } -// Read and store the height of line widgets associated with the -// given line. -function updateWidgetHeight(line) { - if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) - { line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight } } +// Scroll a given position into view (immediately), verifying that +// it actually became visible (as line heights are accurately +// measured, the position of something may 'drift' during drawing). +function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) { margin = 0 } + var rect + if (!cm.options.lineWrapping && pos == end) { + // Set pos and end to the cursor positions around the character pos sticks to + // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch + // If pos == Pos(_, 0, "before"), pos and end are unchanged + pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos + end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos + } + for (var limit = 0; limit < 5; limit++) { + var changed = false + var coords = cursorCoords(cm, pos) + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) + rect = {left: Math.min(coords.left, endCoords.left), + top: Math.min(coords.top, endCoords.top) - margin, + right: Math.max(coords.left, endCoords.left), + bottom: Math.max(coords.bottom, endCoords.bottom) + margin} + var scrollPos = calculateScrollPos(cm, rect) + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft + if (scrollPos.scrollTop != null) { + updateScrollTop(cm, scrollPos.scrollTop) + if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft) + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } + } + if (!changed) { break } + } + return rect } -// Compute the lines that are visible in a given viewport (defaults -// the the current scroll position). viewport may contain top, -// height, and ensure (see op.scrollToPos) properties. -function visibleLines(display, doc, viewport) { - var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop - top = Math.floor(top - paddingTop(display)) - var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight +// Scroll a given set of coordinates into view (immediately). +function scrollIntoView(cm, rect) { + var scrollPos = calculateScrollPos(cm, rect) + if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop) } + if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } +} - var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) - // Ensure is a {from: {line, ch}, to: {line, ch}} object, and - // forces those lines into the viewport (if possible). - if (viewport && viewport.ensure) { - var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line - if (ensureFrom < from) { - from = ensureFrom - to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) - } else if (Math.min(ensureTo, doc.lastLine()) >= to) { - from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) - to = ensureTo - } +// Calculate a new scroll position needed to scroll the given +// rectangle into view. Returns an object with scrollTop and +// scrollLeft properties. When these are undefined, the +// vertical/horizontal position does not need to be adjusted. +function calculateScrollPos(cm, rect) { + var display = cm.display, snapMargin = textHeight(cm.display) + if (rect.top < 0) { rect.top = 0 } + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop + var screen = displayHeight(cm), result = {} + if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } + var docBottom = cm.doc.height + paddingVert(display) + var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin + if (rect.top < screentop) { + result.scrollTop = atTop ? 0 : rect.top + } else if (rect.bottom > screentop + screen) { + var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) + if (newTop != screentop) { result.scrollTop = newTop } } - return {from: from, to: Math.max(to, from + 1)} + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) + var tooWide = rect.right - rect.left > screenw + if (tooWide) { rect.right = rect.left + screenw } + if (rect.left < 10) + { result.scrollLeft = 0 } + else if (rect.left < screenleft) + { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } + else if (rect.right > screenw + screenleft - 3) + { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } + return result +} + +// Store a relative adjustment to the scroll position in the current +// operation (to be applied when the operation finishes). +function addToScrollTop(cm, top) { + if (top == null) { return } + resolveScrollToPos(cm) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top +} + +// Make sure that at the end of the operation the current cursor is +// shown. +function ensureCursorVisible(cm) { + resolveScrollToPos(cm) + var cur = cm.getCursor() + cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin} +} + +function scrollToCoords(cm, x, y) { + if (x != null || y != null) { resolveScrollToPos(cm) } + if (x != null) { cm.curOp.scrollLeft = x } + if (y != null) { cm.curOp.scrollTop = y } +} + +function scrollToRange(cm, range) { + resolveScrollToPos(cm) + cm.curOp.scrollToPos = range +} + +// When an operation has its scrollToPos property set, and another +// scroll action is applied before the end of the operation, this +// 'simulates' scrolling that position into view in a cheap way, so +// that the effect of intermediate scroll commands is not ignored. +function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos + if (range) { + cm.curOp.scrollToPos = null + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) + scrollToCoordsRange(cm, from, to, range.margin) + } +} + +function scrollToCoordsRange(cm, from, to, margin) { + var sPos = calculateScrollPos(cm, { + left: Math.min(from.left, to.left), + top: Math.min(from.top, to.top) - margin, + right: Math.max(from.right, to.right), + bottom: Math.max(from.bottom, to.bottom) + margin + }) + scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop) } // Sync the scrollable area and scrollbars, ensure the viewport // covers the visible area. -function setScrollTop(cm, val) { +function updateScrollTop(cm, val) { if (Math.abs(cm.doc.scrollTop - val) < 2) { return } - cm.doc.scrollTop = val if (!gecko) { updateDisplaySimple(cm, {top: val}) } - if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } - cm.display.scrollbars.setScrollTop(val) + setScrollTop(cm, val, true) if (gecko) { updateDisplaySimple(cm) } startWorker(cm, 100) } + +function setScrollTop(cm, val, forceScroll) { + val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val) + if (cm.display.scroller.scrollTop == val && !forceScroll) { return } + cm.doc.scrollTop = val + cm.display.scrollbars.setScrollTop(val) + if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val } +} + // Sync scroller and scrollbar, ensure the gutter elements are // aligned. -function setScrollLeft(cm, val, isScroller) { - if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) { return } +function setScrollLeft(cm, val, isScroller, forceScroll) { val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth) + if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } cm.doc.scrollLeft = val alignHorizontally(cm) if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val } cm.display.scrollbars.setScrollLeft(val) } -// Since the delta values reported on mouse wheel events are -// unstandardized between browsers and even browser versions, and -// generally horribly unpredictable, this code starts by measuring -// the scroll effect that the first few mouse wheel events have, -// and, from that, detects the way it can convert deltas to pixel -// offsets afterwards. -// -// The reason we want to know the amount a wheel event will scroll -// is that it gives us a chance to update the display before the -// actual scrolling happens, reducing flickering. - -var wheelSamples = 0; -var wheelPixelsPerUnit = null; -// Fill in a browser-detected starting value on browsers where we -// know one. These don't have to be accurate -- the result of them -// being wrong would just be a slight flicker on the first wheel -// scroll (if it is large enough). -if (ie) { wheelPixelsPerUnit = -.53 } -else if (gecko) { wheelPixelsPerUnit = 15 } -else if (chrome) { wheelPixelsPerUnit = -.7 } -else if (safari) { wheelPixelsPerUnit = -1/3 } - -function wheelEventDelta(e) { - var dx = e.wheelDeltaX, dy = e.wheelDeltaY - if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } - if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } - else if (dy == null) { dy = e.wheelDelta } - return {x: dx, y: dy} -} -function wheelEventPixels(e) { - var delta = wheelEventDelta(e) - delta.x *= wheelPixelsPerUnit - delta.y *= wheelPixelsPerUnit - return delta -} - -function onScrollWheel(cm, e) { - var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y - - var display = cm.display, scroll = display.scroller - // Quit if there's nothing to scroll here - var canScrollX = scroll.scrollWidth > scroll.clientWidth - var canScrollY = scroll.scrollHeight > scroll.clientHeight - if (!(dx && canScrollX || dy && canScrollY)) { return } - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { - for (var i = 0; i < view.length; i++) { - if (view[i].node == cur) { - cm.display.currentWheelTarget = cur - break outer - } - } - } - } - - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { - if (dy && canScrollY) - { setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))) } - setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))) - // Only prevent default scrolling if vertical scrolling is - // actually possible. Otherwise, it causes vertical scroll - // jitter on OSX trackpads when deltaX is small and deltaY - // is large (issue #3579) - if (!dy || (dy && canScrollY)) - { e_preventDefault(e) } - display.wheelStartX = null // Abort measurement, if in progress - return - } - - // 'Project' the visible viewport to cover the area that is being - // scrolled into view (if we know enough to estimate it). - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit - var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight - if (pixels < 0) { top = Math.max(0, top + pixels - 50) } - else { bot = Math.min(cm.doc.height, bot + pixels + 50) } - updateDisplaySimple(cm, {top: top, bottom: bot}) - } - - if (wheelSamples < 20) { - if (display.wheelStartX == null) { - display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop - display.wheelDX = dx; display.wheelDY = dy - setTimeout(function () { - if (display.wheelStartX == null) { return } - var movedX = scroll.scrollLeft - display.wheelStartX - var movedY = scroll.scrollTop - display.wheelStartY - var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || - (movedX && display.wheelDX && movedX / display.wheelDX) - display.wheelStartX = display.wheelStartY = null - if (!sample) { return } - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) - ++wheelSamples - }, 200) - } else { - display.wheelDX += dx; display.wheelDY += dy - } - } -} - // SCROLLBARS // Prepare DOM reads needed to update the scrollbars. Done in one @@ -3444,12 +3596,12 @@ NativeScrollbars.prototype.update = function (measure) { NativeScrollbars.prototype.setScrollLeft = function (pos) { if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos } - if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz) } + if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") } }; NativeScrollbars.prototype.setScrollTop = function (pos) { if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos } - if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert) } + if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert") } }; NativeScrollbars.prototype.zeroWidthHack = function () { @@ -3460,17 +3612,18 @@ NativeScrollbars.prototype.zeroWidthHack = function () { this.disableVert = new Delayed }; -NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay) { +NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { bar.style.pointerEvents = "auto" function maybeDisable() { // To find out whether the scrollbar is still visible, we // check whether the element under the pixel in the bottom - // left corner of the scrollbar box is the scrollbar box + // right corner of the scrollbar box is the scrollbar box // itself (when the bar is still visible) or its filler child // (when the bar is hidden). If it is still visible, we keep // it enabled, if it's hidden, we disable pointer events. var box = bar.getBoundingClientRect() - var elt = document.elementFromPoint(box.left + 1, box.bottom - 1) + var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) + : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) if (elt != bar) { bar.style.pointerEvents = "none" } else { delay.set(1000, maybeDisable) } } @@ -3542,139 +3695,12 @@ function initScrollbars(cm) { node.setAttribute("cm-not-content", "true") }, function (pos, axis) { if (axis == "horizontal") { setScrollLeft(cm, pos) } - else { setScrollTop(cm, pos) } + else { updateScrollTop(cm, pos) } }, cm) if (cm.display.scrollbars.addClass) { addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } } -// SCROLLING THINGS INTO VIEW - -// If an editor sits on the top or bottom of the window, partially -// scrolled out of view, this ensures that the cursor is visible. -function maybeScrollWindow(cm, rect) { - if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } - - var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null - if (rect.top + box.top < 0) { doScroll = true } - else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false } - if (doScroll != null && !phantom) { - var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")) - cm.display.lineSpace.appendChild(scrollNode) - scrollNode.scrollIntoView(doScroll) - cm.display.lineSpace.removeChild(scrollNode) - } -} - -// Scroll a given position into view (immediately), verifying that -// it actually became visible (as line heights are accurately -// measured, the position of something may 'drift' during drawing). -function scrollPosIntoView(cm, pos, end, margin) { - if (margin == null) { margin = 0 } - var rect - for (var limit = 0; limit < 5; limit++) { - var changed = false - var coords = cursorCoords(cm, pos) - var endCoords = !end || end == pos ? coords : cursorCoords(cm, end) - rect = {left: Math.min(coords.left, endCoords.left), - top: Math.min(coords.top, endCoords.top) - margin, - right: Math.max(coords.left, endCoords.left), - bottom: Math.max(coords.bottom, endCoords.bottom) + margin} - var scrollPos = calculateScrollPos(cm, rect) - var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft - if (scrollPos.scrollTop != null) { - setScrollTop(cm, scrollPos.scrollTop) - if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true } - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft) - if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true } - } - if (!changed) { break } - } - return rect -} - -// Scroll a given set of coordinates into view (immediately). -function scrollIntoView(cm, rect) { - var scrollPos = calculateScrollPos(cm, rect) - if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop) } - if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) } -} - -// Calculate a new scroll position needed to scroll the given -// rectangle into view. Returns an object with scrollTop and -// scrollLeft properties. When these are undefined, the -// vertical/horizontal position does not need to be adjusted. -function calculateScrollPos(cm, rect) { - var display = cm.display, snapMargin = textHeight(cm.display) - if (rect.top < 0) { rect.top = 0 } - var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop - var screen = displayHeight(cm), result = {} - if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen } - var docBottom = cm.doc.height + paddingVert(display) - var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin - if (rect.top < screentop) { - result.scrollTop = atTop ? 0 : rect.top - } else if (rect.bottom > screentop + screen) { - var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) - if (newTop != screentop) { result.scrollTop = newTop } - } - - var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0) - var tooWide = rect.right - rect.left > screenw - if (tooWide) { rect.right = rect.left + screenw } - if (rect.left < 10) - { result.scrollLeft = 0 } - else if (rect.left < screenleft) - { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)) } - else if (rect.right > screenw + screenleft - 3) - { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw } - return result -} - -// Store a relative adjustment to the scroll position in the current -// operation (to be applied when the operation finishes). -function addToScrollPos(cm, left, top) { - if (left != null || top != null) { resolveScrollToPos(cm) } - if (left != null) - { cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left } - if (top != null) - { cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top } -} - -// Make sure that at the end of the operation the current cursor is -// shown. -function ensureCursorVisible(cm) { - resolveScrollToPos(cm) - var cur = cm.getCursor(), from = cur, to = cur - if (!cm.options.lineWrapping) { - from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur - to = Pos(cur.line, cur.ch + 1) - } - cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin} -} - -// When an operation has its scrollToPos property set, and another -// scroll action is applied before the end of the operation, this -// 'simulates' scrolling that position into view in a cheap way, so -// that the effect of intermediate scroll commands is not ignored. -function resolveScrollToPos(cm) { - var range = cm.curOp.scrollToPos - if (range) { - cm.curOp.scrollToPos = null - var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) - var sPos = calculateScrollPos(cm, { - left: Math.min(from.left, to.left), - top: Math.min(from.top, to.top) - range.margin, - right: Math.max(from.right, to.right), - bottom: Math.max(from.bottom, to.bottom) + range.margin - }) - cm.scrollTo(sPos.scrollLeft, sPos.scrollTop) - } -} - // Operations are used to wrap a series of changes to the editor // state in such a way that each change won't have to update the // cursor and display (which would be awkward, slow, and @@ -3765,7 +3791,7 @@ function endOperation_R2(op) { } if (op.updatedDisplay || op.selectionChanged) - { op.preparedSelection = display.input.prepareSelection(op.focus) } + { op.preparedSelection = display.input.prepareSelection() } } function endOperation_W2(op) { @@ -3778,7 +3804,7 @@ function endOperation_W2(op) { cm.display.maxLineChanged = false } - var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()) + var takeFocus = op.focus && op.focus == activeElt() if (op.preparedSelection) { cm.display.input.showSelection(op.preparedSelection, takeFocus) } if (op.updatedDisplay || op.startHeight != cm.doc.height) @@ -3803,17 +3829,9 @@ function endOperation_finish(op) { { display.wheelStartX = display.wheelStartY = null } // Propagate the scroll position to the actual DOM scroller - if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { - doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)) - display.scrollbars.setScrollTop(doc.scrollTop) - display.scroller.scrollTop = doc.scrollTop - } - if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { - doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)) - display.scrollbars.setScrollLeft(doc.scrollLeft) - display.scroller.scrollLeft = doc.scrollLeft - alignHorizontally(cm) - } + if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll) } + + if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true) } // If we need to scroll a specific position into view, do so. if (op.scrollToPos) { var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), @@ -4026,22 +4044,23 @@ function countDirtyView(cm) { // HIGHLIGHT WORKER function startWorker(cm, time) { - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + if (cm.doc.highlightFrontier < cm.display.viewTo) { cm.state.highlight.set(time, bind(highlightWorker, cm)) } } function highlightWorker(cm) { var doc = cm.doc - if (doc.frontier < doc.first) { doc.frontier = doc.first } - if (doc.frontier >= cm.display.viewTo) { return } + if (doc.highlightFrontier >= cm.display.viewTo) { return } var end = +new Date + cm.options.workTime - var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)) + var context = getContextBefore(cm, doc.highlightFrontier) var changedLines = [] - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { - if (doc.frontier >= cm.display.viewFrom) { // Visible - var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength - var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true) + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { + if (context.line >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles + var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null + var highlighted = highlightLine(cm, line, context, true) + if (resetState) { context.state = resetState } line.styles = highlighted.styles var oldCls = line.styleClasses, newCls = highlighted.classes if (newCls) { line.styleClasses = newCls } @@ -4049,19 +4068,22 @@ function highlightWorker(cm) { var ischange = !oldStyles || oldStyles.length != line.styles.length || oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i] } - if (ischange) { changedLines.push(doc.frontier) } - line.stateAfter = tooLong ? state : copyState(doc.mode, state) + if (ischange) { changedLines.push(context.line) } + line.stateAfter = context.save() + context.nextLine() } else { if (line.text.length <= cm.options.maxHighlightLength) - { processLine(cm, line.text, state) } - line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null + { processLine(cm, line.text, context) } + line.stateAfter = context.line % 5 == 0 ? context.save() : null + context.nextLine() } - ++doc.frontier if (+new Date > end) { startWorker(cm, cm.options.workDelay) return true } }) + doc.highlightFrontier = context.line + doc.modeFrontier = Math.max(doc.modeFrontier, context.line) if (changedLines.length) { runInOp(cm, function () { for (var i = 0; i < changedLines.length; i++) { regLineChange(cm, changedLines[i], "text") } @@ -4107,6 +4129,36 @@ function maybeClipScrollbars(cm) { } } +function selectionSnapshot(cm) { + if (cm.hasFocus()) { return null } + var active = activeElt() + if (!active || !contains(cm.display.lineDiv, active)) { return null } + var result = {activeElt: active} + if (window.getSelection) { + var sel = window.getSelection() + if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { + result.anchorNode = sel.anchorNode + result.anchorOffset = sel.anchorOffset + result.focusNode = sel.focusNode + result.focusOffset = sel.focusOffset + } + } + return result +} + +function restoreSelection(snapshot) { + if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return } + snapshot.activeElt.focus() + if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { + var sel = window.getSelection(), range = document.createRange() + range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) + range.collapse(false) + sel.removeAllRanges() + sel.addRange(range) + sel.extend(snapshot.focusNode, snapshot.focusOffset) + } +} + // Does the actual updating of the line display. Bails out // (returning false) when there is nothing to be done and forced is // false. @@ -4156,14 +4208,14 @@ function updateDisplayIfNeeded(cm, update) { // For big changes, we hide the enclosing element during the // update, since that speeds up the operations on most browsers. - var focused = activeElt() + var selSnapshot = selectionSnapshot(cm) if (toUpdate > 4) { display.lineDiv.style.display = "none" } patchDisplay(cm, display.updateLineNumbers, update.dims) if (toUpdate > 4) { display.lineDiv.style.display = "" } display.renderedView = display.view // There might have been a widget with a focused element that got // hidden or updated, if so re-focus it. - if (focused && activeElt() != focused && focused.offsetHeight) { focused.focus() } + restoreSelection(selSnapshot) // Prevent selection and cursors from interfering with the scroll // width and height. @@ -4202,6 +4254,7 @@ function postUpdateDisplay(cm, update) { updateSelection(cm) updateScrollbars(cm, barMeasure) setDocumentHeight(cm, barMeasure) + update.force = false } update.signal(cm, "update", cm) @@ -4311,6 +4364,106 @@ function setGuttersForLineNumbers(options) { } } +var wheelSamples = 0; +var wheelPixelsPerUnit = null; +// Fill in a browser-detected starting value on browsers where we +// know one. These don't have to be accurate -- the result of them +// being wrong would just be a slight flicker on the first wheel +// scroll (if it is large enough). +if (ie) { wheelPixelsPerUnit = -.53 } +else if (gecko) { wheelPixelsPerUnit = 15 } +else if (chrome) { wheelPixelsPerUnit = -.7 } +else if (safari) { wheelPixelsPerUnit = -1/3 } + +function wheelEventDelta(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail } + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail } + else if (dy == null) { dy = e.wheelDelta } + return {x: dx, y: dy} +} +function wheelEventPixels(e) { + var delta = wheelEventDelta(e) + delta.x *= wheelPixelsPerUnit + delta.y *= wheelPixelsPerUnit + return delta +} + +function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y + + var display = cm.display, scroll = display.scroller + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth + var canScrollY = scroll.scrollHeight > scroll.clientHeight + if (!(dx && canScrollX || dy && canScrollY)) { return } + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) } + setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + { e_preventDefault(e) } + display.wheelStartX = null // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight + if (pixels < 0) { top = Math.max(0, top + pixels - 50) } + else { bot = Math.min(cm.doc.height, bot + pixels + 50) } + updateDisplaySimple(cm, {top: top, bottom: bot}) + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop + display.wheelDX = dx; display.wheelDY = dy + setTimeout(function () { + if (display.wheelStartX == null) { return } + var movedX = scroll.scrollLeft - display.wheelStartX + var movedY = scroll.scrollTop - display.wheelStartY + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX) + display.wheelStartX = display.wheelStartY = null + if (!sample) { return } + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) + ++wheelSamples + }, 200) + } else { + display.wheelDX += dx; display.wheelDY += dy + } + } +} + // Selection objects are immutable. A new one is created every time // the selection changes. A selection is one or more non-overlapping // (and non-touching) ranges, sorted, and an integer that indicates @@ -4464,7 +4617,7 @@ function resetModeState(cm) { if (line.stateAfter) { line.stateAfter = null } if (line.styles) { line.styles = null } }) - cm.doc.frontier = cm.doc.first + cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first startWorker(cm, 100) cm.state.modeGen++ if (cm.curOp) { regChange(cm) } @@ -4798,8 +4951,8 @@ function copyHistoryArray(events, newGroup, instantiateSel) { // include a given position (and optionally a second position). // Otherwise, simply returns the range between the given positions. // Used for cursor motion and such. -function extendRange(doc, range, head, other) { - if (doc.cm && doc.cm.display.shift || doc.extend) { +function extendRange(range, head, other, extend) { + if (extend) { var anchor = range.anchor if (other) { var posBefore = cmp(head, anchor) < 0 @@ -4817,16 +4970,18 @@ function extendRange(doc, range, head, other) { } // Extend the primary selection range, discard the rest. -function extendSelection(doc, head, other, options) { - setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options) +function extendSelection(doc, head, other, options, extend) { + if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend) } + setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) } // Extend all selections (pos is an array of selections with length // equal the number of selections) function extendSelections(doc, heads, options) { var out = [] + var extend = doc.cm && (doc.cm.display.shift || doc.extend) for (var i = 0; i < doc.sel.ranges.length; i++) - { out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null) } + { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) } var newSel = normalizeSelection(out, doc.sel.primIndex) setSelection(doc, newSel, options) } @@ -4907,7 +5062,7 @@ function setSelectionInner(doc, sel) { // Verify that the selection does not partially select any atomic // marked ranges. function reCheckSelection(doc) { - setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll) + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) } // Return a selection that does not partially select any atomic @@ -5032,7 +5187,7 @@ function makeChange(doc, change, ignoreReadOnly) { var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) if (split) { for (var i = split.length - 1; i >= 0; --i) - { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}) } + { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}) } } else { makeChangeInner(doc, change) } @@ -5210,8 +5365,7 @@ function makeChangeSingleDocInEditor(cm, change, spans) { if (recomputeMaxLength) { cm.curOp.updateMaxLine = true } } - // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line) + retreatFrontier(doc, from.line) startWorker(cm, 400) var lendiff = change.text.length - (to.line - from.line) - 1 @@ -5239,7 +5393,8 @@ function makeChangeSingleDocInEditor(cm, change, spans) { function replaceRange(doc, code, from, to, origin) { if (!to) { to = from } - if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp } + if (cmp(to, from) < 0) { var assign; + (assign = [to, from], from = assign[0], to = assign[1], assign) } if (typeof code == "string") { code = doc.splitLines(code) } makeChange(doc, {from: from, to: to, text: code, origin: origin}) } @@ -5321,7 +5476,7 @@ function changeLine(doc, handle, changeType, op) { // // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html -var LeafChunk = function(lines) { +function LeafChunk(lines) { var this$1 = this; this.lines = lines @@ -5332,47 +5487,49 @@ var LeafChunk = function(lines) { height += lines[i].height } this.height = height -}; +} -LeafChunk.prototype.chunkSize = function () { return this.lines.length }; +LeafChunk.prototype = { + chunkSize: function chunkSize() { return this.lines.length }, -// Remove the n lines at offset 'at'. -LeafChunk.prototype.removeInner = function (at, n) { + // Remove the n lines at offset 'at'. + removeInner: function removeInner(at, n) { var this$1 = this; - for (var i = at, e = at + n; i < e; ++i) { - var line = this$1.lines[i] - this$1.height -= line.height - cleanUpLine(line) - signalLater(line, "delete") + for (var i = at, e = at + n; i < e; ++i) { + var line = this$1.lines[i] + this$1.height -= line.height + cleanUpLine(line) + signalLater(line, "delete") + } + this.lines.splice(at, n) + }, + + // Helper used to collapse a small branch into a single leaf. + collapse: function collapse(lines) { + lines.push.apply(lines, this.lines) + }, + + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function insertInner(at, lines, height) { + var this$1 = this; + + this.height += height + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) + for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } + }, + + // Used to iterate over a part of the tree. + iterN: function iterN(at, n, op) { + var this$1 = this; + + for (var e = at + n; at < e; ++at) + { if (op(this$1.lines[at])) { return true } } } - this.lines.splice(at, n) -}; +} -// Helper used to collapse a small branch into a single leaf. -LeafChunk.prototype.collapse = function (lines) { - lines.push.apply(lines, this.lines) -}; - -// Insert the given array of lines at offset 'at', count them as -// having the given height. -LeafChunk.prototype.insertInner = function (at, lines, height) { - var this$1 = this; - - this.height += height - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) - for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1 } -}; - -// Used to iterate over a part of the tree. -LeafChunk.prototype.iterN = function (at, n, op) { - var this$1 = this; - - for (var e = at + n; at < e; ++at) - { if (op(this$1.lines[at])) { return true } } -}; - -var BranchChunk = function(children) { +function BranchChunk(children) { var this$1 = this; this.children = children @@ -5385,106 +5542,108 @@ var BranchChunk = function(children) { this.size = size this.height = height this.parent = null -}; +} -BranchChunk.prototype.chunkSize = function () { return this.size }; +BranchChunk.prototype = { + chunkSize: function chunkSize() { return this.size }, -BranchChunk.prototype.removeInner = function (at, n) { + removeInner: function removeInner(at, n) { var this$1 = this; - this.size -= n - for (var i = 0; i < this.children.length; ++i) { - var child = this$1.children[i], sz = child.chunkSize() - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height - child.removeInner(at, rm) - this$1.height -= oldHeight - child.height - if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } - if ((n -= rm) == 0) { break } - at = 0 - } else { at -= sz } - } - // If the result is smaller than 25 lines, ensure that it is a - // single leaf node. - if (this.size - n < 25 && - (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { - var lines = [] - this.collapse(lines) - this.children = [new LeafChunk(lines)] - this.children[0].parent = this - } -}; + this.size -= n + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height + child.removeInner(at, rm) + this$1.height -= oldHeight - child.height + if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null } + if ((n -= rm) == 0) { break } + at = 0 + } else { at -= sz } + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = [] + this.collapse(lines) + this.children = [new LeafChunk(lines)] + this.children[0].parent = this + } + }, -BranchChunk.prototype.collapse = function (lines) { + collapse: function collapse(lines) { var this$1 = this; - for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } -}; + for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines) } + }, -BranchChunk.prototype.insertInner = function (at, lines, height) { + insertInner: function insertInner(at, lines, height) { var this$1 = this; - this.size += lines.length - this.height += height - for (var i = 0; i < this.children.length; ++i) { - var child = this$1.children[i], sz = child.chunkSize() - if (at <= sz) { - child.insertInner(at, lines, height) - if (child.lines && child.lines.length > 50) { - // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. - // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. - var remaining = child.lines.length % 25 + 25 - for (var pos = remaining; pos < child.lines.length;) { - var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) - child.height -= leaf.height - this$1.children.splice(++i, 0, leaf) - leaf.parent = this$1 + this.size += lines.length + this.height += height + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at <= sz) { + child.insertInner(at, lines, height) + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25 + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) + child.height -= leaf.height + this$1.children.splice(++i, 0, leaf) + leaf.parent = this$1 + } + child.lines = child.lines.slice(0, remaining) + this$1.maybeSpill() } - child.lines = child.lines.slice(0, remaining) - this$1.maybeSpill() + break } - break + at -= sz } - at -= sz - } -}; + }, -// When a node has grown, check whether it should be split. -BranchChunk.prototype.maybeSpill = function () { - if (this.children.length <= 10) { return } - var me = this - do { - var spilled = me.children.splice(me.children.length - 5, 5) - var sibling = new BranchChunk(spilled) - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children) - copy.parent = me - me.children = [copy, sibling] - me = copy - } else { - me.size -= sibling.size - me.height -= sibling.height - var myIndex = indexOf(me.parent.children, me) - me.parent.children.splice(myIndex + 1, 0, sibling) - } - sibling.parent = me.parent - } while (me.children.length > 10) - me.parent.maybeSpill() -}; + // When a node has grown, check whether it should be split. + maybeSpill: function maybeSpill() { + if (this.children.length <= 10) { return } + var me = this + do { + var spilled = me.children.splice(me.children.length - 5, 5) + var sibling = new BranchChunk(spilled) + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children) + copy.parent = me + me.children = [copy, sibling] + me = copy + } else { + me.size -= sibling.size + me.height -= sibling.height + var myIndex = indexOf(me.parent.children, me) + me.parent.children.splice(myIndex + 1, 0, sibling) + } + sibling.parent = me.parent + } while (me.children.length > 10) + me.parent.maybeSpill() + }, -BranchChunk.prototype.iterN = function (at, n, op) { + iterN: function iterN(at, n, op) { var this$1 = this; - for (var i = 0; i < this.children.length; ++i) { - var child = this$1.children[i], sz = child.chunkSize() - if (at < sz) { - var used = Math.min(n, sz - at) - if (child.iterN(at, used, op)) { return true } - if ((n -= used) == 0) { break } - at = 0 - } else { at -= sz } + for (var i = 0; i < this.children.length; ++i) { + var child = this$1.children[i], sz = child.chunkSize() + if (at < sz) { + var used = Math.min(n, sz - at) + if (child.iterN(at, used, op)) { return true } + if ((n -= used) == 0) { break } + at = 0 + } else { at -= sz } + } } -}; +} // Line widgets are block elements displayed above or below a line. @@ -5535,7 +5694,7 @@ eventMixin(LineWidget) function adjustScrollWhenAboveVisible(cm, line, diff) { if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) - { addToScrollPos(cm, null, diff) } + { addToScrollTop(cm, diff) } } function addLineWidget(doc, handle, node, options) { @@ -5550,7 +5709,7 @@ function addLineWidget(doc, handle, node, options) { if (cm && !lineIsHidden(doc, line)) { var aboveVisible = heightAtLine(line) < doc.scrollTop updateLineHeight(line, line.height + widgetHeight(widget)) - if (aboveVisible) { addToScrollPos(cm, null, widget.height) } + if (aboveVisible) { addToScrollTop(cm, widget.height) } cm.curOp.forceUpdate = true } return true @@ -5714,8 +5873,7 @@ function markText(doc, from, to, options, type) { if (marker.replacedWith) { // Showing up as a widget implies collapsed (widget replaces text) marker.collapsed = true - marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget") - marker.widgetNode.setAttribute("role", "presentation") // hide from accessibility tree + marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true") } if (options.insertLeft) { marker.widgetNode.insertLeft = true } } @@ -5854,7 +6012,7 @@ var Doc = function(text, mode, firstLine, lineSep, direction) { this.scrollTop = this.scrollLeft = 0 this.cantEdit = false this.cleanGeneration = 1 - this.frontier = firstLine + this.modeFrontier = this.highlightFrontier = firstLine var start = Pos(firstLine, 0) this.sel = simpleSelection(start) this.history = new History(null) @@ -5900,7 +6058,8 @@ Doc.prototype = createObj(BranchChunk.prototype, { var top = Pos(this.first, 0), last = this.first + this.size - 1 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), text: this.splitLines(code), origin: "setValue", full: true}, true) - setSelection(this, simpleSelection(top)) + if (this.cm) { scrollToCoords(this.cm, 0, 0) } + setSelection(this, simpleSelection(top), sel_dontScroll) }), replaceRange: function(code, from, to, origin) { from = clipPos(this, from) @@ -6376,8 +6535,8 @@ function clearDragCursor(cm) { // garbage collected. function forEachCodeMirror(f) { - if (!document.body.getElementsByClassName) { return } - var byClass = document.body.getElementsByClassName("CodeMirror") + if (!document.getElementsByClassName) { return } + var byClass = document.getElementsByClassName("CodeMirror") for (var i = 0; i < byClass.length; i++) { var cm = byClass[i].CodeMirror if (cm) { f(cm) } @@ -6551,11 +6710,8 @@ function isModifierKey(value) { return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" } -// Look up the name of a key as indicated by an event object. -function keyName(event, noShift) { - if (presto && event.keyCode == 34 && event["char"]) { return false } - var base = keyNames[event.keyCode], name = base - if (name == null || event.altGraphKey) { return false } +function addModifierNames(name, event, noShift) { + var base = name if (event.altKey && base != "Alt") { name = "Alt-" + name } if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name } if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") { name = "Cmd-" + name } @@ -6563,6 +6719,14 @@ function keyName(event, noShift) { return name } +// Look up the name of a key as indicated by an event object. +function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) { return false } + var name = keyNames[event.keyCode] + if (name == null || event.altGraphKey) { return false } + return addModifierNames(name, event, noShift) +} + function getKeyMap(val) { return typeof val == "string" ? keyMap[val] : val } @@ -6592,6 +6756,112 @@ function deleteNearSelection(cm, compute) { }) } +function moveCharLogically(line, ch, dir) { + var target = skipExtendingChars(line.text, ch + dir, dir) + return target < 0 || target > line.text.length ? null : target +} + +function moveLogically(line, start, dir) { + var ch = moveCharLogically(line, start.ch, dir) + return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") +} + +function endOfLine(visually, cm, lineObj, lineNo, dir) { + if (visually) { + var order = getOrder(lineObj, cm.doc.direction) + if (order) { + var part = dir < 0 ? lst(order) : order[0] + var moveInStorageOrder = (dir < 0) == (part.level == 1) + var sticky = moveInStorageOrder ? "after" : "before" + var ch + // With a wrapped rtl chunk (possibly spanning multiple bidi parts), + // it could be that the last bidi part is not on the last visual line, + // since visual lines contain content order-consecutive chunks. + // Thus, in rtl, we are looking for the first (content-order) character + // in the rtl chunk that is on the last line (that is, the same line + // as the last (content-order) character). + if (part.level > 0 || cm.doc.direction == "rtl") { + var prep = prepareMeasureForLine(cm, lineObj) + ch = dir < 0 ? lineObj.text.length - 1 : 0 + var targetTop = measureCharPrepared(cm, prep, ch).top + ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) + if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) } + } else { ch = dir < 0 ? part.to : part.from } + return new Pos(lineNo, ch, sticky) + } + } + return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") +} + +function moveVisually(cm, line, start, dir) { + var bidi = getOrder(line, cm.doc.direction) + if (!bidi) { return moveLogically(line, start, dir) } + if (start.ch >= line.text.length) { + start.ch = line.text.length + start.sticky = "before" + } else if (start.ch <= 0) { + start.ch = 0 + start.sticky = "after" + } + var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] + if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { + // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, + // nothing interesting happens. + return moveLogically(line, start, dir) + } + + var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); } + var prep + var getWrappedLineExtent = function (ch) { + if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } + prep = prep || prepareMeasureForLine(cm, line) + return wrappedLineExtentChar(cm, line, prep, ch) + } + var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) + + if (cm.doc.direction == "rtl" || part.level == 1) { + var moveInStorageOrder = (part.level == 1) == (dir < 0) + var ch = mv(start, moveInStorageOrder ? 1 : -1) + if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { + // Case 2: We move within an rtl part or in an rtl editor on the same visual line + var sticky = moveInStorageOrder ? "before" : "after" + return new Pos(start.line, ch, sticky) + } + } + + // Case 3: Could not move within this bidi part in this visual line, so leave + // the current bidi part + + var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { + var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder + ? new Pos(start.line, mv(ch, 1), "before") + : new Pos(start.line, ch, "after"); } + + for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { + var part = bidi[partPos] + var moveInStorageOrder = (dir > 0) == (part.level != 1) + var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) + if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } + ch = moveInStorageOrder ? part.from : mv(part.to, -1) + if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } + } + } + + // Case 3a: Look for other bidi parts on the same visual line + var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) + if (res) { return res } + + // Case 3b: Look for other bidi parts on the next visual line + var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) + if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { + res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) + if (res) { return res } + } + + // Case 4: Nowhere to move + return null +} + // Commands are parameter-less actions that can be performed on an // editor, mostly used for keybindings. var commands = { @@ -6641,15 +6911,15 @@ var commands = { {origin: "+move", bias: -1} ); }, goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { - var top = cm.charCoords(range.head, "div").top + 5 + var top = cm.cursorCoords(range.head, "div").top + 5 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") }, sel_move); }, goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { - var top = cm.charCoords(range.head, "div").top + 5 + var top = cm.cursorCoords(range.head, "div").top + 5 return cm.coordsChar({left: 0, top: top}, "div") }, sel_move); }, goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { - var top = cm.charCoords(range.head, "div").top + 5 + var top = cm.cursorCoords(range.head, "div").top + 5 var pos = cm.coordsChar({left: 0, top: top}, "div") if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } return pos @@ -6789,19 +7059,30 @@ function lookupKeyForEditor(cm, name, handle) { || lookupKey(name, cm.options.keyMap, handle, cm) } +// Note that, despite the name, this function is also used to check +// for bound mouse clicks. + var stopSeq = new Delayed + function dispatchKey(cm, name, e, handle) { var seq = cm.state.keySeq if (seq) { if (isModifierKey(name)) { return "handled" } - stopSeq.set(50, function () { - if (cm.state.keySeq == seq) { - cm.state.keySeq = null - cm.display.input.reset() - } - }) - name = seq + " " + name + if (/\'$/.test(name)) + { cm.state.keySeq = null } + else + { stopSeq.set(50, function () { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null + cm.display.input.reset() + } + }) } + if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } } + return dispatchKeyInner(cm, name, e, handle) +} + +function dispatchKeyInner(cm, name, e, handle) { var result = lookupKeyForEditor(cm, name, handle) if (result == "multi") @@ -6814,10 +7095,6 @@ function dispatchKey(cm, name, e, handle) { restartBlink(cm) } - if (seq && !result && /\'$/.test(name)) { - e_preventDefault(e) - return true - } return !!result } @@ -6900,6 +7177,37 @@ function onKeyPress(e) { cm.display.input.onKeyPress(e) } +var DOUBLECLICK_DELAY = 400 + +var PastClick = function(time, pos, button) { + this.time = time + this.pos = pos + this.button = button +}; + +PastClick.prototype.compare = function (time, pos, button) { + return this.time + DOUBLECLICK_DELAY > time && + cmp(pos, this.pos) == 0 && button == this.button +}; + +var lastClick; +var lastDoubleClick; +function clickRepeat(pos, button) { + var now = +new Date + if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { + lastClick = lastDoubleClick = null + return "triple" + } else if (lastClick && lastClick.compare(now, pos, button)) { + lastDoubleClick = new PastClick(now, pos, button) + lastClick = null + return "double" + } else { + lastClick = new PastClick(now, pos, button) + lastDoubleClick = null + return "single" + } +} + // A mouse down can be a single click, double click, triple click, // start of selection drag, start of text drag, new cursor // (ctrl-click), rectangle drag (alt-drag), or xwin @@ -6921,72 +7229,91 @@ function onMouseDown(e) { return } if (clickInGutter(cm, e)) { return } - var start = posFromMouse(cm, e) + var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single" window.focus() - switch (e_button(e)) { - case 1: - // #3261: make sure, that we're not starting a second selection - if (cm.state.selectingText) - { cm.state.selectingText(e) } - else if (start) - { leftButtonDown(cm, e, start) } - else if (e_target(e) == display.scroller) - { e_preventDefault(e) } - break - case 2: - if (webkit) { cm.state.lastMiddleDown = +new Date } - if (start) { extendSelection(cm.doc, start) } + // #3261: make sure, that we're not starting a second selection + if (button == 1 && cm.state.selectingText) + { cm.state.selectingText(e) } + + if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } + + if (button == 1) { + if (pos) { leftButtonDown(cm, pos, repeat, e) } + else if (e_target(e) == display.scroller) { e_preventDefault(e) } + } else if (button == 2) { + if (pos) { extendSelection(cm.doc, pos) } setTimeout(function () { return display.input.focus(); }, 20) - e_preventDefault(e) - break - case 3: + } else if (button == 3) { if (captureRightClick) { onContextMenu(cm, e) } else { delayBlurEvent(cm) } - break } } -var lastClick; -var lastDoubleClick; -function leftButtonDown(cm, e, start) { +function handleMappedButton(cm, button, pos, repeat, event) { + var name = "Click" + if (repeat == "double") { name = "Double" + name } + else if (repeat == "triple") { name = "Triple" + name } + name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name + + return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { + if (typeof bound == "string") { bound = commands[bound] } + if (!bound) { return false } + var done = false + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true } + done = bound(cm, pos) != Pass + } finally { + cm.state.suppressEdits = false + } + return done + }) +} + +function configureMouse(cm, repeat, event) { + var option = cm.getOption("configureMouse") + var value = option ? option(cm, repeat, event) : {} + if (value.unit == null) { + var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey + value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line" + } + if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey } + if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey } + if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) } + return value +} + +function leftButtonDown(cm, pos, repeat, event) { if (ie) { setTimeout(bind(ensureFocus, cm), 0) } else { cm.curOp.focus = activeElt() } - var now = +new Date, type - if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { - type = "triple" - } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { - type = "double" - lastDoubleClick = {time: now, pos: start} - } else { - type = "single" - lastClick = {time: now, pos: start} - } + var behavior = configureMouse(cm, repeat, event) - var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained + var sel = cm.doc.sel, contained if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && - type == "single" && (contained = sel.contains(start)) > -1 && - (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && - (cmp(contained.to(), start) > 0 || start.xRel < 0)) - { leftButtonStartDrag(cm, e, start, modifier) } + repeat == "single" && (contained = sel.contains(pos)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && + (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) + { leftButtonStartDrag(cm, event, pos, behavior) } else - { leftButtonSelect(cm, e, start, type, modifier) } + { leftButtonSelect(cm, event, pos, behavior) } } // Start a text drag. When it ends, see if any dragging actually // happen, and treat as a click if it didn't. -function leftButtonStartDrag(cm, e, start, modifier) { - var display = cm.display, startTime = +new Date - var dragEnd = operation(cm, function (e2) { +function leftButtonStartDrag(cm, event, pos, behavior) { + var display = cm.display, moved = false + var dragEnd = operation(cm, function (e) { if (webkit) { display.scroller.draggable = false } cm.state.draggingText = false off(document, "mouseup", dragEnd) + off(document, "mousemove", mouseMove) + off(display.scroller, "dragstart", dragStart) off(display.scroller, "drop", dragEnd) - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2) - if (!modifier && +new Date - 200 < startTime) - { extendSelection(cm.doc, start) } + if (!moved) { + e_preventDefault(e) + if (!behavior.addNew) + { extendSelection(cm.doc, pos, null, null, behavior.extend) } // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) if (webkit || ie && ie_version == 9) { setTimeout(function () {document.body.focus(); display.input.focus()}, 20) } @@ -6994,23 +7321,40 @@ function leftButtonStartDrag(cm, e, start, modifier) { { display.input.focus() } } }) + var mouseMove = function(e2) { + moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10 + } + var dragStart = function () { return moved = true; } // Let the drag handler handle this. if (webkit) { display.scroller.draggable = true } cm.state.draggingText = dragEnd - dragEnd.copy = mac ? e.altKey : e.ctrlKey + dragEnd.copy = !behavior.moveOnDrag // IE's approach to draggable if (display.scroller.dragDrop) { display.scroller.dragDrop() } on(document, "mouseup", dragEnd) + on(document, "mousemove", mouseMove) + on(display.scroller, "dragstart", dragStart) on(display.scroller, "drop", dragEnd) + + delayBlurEvent(cm) + setTimeout(function () { return display.input.focus(); }, 20) +} + +function rangeForUnit(cm, pos, unit) { + if (unit == "char") { return new Range(pos, pos) } + if (unit == "word") { return cm.findWordAt(pos) } + if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } + var result = unit(cm, pos) + return new Range(result.from, result.to) } // Normal selection, as opposed to text dragging. -function leftButtonSelect(cm, e, start, type, addNew) { +function leftButtonSelect(cm, event, start, behavior) { var display = cm.display, doc = cm.doc - e_preventDefault(e) + e_preventDefault(event) var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges - if (addNew && !e.shiftKey) { + if (behavior.addNew && !behavior.extend) { ourIndex = doc.sel.contains(start) if (ourIndex > -1) { ourRange = ranges[ourIndex] } @@ -7021,28 +7365,19 @@ function leftButtonSelect(cm, e, start, type, addNew) { ourIndex = doc.sel.primIndex } - if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) { - type = "rect" - if (!addNew) { ourRange = new Range(start, start) } - start = posFromMouse(cm, e, true, true) + if (behavior.unit == "rectangle") { + if (!behavior.addNew) { ourRange = new Range(start, start) } + start = posFromMouse(cm, event, true, true) ourIndex = -1 - } else if (type == "double") { - var word = cm.findWordAt(start) - if (cm.display.shift || doc.extend) - { ourRange = extendRange(doc, ourRange, word.anchor, word.head) } - else - { ourRange = word } - } else if (type == "triple") { - var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))) - if (cm.display.shift || doc.extend) - { ourRange = extendRange(doc, ourRange, line.anchor, line.head) } - else - { ourRange = line } } else { - ourRange = extendRange(doc, ourRange, start) + var range = rangeForUnit(cm, start, behavior.unit) + if (behavior.extend) + { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) } + else + { ourRange = range } } - if (!addNew) { + if (!behavior.addNew) { ourIndex = 0 setSelection(doc, new Selection([ourRange], 0), sel_mouse) startSel = doc.sel @@ -7050,7 +7385,7 @@ function leftButtonSelect(cm, e, start, type, addNew) { ourIndex = ranges.length setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), {scroll: false, origin: "*mouse"}) - } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), {scroll: false, origin: "*mouse"}) startSel = doc.sel @@ -7063,7 +7398,7 @@ function leftButtonSelect(cm, e, start, type, addNew) { if (cmp(lastPos, pos) == 0) { return } lastPos = pos - if (type == "rect") { + if (behavior.unit == "rectangle") { var ranges = [], tabSize = cm.options.tabSize var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) @@ -7082,23 +7417,17 @@ function leftButtonSelect(cm, e, start, type, addNew) { cm.scrollIntoView(pos) } else { var oldRange = ourRange - var anchor = oldRange.anchor, head = pos - if (type != "single") { - var range - if (type == "double") - { range = cm.findWordAt(pos) } - else - { range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))) } - if (cmp(range.anchor, anchor) > 0) { - head = range.head - anchor = minPos(oldRange.from(), range.anchor) - } else { - head = range.anchor - anchor = maxPos(oldRange.to(), range.head) - } + var range = rangeForUnit(cm, pos, behavior.unit) + var anchor = oldRange.anchor, head + if (cmp(range.anchor, anchor) > 0) { + head = range.head + anchor = minPos(oldRange.from(), range.anchor) + } else { + head = range.anchor + anchor = maxPos(oldRange.to(), range.head) } var ranges$1 = startSel.ranges.slice(0) - ranges$1[ourIndex] = new Range(clipPos(doc, anchor), head) + ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) setSelection(doc, normalizeSelection(ranges$1, ourIndex), sel_mouse) } } @@ -7112,7 +7441,7 @@ function leftButtonSelect(cm, e, start, type, addNew) { function extend(e) { var curCount = ++counter - var cur = posFromMouse(cm, e, true, type == "rect") + var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle") if (!cur) { return } if (cmp(cur, lastPos) != 0) { cm.curOp.focus = activeElt() @@ -7150,13 +7479,52 @@ function leftButtonSelect(cm, e, start, type, addNew) { on(document, "mouseup", up) } +// 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) { + var anchor = range.anchor; + var head = range.head; + var anchorLine = getLine(cm.doc, anchor.line) + if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } + var order = getOrder(anchorLine) + if (!order) { return range } + var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] + if (part.from != anchor.ch && part.to != anchor.ch) { return range } + var 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) + var leftSide + if (head.line != anchor.line) { + leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 + } else { + var headIndex = getBidiPartAt(order, head.ch, head.sticky) + var 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 } + } + + var usePart = order[boundary + (leftSide ? -1 : 0)] + var from = leftSide == (usePart.level == 1) + var 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 // handlers for the corresponding event. function gutterEvent(cm, e, type, prevent) { var 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 } if (prevent) { e_preventDefault(e) } @@ -7278,6 +7646,7 @@ function defineOptions(CodeMirror) { if (next.attach) { next.attach(cm, prev || null) } }) option("extraKeys", null) + option("configureMouse", null) option("lineWrapping", false, wrappingChanged, true) option("gutters", [], function (cm) { @@ -7305,14 +7674,12 @@ function defineOptions(CodeMirror) { option("resetSelectionOnContextMenu", true) option("lineWiseCopyCut", true) + option("pasteLinesPerSelection", true) option("readOnly", false, function (cm, val) { if (val == "nocursor") { onBlur(cm) cm.display.input.blur() - cm.display.disabled = true - } else { - cm.display.disabled = false } cm.display.input.readOnlyChanged(val) }) @@ -7495,7 +7862,7 @@ function registerEventHandlers(cm) { return dx * dx + dy * dy > 20 * 20 } on(d.scroller, "touchstart", function (e) { - if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { d.input.ensurePolled() clearTimeout(touchFinished) var now = +new Date @@ -7533,7 +7900,7 @@ function registerEventHandlers(cm) { // area, ensure viewport is updated when scrolling. on(d.scroller, "scroll", function () { if (d.scroller.clientHeight) { - setScrollTop(cm, d.scroller.scrollTop) + updateScrollTop(cm, d.scroller.scrollTop) setScrollLeft(cm, d.scroller.scrollLeft, true) signal(cm, "scroll", cm) } @@ -7577,7 +7944,7 @@ function indentLine(cm, n, how, aggressive) { // Fall back to "prev" when the mode doesn't have an indentation // method. if (!doc.mode.indent) { how = "prev" } - else { state = getStateBefore(cm, n) } + else { state = getContextBefore(cm, n).state } } var tabSize = cm.options.tabSize @@ -7653,7 +8020,7 @@ function applyTextInput(cm, inserted, deleted, sel, origin) { for (var i = 0; i < lastCopied.text.length; i++) { multiPaste.push(doc.splitLines(lastCopied.text[i])) } } - } else if (textLines.length == sel.ranges.length) { + } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { multiPaste = map(textLines, function (l) { return [l]; }) } } @@ -7913,7 +8280,7 @@ function addEditorMethods(CodeMirror) { getStateAfter: function(line, precise) { var doc = this.doc line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) - return getStateBefore(this, line + 1, precise) + return getContextBefore(this, line + 1, precise).state }, cursorCoords: function(start, mode) { @@ -7994,6 +8361,7 @@ function addEditorMethods(CodeMirror) { triggerOnKeyDown: methodOp(onKeyDown), triggerOnKeyPress: methodOp(onKeyPress), triggerOnKeyUp: onKeyUp, + triggerOnMouseDown: methodOp(onMouseDown), execCommand: function(cmd) { if (commands.hasOwnProperty(cmd)) @@ -8066,7 +8434,7 @@ function addEditorMethods(CodeMirror) { goals.push(headPos.left) var pos = findPosV(this$1, headPos, dir, unit) if (unit == "page" && range == doc.sel.primary()) - { addToScrollPos(this$1, null, charCoords(this$1, pos, "div").top - headPos.top) } + { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top) } return pos }, sel_move) if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) @@ -8103,11 +8471,7 @@ function addEditorMethods(CodeMirror) { hasFocus: function() { return this.display.input.getField() == activeElt() }, isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, - scrollTo: methodOp(function(x, y) { - if (x != null || y != null) { resolveScrollToPos(this) } - if (x != null) { this.curOp.scrollLeft = x } - if (y != null) { this.curOp.scrollTop = y } - }), + scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), getScrollInfo: function() { var scroller = this.display.scroller return {left: scroller.scrollLeft, top: scroller.scrollTop, @@ -8129,16 +8493,9 @@ function addEditorMethods(CodeMirror) { range.margin = margin || 0 if (range.from.line != null) { - resolveScrollToPos(this) - this.curOp.scrollToPos = range + scrollToRange(this, range) } else { - var sPos = calculateScrollPos(this, { - left: Math.min(range.from.left, range.to.left), - top: Math.min(range.from.top, range.to.top) - range.margin, - right: Math.max(range.from.right, range.to.right), - bottom: Math.max(range.from.bottom, range.to.bottom) + range.margin - }) - this.scrollTo(sPos.scrollLeft, sPos.scrollTop) + scrollToCoordsRange(this, range.from, range.to, range.margin) } }), @@ -8160,13 +8517,15 @@ function addEditorMethods(CodeMirror) { }), operation: function(f){return runInOp(this, f)}, + startOperation: function(){return startOperation(this)}, + endOperation: function(){return endOperation(this)}, refresh: methodOp(function() { var oldHeight = this.display.cachedTextHeight regChange(this) this.curOp.forceUpdate = true clearCaches(this) - this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop) + scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) updateGutterSpace(this) if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) { estimateLineHeights(this) } @@ -8179,7 +8538,7 @@ function addEditorMethods(CodeMirror) { attachDoc(this, doc) clearCaches(this) this.display.input.reset() - this.scrollTo(doc.scrollLeft, doc.scrollTop) + scrollToCoords(this, doc.scrollLeft, doc.scrollTop) this.curOp.forceScroll = true signalLater(this, "swapDoc", this, old) return old @@ -8390,36 +8749,41 @@ ContentEditableInput.prototype.showSelection = function (info, takeFocus) { }; ContentEditableInput.prototype.showPrimarySelection = function () { - var sel = window.getSelection(), prim = this.cm.doc.sel.primary() - var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset) - var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset) - if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && - cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && - cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) - { return } + var sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() + var from = prim.from(), to = prim.to() - var start = posToDOM(this.cm, prim.from()) - var end = posToDOM(this.cm, prim.to()) - if (!start && !end) { + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { sel.removeAllRanges() return } - var view = this.cm.display.view - var old = sel.rangeCount && sel.getRangeAt(0) - if (!start) { - start = {node: view[0].measure.map[2], offset: 0} - } else if (!end) { // FIXME dangerously hacky + var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + { return } + + var view = cm.display.view + var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0} + var end = to.line < cm.display.viewTo && posToDOM(cm, to) + if (!end) { var measure = view[view.length - 1].measure var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} } - var rng + if (!start || !end) { + sel.removeAllRanges() + return + } + + var old = sel.rangeCount && sel.getRangeAt(0), rng try { rng = range(start.node, start.offset, end.offset, end.node) } catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible if (rng) { - if (!gecko && this.cm.state.focused) { + if (!gecko && cm.state.focused) { sel.collapse(start.node, start.offset) if (!rng.collapsed) { sel.removeAllRanges() @@ -8698,7 +9062,7 @@ function domTextBetween(cm, from, to, fromLine, toLine) { var markerID = node.getAttribute("cm-marker"), range if (markerID) { var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) - if (found.length && (range = found[0].find())) + if (found.length && (range = found[0].find(0))) { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) } return } @@ -8807,9 +9171,6 @@ var TextareaInput = function(cm) { this.pollingFast = false // Self-resetting timeout for the poller this.polling = new Delayed() - // Tracks when input.reset has punted to just putting a short - // string into the textarea instead of the full selection. - this.inaccurateSelection = false // Used to work around IE issue with selection being forgotten when focus moves away from textarea this.hasSelection = false this.composing = null @@ -8846,12 +9207,6 @@ TextareaInput.prototype.init = function (display) { if (signalDOMEvent(cm, e)) { return } if (cm.somethingSelected()) { setLastCopied({lineWise: false, text: cm.getSelections()}) - if (input.inaccurateSelection) { - input.prevInput = "" - input.inaccurateSelection = false - te.value = lastCopied.text.join("\n") - selectInput(te) - } } else if (!cm.options.lineWiseCopyCut) { return } else { @@ -8929,14 +9284,11 @@ TextareaInput.prototype.showSelection = function (drawn) { // Reset the input to correspond to the selection (or to be empty, // when not typing and nothing is selected) TextareaInput.prototype.reset = function (typing) { - if (this.contextMenuPending) { return } - var minimal, selected, cm = this.cm, doc = cm.doc + if (this.contextMenuPending || this.composing) { return } + var cm = this.cm if (cm.somethingSelected()) { this.prevInput = "" - var range = doc.sel.primary() - minimal = hasCopyEvent && - (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000) - var content = minimal ? "-" : selected || cm.getSelection() + var content = cm.getSelection() this.textarea.value = content if (cm.state.focused) { selectInput(this.textarea) } if (ie && ie_version >= 9) { this.hasSelection = content } @@ -8944,7 +9296,6 @@ TextareaInput.prototype.reset = function (typing) { this.prevInput = this.textarea.value = "" if (ie && ie_version >= 9) { this.hasSelection = null } } - this.inaccurateSelection = minimal }; TextareaInput.prototype.getField = function () { return this.textarea }; @@ -9139,6 +9490,7 @@ TextareaInput.prototype.onContextMenu = function (e) { TextareaInput.prototype.readOnlyChanged = function (val) { if (!val) { this.reset() } + this.textarea.disabled = val == "nocursor" }; TextareaInput.prototype.setUneditable = function () {}; @@ -9294,7 +9646,7 @@ CodeMirror.fromTextArea = fromTextArea addLegacyProps(CodeMirror) -CodeMirror.version = "5.24.3" +CodeMirror.version = "5.32.0" return CodeMirror; diff --git a/gui/public/codemirror/mode/apl/apl.js b/gui/public/codemirror/mode/apl/apl.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/apl/index.html b/gui/public/codemirror/mode/apl/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/asciiarmor/asciiarmor.js b/gui/public/codemirror/mode/asciiarmor/asciiarmor.js old mode 100755 new mode 100644 index d8309037..fa1b0f8c --- a/gui/public/codemirror/mode/asciiarmor/asciiarmor.js +++ b/gui/public/codemirror/mode/asciiarmor/asciiarmor.js @@ -68,6 +68,7 @@ }); CodeMirror.defineMIME("application/pgp", "asciiarmor"); + CodeMirror.defineMIME("application/pgp-encrypted", "asciiarmor"); CodeMirror.defineMIME("application/pgp-keys", "asciiarmor"); CodeMirror.defineMIME("application/pgp-signature", "asciiarmor"); }); diff --git a/gui/public/codemirror/mode/asciiarmor/index.html b/gui/public/codemirror/mode/asciiarmor/index.html old mode 100755 new mode 100644 index 8ba1b5c7..4d584efb --- a/gui/public/codemirror/mode/asciiarmor/index.html +++ b/gui/public/codemirror/mode/asciiarmor/index.html @@ -41,6 +41,6 @@ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
MIME types
-defined: application/pgp
, application/pgp-keys
, application/pgp-signature
application/pgp
, application/pgp-encrypted
, application/pgp-keys
, application/pgp-signature
diff --git a/gui/public/codemirror/mode/asn.1/asn.1.js b/gui/public/codemirror/mode/asn.1/asn.1.js
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/asn.1/index.html b/gui/public/codemirror/mode/asn.1/index.html
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/asterisk/asterisk.js b/gui/public/codemirror/mode/asterisk/asterisk.js
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/asterisk/index.html b/gui/public/codemirror/mode/asterisk/index.html
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/brainfuck/brainfuck.js b/gui/public/codemirror/mode/brainfuck/brainfuck.js
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/brainfuck/index.html b/gui/public/codemirror/mode/brainfuck/index.html
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/clike/clike.js b/gui/public/codemirror/mode/clike/clike.js
old mode 100755
new mode 100644
index 7424c9a0..02a85319
--- a/gui/public/codemirror/mode/clike/clike.js
+++ b/gui/public/codemirror/mode/clike/clike.js
@@ -33,7 +33,7 @@ function popContext(state) {
}
function typeBefore(stream, state, pos) {
- if (state.prevToken == "variable" || state.prevToken == "variable-3") return true;
+ if (state.prevToken == "variable" || state.prevToken == "type") return true;
if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true;
if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true;
}
@@ -64,7 +64,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/,
numberStart = parserConfig.numberStart || /[\d\.]/,
number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,
- isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/;
+ isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/,
+ isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/;
var curPunc, isDefKeyword;
@@ -101,9 +102,9 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {}
return "operator";
}
- stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ stream.eatWhile(isIdentifierChar);
if (namespaceSeparator) while (stream.match(namespaceSeparator))
- stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ stream.eatWhile(isIdentifierChar);
var cur = stream.current();
if (contains(keywords, cur)) {
@@ -111,7 +112,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
if (contains(defKeywords, cur)) isDefKeyword = true;
return "keyword";
}
- if (contains(types, cur)) return "variable-3";
+ if (contains(types, cur)) return "type";
if (contains(builtin, cur)) {
if (contains(blockKeywords, cur)) curPunc = "newstatement";
return "builtin";
@@ -243,6 +244,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/,
blockCommentStart: "/*",
blockCommentEnd: "*/",
+ blockCommentContinue: " * ",
lineComment: "//",
fold: "brace"
};
@@ -280,7 +282,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
function pointerHook(_stream, state) {
- if (state.prevToken == "variable-3") return "variable-3";
+ if (state.prevToken == "type") return "type";
return false;
}
@@ -314,7 +316,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
function cppLooksLikeConstructor(word) {
- var lastTwo = /(\w+)::(\w+)$/.exec(word);
+ var lastTwo = /(\w+)::~?(\w+)$/.exec(word);
return lastTwo && lastTwo[1] == lastTwo[2];
}
@@ -390,6 +392,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
typeFirstDefinitions: true,
atoms: words("true false null"),
dontIndentStatements: /^template$/,
+ isIdentifierChar: /[\w\$_~\xa1-\uffff]/,
hooks: {
"#": cppHook,
"*": pointerHook,
@@ -429,7 +432,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
types: words("byte short int long float double boolean char void Boolean Byte Character Double Float " +
"Integer Long Number Object Short String StringBuffer StringBuilder Void"),
blockKeywords: words("catch class do else finally for if switch try while"),
- defKeywords: words("class interface package enum @interface"),
+ defKeywords: words("class interface enum @interface"),
typeFirstDefinitions: true,
atoms: words("true false null"),
number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
@@ -513,8 +516,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
),
multiLineStrings: true,
- blockKeywords: words("catch class do else finally for forSome if match switch try while"),
- defKeywords: words("class def object package trait type val var"),
+ blockKeywords: words("catch class enum do else finally for forSome if match switch try while"),
+ defKeywords: words("class enum def object package trait type val var"),
atoms: words("true false null"),
indentStatements: false,
indentSwitch: false,
@@ -575,7 +578,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"file import where by get set abstract enum open inner override private public internal " +
"protected catch finally out final vararg reified dynamic companion constructor init " +
"sealed field property receiver param sparam lateinit data inline noinline tailrec " +
- "external annotation crossinline const operator infix"
+ "external annotation crossinline const operator infix suspend"
),
types: words(
/* package java.lang */
@@ -587,8 +590,9 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
intendSwitch: false,
indentStatements: false,
multiLineStrings: true,
+ number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
blockKeywords: words("catch class do else finally for if where try while enum"),
- defKeywords: words("class val var object package interface fun"),
+ defKeywords: words("class val var object interface fun"),
atoms: words("true false null this"),
hooks: {
'"': function(stream, state) {
@@ -771,7 +775,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "atom";
},
token: function(_stream, state, style) {
- if ((style == "variable" || style == "variable-3") &&
+ if ((style == "variable" || style == "type") &&
state.prevToken == ".") {
return "variable-2";
}
diff --git a/gui/public/codemirror/mode/clike/index.html b/gui/public/codemirror/mode/clike/index.html
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/clike/scala.html b/gui/public/codemirror/mode/clike/scala.html
old mode 100755
new mode 100644
diff --git a/gui/public/codemirror/mode/clike/test.js b/gui/public/codemirror/mode/clike/test.js
old mode 100755
new mode 100644
index bea85b86..dad2e246
--- a/gui/public/codemirror/mode/clike/test.js
+++ b/gui/public/codemirror/mode/clike/test.js
@@ -6,8 +6,8 @@
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
MT("indent",
- "[variable-3 void] [def foo]([variable-3 void*] [variable a], [variable-3 int] [variable b]) {",
- " [variable-3 int] [variable c] [operator =] [variable b] [operator +]",
+ "[type void] [def foo]([type void*] [variable a], [type int] [variable b]) {",
+ " [type int] [variable c] [operator =] [variable b] [operator +]",
" [number 1];",
" [keyword return] [operator *][variable a];",
"}");
@@ -21,9 +21,9 @@
"}");
MT("def",
- "[variable-3 void] [def foo]() {}",
+ "[type void] [def foo]() {}",
"[keyword struct] [def bar]{}",
- "[variable-3 int] [variable-3 *][def baz]() {}");
+ "[type int] [type *][def baz]() {}");
MT("def_new_line",
"::[variable std]::[variable SomeTerribleType][operator <][variable T][operator >]",
@@ -37,10 +37,10 @@
MT("preprocessor",
"[meta #define FOO 3]",
- "[variable-3 int] [variable foo];",
+ "[type int] [variable foo];",
"[meta #define BAR\\]",
"[meta 4]",
- "[variable-3 unsigned] [variable-3 int] [variable bar] [operator =] [number 8];",
+ "[type unsigned] [type int] [variable bar] [operator =] [number 8];",
"[meta #include MIME types defined: text/x-coffeescript
.
MIME types defined: application/vnd.coffeescript
, text/coffeescript
, text/x-coffeescript
.
The CoffeeScript mode was written by Jeff Pickhardt.
diff --git a/gui/public/codemirror/mode/commonlisp/commonlisp.js b/gui/public/codemirror/mode/commonlisp/commonlisp.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/commonlisp/index.html b/gui/public/codemirror/mode/commonlisp/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/crystal/crystal.js b/gui/public/codemirror/mode/crystal/crystal.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/crystal/index.html b/gui/public/codemirror/mode/crystal/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/css.js b/gui/public/codemirror/mode/css/css.js old mode 100755 new mode 100644 index 23d0b4db..00e9b3df --- a/gui/public/codemirror/mode/css/css.js +++ b/gui/public/codemirror/mode/css/css.js @@ -383,7 +383,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { style = style[0]; } override = style; - state.state = states[state.state](type, stream, state); + if (type != "comment") + state.state = states[state.state](type, stream, state); return override; }, @@ -401,7 +402,6 @@ CodeMirror.defineMode("css", function(config, parserConfig) { ch == "{" && (cx.type == "at" || cx.type == "atBlock")) { // Dedent relative to current context. indent = Math.max(0, cx.indent - indentUnit); - cx = cx.prev; } } return indent; @@ -410,6 +410,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { electricChars: "}", blockCommentStart: "/*", blockCommentEnd: "*/", + blockCommentContinue: " * ", lineComment: lineComment, fold: "brace" }; @@ -472,7 +473,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", "border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", - "caption-side", "clear", "clip", "color", "color-profile", "column-count", + "caption-side", "caret-color", "clear", "clip", "color", "color-profile", "column-count", "column-fill", "column-gap", "column-rule", "column-rule-color", "column-rule-style", "column-rule-width", "column-span", "column-width", "columns", "content", "counter-increment", "counter-reset", "crop", "cue", @@ -493,7 +494,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns", "grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon", "image-orientation", "image-rendering", "image-resolution", - "inline-box-align", "justify-content", "left", "letter-spacing", + "inline-box-align", "justify-content", "justify-items", "justify-self", "left", "letter-spacing", "line-break", "line-height", "line-stacking", "line-stacking-ruby", "line-stacking-shift", "line-stacking-strategy", "list-style", "list-style-image", "list-style-position", "list-style-type", "margin", @@ -508,7 +509,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "page", "page-break-after", "page-break-before", "page-break-inside", "page-policy", "pause", "pause-after", "pause-before", "perspective", - "perspective-origin", "pitch", "pitch-range", "play-during", "position", + "perspective-origin", "pitch", "pitch-range", "place-content", "place-items", "place-self", "play-during", "position", "presentation-level", "punctuation-trim", "quotes", "region-break-after", "region-break-before", "region-break-inside", "region-fragment", "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness", @@ -659,15 +660,15 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen", "scroll", "scrollbar", "scroll-position", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", - "searchfield-results-button", "searchfield-results-decoration", + "searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "simp-chinese-formal", "simp-chinese-informal", "single", "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali", - "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "spell-out", "square", + "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", - "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table", + "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "tamil", @@ -748,8 +749,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } }, ":": function(stream) { - if (stream.match(/\s*\{/)) - return [null, "{"]; + if (stream.match(/\s*\{/, false)) + return [null, null] return false; }, "$": function(stream) { diff --git a/gui/public/codemirror/mode/css/gss.html b/gui/public/codemirror/mode/css/gss.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/gss_test.js b/gui/public/codemirror/mode/css/gss_test.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/index.html b/gui/public/codemirror/mode/css/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/less.html b/gui/public/codemirror/mode/css/less.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/less_test.js b/gui/public/codemirror/mode/css/less_test.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/scss.html b/gui/public/codemirror/mode/css/scss.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/scss_test.js b/gui/public/codemirror/mode/css/scss_test.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/css/test.js b/gui/public/codemirror/mode/css/test.js old mode 100755 new mode 100644 index 7a496fb0..6fc6e33c --- a/gui/public/codemirror/mode/css/test.js +++ b/gui/public/codemirror/mode/css/test.js @@ -197,4 +197,10 @@ MT("counter-style-symbols", "[tag ol] { [property list-style]: [atom symbols]([atom cyclic] [string \"*\"] [string \"\\2020\"] [string \"\\2021\"] [string \"\\A7\"]); }"); + + MT("comment-does-not-disrupt", + "[def @font-face] [comment /* foo */] {", + " [property src]: [atom url]([string x]);", + " [property font-family]: [variable One];", + "}") })(); diff --git a/gui/public/codemirror/mode/cypher/cypher.js b/gui/public/codemirror/mode/cypher/cypher.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/cypher/index.html b/gui/public/codemirror/mode/cypher/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/cypher/test.js b/gui/public/codemirror/mode/cypher/test.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/d/d.js b/gui/public/codemirror/mode/d/d.js old mode 100755 new mode 100644 index c927a7e3..77b09c22 --- a/gui/public/codemirror/mode/d/d.js +++ b/gui/public/codemirror/mode/d/d.js @@ -44,7 +44,7 @@ CodeMirror.defineMode("d", function(config, parserConfig) { } if (ch == "/") { if (stream.eat("+")) { - state.tokenize = tokenComment; + state.tokenize = tokenNestedComment; return tokenNestedComment(stream, state); } if (stream.eat("*")) { diff --git a/gui/public/codemirror/mode/d/index.html b/gui/public/codemirror/mode/d/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/d/test.js b/gui/public/codemirror/mode/d/test.js new file mode 100644 index 00000000..d8de4ad3 --- /dev/null +++ b/gui/public/codemirror/mode/d/test.js @@ -0,0 +1,11 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "d"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT("nested_comments", + "[comment /+]","[comment comment]","[comment +/]","[variable void] [variable main](){}"); + +})(); diff --git a/gui/public/codemirror/mode/dart/dart.js b/gui/public/codemirror/mode/dart/dart.js old mode 100755 new mode 100644 index 8d383a95..4db0d371 --- a/gui/public/codemirror/mode/dart/dart.js +++ b/gui/public/codemirror/mode/dart/dart.js @@ -12,8 +12,8 @@ "use strict"; var keywords = ("this super static final const abstract class extends external factory " + - "implements get native operator set typedef with enum throw rethrow " + - "assert break case continue default in return new deferred async await " + + "implements get native set typedef with enum throw rethrow " + + "assert break case continue default in return new deferred async await covariant " + "try catch finally do else for if switch while import library export " + "part of show hide is as").split(" "); var blockKeywords = "try catch finally do else for if switch while".split(" "); diff --git a/gui/public/codemirror/mode/dart/index.html b/gui/public/codemirror/mode/dart/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/diff/diff.js b/gui/public/codemirror/mode/diff/diff.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/diff/index.html b/gui/public/codemirror/mode/diff/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/django/django.js b/gui/public/codemirror/mode/django/django.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/django/index.html b/gui/public/codemirror/mode/django/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/dtd/dtd.js b/gui/public/codemirror/mode/dtd/dtd.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/dtd/index.html b/gui/public/codemirror/mode/dtd/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/dylan/dylan.js b/gui/public/codemirror/mode/dylan/dylan.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/dylan/index.html b/gui/public/codemirror/mode/dylan/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/dylan/test.js b/gui/public/codemirror/mode/dylan/test.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/ebnf/ebnf.js b/gui/public/codemirror/mode/ebnf/ebnf.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/ebnf/index.html b/gui/public/codemirror/mode/ebnf/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/ecl/ecl.js b/gui/public/codemirror/mode/ecl/ecl.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/ecl/index.html b/gui/public/codemirror/mode/ecl/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/eiffel/eiffel.js b/gui/public/codemirror/mode/eiffel/eiffel.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/eiffel/index.html b/gui/public/codemirror/mode/eiffel/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/elm/elm.js b/gui/public/codemirror/mode/elm/elm.js old mode 100755 new mode 100644 index b31e6637..9fcfc883 --- a/gui/public/codemirror/mode/elm/elm.js +++ b/gui/public/codemirror/mode/elm/elm.js @@ -70,7 +70,7 @@ if (smallRE.test(ch)) { var isDef = source.pos === 1; source.eatWhile(idRE); - return isDef ? "variable-3" : "variable"; + return isDef ? "type" : "variable"; } if (digitRE.test(ch)) { diff --git a/gui/public/codemirror/mode/elm/index.html b/gui/public/codemirror/mode/elm/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/erlang/erlang.js b/gui/public/codemirror/mode/erlang/erlang.js old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/erlang/index.html b/gui/public/codemirror/mode/erlang/index.html old mode 100755 new mode 100644 diff --git a/gui/public/codemirror/mode/factor/factor.js b/gui/public/codemirror/mode/factor/factor.js old mode 100755 new mode 100644 index 86d7adf6..e238c5ea --- a/gui/public/codemirror/mode/factor/factor.js +++ b/gui/public/codemirror/mode/factor/factor.js @@ -22,52 +22,54 @@ {regex: /#?!.*/, token: "comment"}, // strings """, multiline --> state {regex: /"""/, token: "string", next: "string3"}, - {regex: /"/, token: "string", next: "string"}, + {regex: /(STRING:)(\s)/, token: ["keyword", null], next: "string2"}, + {regex: /\S*?"/, token: "string", next: "string"}, // numbers: dec, hex, unicode, bin, fractional, complex - {regex: /(?:[+-]?)(?:0x[\d,a-f]+)|(?:0o[0-7]+)|(?:0b[0,1]+)|(?:\d+.?\d*)/, token: "number"}, + {regex: /(?:0x[\d,a-f]+)|(?:0o[0-7]+)|(?:0b[0,1]+)|(?:\-?\d+.?\d*)(?=\s)/, token: "number"}, //{regex: /[+-]?/} //fractional // definition: defining word, defined word, etc - {regex: /(\:)(\s+)(\S+)(\s+)(\()/, token: ["keyword", null, "def", null, "keyword"], next: "stack"}, + {regex: /((?:GENERIC)|\:?\:)(\s+)(\S+)(\s+)(\()/, token: ["keyword", null, "def", null, "bracket"], next: "stack"}, + // method definition: defining word, type, defined word, etc + {regex: /(M\:)(\s+)(\S+)(\s+)(\S+)/, token: ["keyword", null, "def", null, "tag"]}, // vocabulary using --> state {regex: /USING\:/, token: "keyword", next: "vocabulary"}, // vocabulary definition/use - {regex: /(USE\:|IN\:)(\s+)(\S+)/, token: ["keyword", null, "variable-2"]}, - //