From 16ce81c1cf4f489274758cd94534157c030f2670 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 20 Mar 2019 10:44:46 +0100 Subject: [PATCH 01/38] Bump version number post-5.45.0 --- doc/manual.html | 2 +- package.json | 2 +- src/edit/main.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index bbc8ed7b7a..5b74e40324 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -69,7 +69,7 @@

User manual and reference guide - version 5.45.0 + version 5.45.1

CodeMirror is a code-editor component that can be embedded in diff --git a/package.json b/package.json index c22f47e376..b7e4a810c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version": "5.45.0", + "version": "5.45.1", "main": "lib/codemirror.js", "style": "lib/codemirror.css", "author": { diff --git a/src/edit/main.js b/src/edit/main.js index 5a6ccdd14e..04f138d66a 100644 --- a/src/edit/main.js +++ b/src/edit/main.js @@ -66,4 +66,4 @@ import { addLegacyProps } from "./legacy.js" addLegacyProps(CodeMirror) -CodeMirror.version = "5.45.0" +CodeMirror.version = "5.45.1" From e6427cd57ae9ec2fc6a48127201aee4c3cf41e94 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 20 Mar 2019 18:15:56 -0400 Subject: [PATCH 02/38] Recursively compute innerMode in continuelist addon When creating an overlay mode from e.g. `gfm`, which has `markdown` as an `innerMode`, we need to recursively compute `innerMode` via `CodeMirror.innerMode` to correctly determine `markdown` descendant mode. --- addon/edit/continuelist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/edit/continuelist.js b/addon/edit/continuelist.js index 65096fb513..fb5f03735d 100644 --- a/addon/edit/continuelist.js +++ b/addon/edit/continuelist.js @@ -23,7 +23,7 @@ // If we're not in Markdown mode, fall back to normal newlineAndIndent var eolState = cm.getStateAfter(pos.line); - var inner = cm.getMode().innerMode(eolState); + var inner = CodeMirror.innerMode(cm.getMode(), eolState); if (inner.mode.name !== "markdown") { cm.execCommand("newlineAndIndent"); return; From 042d981330a9895f9c22b3fb043ffe783bc000a3 Mon Sep 17 00:00:00 2001 From: Markus Olsson Date: Thu, 21 Mar 2019 14:07:58 +0100 Subject: [PATCH 03/38] Cancel mouse selection before swapping document To avoid the mouse handling code getting confused --- src/edit/methods.js | 2 ++ src/edit/mouse_events.js | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/edit/methods.js b/src/edit/methods.js index 685b5112a4..7ed6381e48 100644 --- a/src/edit/methods.js +++ b/src/edit/methods.js @@ -423,6 +423,8 @@ export default function(CodeMirror) { swapDoc: methodOp(function(doc) { let old = this.doc old.cm = null + // Cancel the current text selection if any (#5821) + if (this.state.selectingText) this.state.selectingText() attachDoc(this, doc) clearCaches(this) this.display.input.reset() diff --git a/src/edit/mouse_events.js b/src/edit/mouse_events.js index b62789ee2a..5975fd4428 100644 --- a/src/edit/mouse_events.js +++ b/src/edit/mouse_events.js @@ -305,8 +305,13 @@ function leftButtonSelect(cm, event, start, behavior) { function done(e) { cm.state.selectingText = false counter = Infinity - e_preventDefault(e) - display.input.focus() + // If e is null or undefined we interpret this as someone trying + // to explicitly cancel the selection rather than the user + // letting go of the mouse button. + if (e) { + e_preventDefault(e) + display.input.focus() + } off(display.wrapper.ownerDocument, "mousemove", move) off(display.wrapper.ownerDocument, "mouseup", up) doc.history.lastSelOrigin = null From d5906e25c5d417c4e1ab83afaeb5bae6ca6a5433 Mon Sep 17 00:00:00 2001 From: Leo Baschy Date: Thu, 21 Mar 2019 19:13:01 -0700 Subject: [PATCH 04/38] [xml-hint addon] Prevent extraneous quote If typing attribute=" and by closetag it becomes attribute="" and user presses Ctrl-Space to autocomplete and the completion is "x" then by this fix instead of obnoxious attribute="x"" it becomes nice attribute="x". --- addon/hint/xml-hint.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addon/hint/xml-hint.js b/addon/hint/xml-hint.js index 69914fa21d..106ba4f32c 100644 --- a/addon/hint/xml-hint.js +++ b/addon/hint/xml-hint.js @@ -92,6 +92,10 @@ quote = token.string.charAt(len - 1); prefix = token.string.substr(n, len - 2); } + if (n) { // an opening quote + var line = cm.getLine(cur.line); + if (line.length > token.end && line.charAt(token.end) == quote) token.end++; // include a closing quote + } replaceToken = true; } for (var i = 0; i < atValues.length; ++i) if (!prefix || matches(atValues[i], prefix, matchInMiddle)) From 1dc1fa16871842f9efecebb37238160ac7d5bfba Mon Sep 17 00:00:00 2001 From: Leo Baschy Date: Fri, 22 Mar 2019 04:02:04 -0700 Subject: [PATCH 05/38] [closetag addon] Optionally prefer empty-element tags When user types substitute if so configured. --- addon/edit/closetag.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 25e62d540e..e5e83bcd32 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -21,6 +21,8 @@ * An array of tag names that should, when opened, cause a * blank line to be added inside the tag, and the blank line and * closing line to be indented. + * `emptyTags` (default is none) + * An array of XML tag names that should be autoclosed with '/>'. * * See demos/closetag.html for a usage example. */ @@ -76,6 +78,12 @@ closingTagExists(cm, tagName, pos, state, true)) return CodeMirror.Pass; + var emptyTags = typeof opt == "object" && opt.emptyTags; + if (emptyTags && indexOf(emptyTags, tagName) > -1) { + replacements[i] = { text: "/>", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) }; + continue; + } + var indent = indentTags && indexOf(indentTags, lowerTagName) > -1; replacements[i] = {indent: indent, text: ">" + (indent ? "\n\n" : "") + "", From 11e58686f467719fe7e469c2e025b334d0510e5e Mon Sep 17 00:00:00 2001 From: thomasmaclean Date: Fri, 22 Mar 2019 09:16:41 -0400 Subject: [PATCH 06/38] [yonce theme] Update colors --- theme/yonce.css | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/theme/yonce.css b/theme/yonce.css index b74bf53e71..e01c0c3b1c 100644 --- a/theme/yonce.css +++ b/theme/yonce.css @@ -9,32 +9,47 @@ .cm-s-yonce.CodeMirror { background: #1C1C1C; color: #d4d4d4; } /**/ .cm-s-yonce div.CodeMirror-selected { background: rgba(252, 69, 133, 0.478); } /**/ -.cm-s-yonce .CodeMirror-line::selection, .cm-s-yonce .CodeMirror-line > span::selection, .cm-s-yonce .CodeMirror-line > span > span::selection { background: rgba(252, 67, 132, 0.47); } -.cm-s-yonce .CodeMirror-line::-moz-selection, .cm-s-yonce .CodeMirror-line > span::-moz-selection, .cm-s-yonce .CodeMirror-line > span > span::-moz-selection { background: rgba(252, 67, 132, 0.47); } +.cm-s-yonce .CodeMirror-selectedtext, +.cm-s-yonce .CodeMirror-selected, +.cm-s-yonce .CodeMirror-line::selection, +.cm-s-yonce .CodeMirror-line > span::selection, +.cm-s-yonce .CodeMirror-line > span > span::selection, +.cm-s-yonce .CodeMirror-line::-moz-selection, +.cm-s-yonce .CodeMirror-line > span::-moz-selection, +.cm-s-yonce .CodeMirror-line > span > span::-moz-selection { background: rgba(252, 67, 132, 0.47); } -.cm-s-yonce.CodeMirror pre { padding-left: 15px; } +.cm-s-yonce.CodeMirror pre { padding-left: 0px; } .cm-s-yonce .CodeMirror-gutters {background: #1C1C1C; border-right: 0px;} .cm-s-yonce .CodeMirror-linenumber {color: #777777; padding-right: 10px; } .cm-s-yonce .CodeMirror-activeline .CodeMirror-linenumber.CodeMirror-gutter-elt { background: #1C1C1C; color: #fc4384; } .cm-s-yonce .CodeMirror-linenumber { color: #777; } -.cm-s-yonce .CodeMirror-cursor { border-left: 1px solid #FC4384; } +.cm-s-yonce .CodeMirror-cursor { border-left: 2px solid #FC4384; } +.cm-s-yonce .cm-searching { background: rgb(243, 155, 53, .3) !important; outline: 1px solid #F39B35; } +.cm-s-yonce .cm-searching.CodeMirror-selectedtext { background: rgb(243, 155, 53, .7) !important; color: white; } .cm-s-yonce .cm-keyword { color: #00A7AA; } /**/ .cm-s-yonce .cm-atom { color: #F39B35; } .cm-s-yonce .cm-number, .cm-s-yonce span.cm-type { color: #A06FCA; } /**/ -.cm-s-yonce .cm-def { color: #D4D4D4; font-style: italic; } -.cm-s-yonce .cm-property { color: #FC4384; } +.cm-s-yonce .cm-def { color: #98E342; } +.cm-s-yonce .cm-property, +.cm-s-yonce span.cm-variable { color: #D4D4D4; font-style: italic; } .cm-s-yonce span.cm-variable-2 { color: #da7dae; font-style: italic; } -.cm-s-yonce span.cm-variable-3 { color: #FC4384; } /**/ -.cm-s-yonce span.cm-tag { color: #D4D4D4; font-style: italic; } /**/ -.cm-s-yonce .cm-operator { color: #98E342; } /**/ +.cm-s-yonce span.cm-variable-3 { color: #A06FCA; } +.cm-s-yonce .cm-type.cm-def { color: #FC4384; font-style: normal; text-decoration: underline; } +.cm-s-yonce .cm-property.cm-def { color: #FC4384; font-style: normal; } +.cm-s-yonce .cm-callee { color: #FC4384; font-style: normal; } +.cm-s-yonce .cm-operator { color: #FC4384; } /**/ +.cm-s-yonce .cm-qualifier, +.cm-s-yonce .cm-tag { color: #FC4384; } +.cm-s-yonce .cm-tag.cm-bracket { color: #D4D4D4; } +.cm-s-yonce .cm-attribute { color: #A06FCA; } .cm-s-yonce .cm-comment { color:#696d70; font-style:italic; font-weight:normal; } /**/ -.cm-s-yonce .cm-string { color:#E6DB74; font-style:italic; } /**/ +.cm-s-yonce .cm-comment.cm-tag { color: #FC4384 } +.cm-s-yonce .cm-comment.cm-attribute { color: #D4D4D4; } +.cm-s-yonce .cm-string { color:#E6DB74; } /**/ .cm-s-yonce .cm-string-2 { color:#F39B35; } /*?*/ -.cm-s-yonce .cm-meta { background-color:#141414; color:#D4D4D4; } /*?*/ +.cm-s-yonce .cm-meta { color: #D4D4D4; background: inherit; } .cm-s-yonce .cm-builtin { color: #FC4384; } /*?*/ -.cm-s-yonce .cm-tag { color: #00A7AA; } /**/ -.cm-s-yonce .cm-attribute { color: #A06FCA; } /*?*/ .cm-s-yonce .cm-header { color: #da7dae; } .cm-s-yonce .cm-hr { color: #98E342; } .cm-s-yonce .cm-link { color:#696d70; font-style:italic; text-decoration:none; } /**/ From 4c6269c510c632639666776c76020555cc87249f Mon Sep 17 00:00:00 2001 From: Vadim Dyachenko Date: Sat, 23 Mar 2019 17:27:15 +0200 Subject: [PATCH 07/38] Do not consider key-127 (F16) to be Delete As per https://github.com/codemirror/CodeMirror/issues/5829 --- src/input/keynames.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/keynames.js b/src/input/keynames.js index 9a61d152b8..d3339038fc 100644 --- a/src/input/keynames.js +++ b/src/input/keynames.js @@ -3,7 +3,7 @@ export let keyNames = { 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", - 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", 145: "ScrollLock", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock", 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" From b28c41e8698e6402184bf9c24af5f74c81aaac90 Mon Sep 17 00:00:00 2001 From: Luciano Santana Date: Wed, 27 Mar 2019 12:33:16 +0200 Subject: [PATCH 08/38] [html-lint addon] Adding compatibilty with HTMLHint 0.11.0 --- addon/lint/html-lint.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/addon/lint/html-lint.js b/addon/lint/html-lint.js index dae4764924..5295c33331 100644 --- a/addon/lint/html-lint.js +++ b/addon/lint/html-lint.js @@ -29,7 +29,13 @@ CodeMirror.registerHelper("lint", "html", function(text, options) { var found = []; - if (HTMLHint && !HTMLHint.verify) HTMLHint = HTMLHint.HTMLHint; + if (HTMLHint && !HTMLHint.verify) { + if(typeof HTMLHint.default !== 'undefined') { + HTMLHint = HTMLHint.default; + } else { + HTMLHint = HTMLHint.HTMLHint; + } + } if (!HTMLHint) HTMLHint = window.HTMLHint; if (!HTMLHint) { if (window.console) { From c18094eab567211f73050beaa5c3fcceb2133888 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 28 Mar 2019 10:55:21 +0100 Subject: [PATCH 09/38] Allow CSS styles to be passed for gutter backgrounds Issue #5834 --- doc/manual.html | 18 ++++---- src/display/Display.js | 6 ++- src/display/gutters.js | 56 +++++++++++++++---------- src/display/line_numbers.js | 2 +- src/display/update_display.js | 6 +-- src/display/update_line.js | 4 +- src/edit/CodeMirror.js | 5 +-- src/edit/methods.js | 2 +- src/edit/mouse_events.js | 6 +-- src/edit/options.js | 27 +++++------- src/input/ContentEditableInput.js | 2 +- src/measurement/position_measurement.js | 5 ++- 12 files changed, 72 insertions(+), 67 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 5b74e40324..260b989ef8 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -371,16 +371,16 @@

Configuration

passed the line number, and should return a string that will be shown in the gutter. -
gutters: array<string>
+
gutters: array<string | {className: string, style: ?string}>
Can be used to add extra gutters (beyond or instead of the - line number gutter). Should be an array of CSS class names, each - of which defines a width (and optionally a - background), and which will be used to draw the background of - the gutters. May include - the CodeMirror-linenumbers class, in order to - explicitly set the position of the line number gutter (it will - default to be to the right of all other gutters). These class - names are the keys passed + line number gutter). Should be an array of CSS class names or + class name / CSS string pairs, each of which defines + a width (and optionally a background), and which + will be used to draw the background of the gutters. May + include the CodeMirror-linenumbers class, in order + to explicitly set the position of the line number gutter (it + will default to be to the right of all other gutters). These + class names are the keys passed to setGutterMarker.
fixedGutter: boolean
diff --git a/src/display/Display.js b/src/display/Display.js index 54b228a9b3..d57f00bddd 100644 --- a/src/display/Display.js +++ b/src/display/Display.js @@ -1,12 +1,13 @@ import { gecko, ie, ie_version, mobile, webkit } from "../util/browser.js" import { elt, eltP } from "../util/dom.js" import { scrollerGap } from "../util/misc.js" +import { getGutters, renderGutters } from "./gutters.js" // The display handles the DOM integration, both for input reading // and content drawing. It holds references to DOM nodes and // display-related state. -export function Display(place, doc, input) { +export function Display(place, doc, input, options) { let d = this this.input = input @@ -102,5 +103,8 @@ export function Display(place, doc, input) { d.activeTouch = null + d.gutterSpecs = getGutters(options.gutters, options.lineNumbers) + renderGutters(d) + input.init(d) } diff --git a/src/display/gutters.js b/src/display/gutters.js index 37405b6d8c..b27b6ce774 100644 --- a/src/display/gutters.js +++ b/src/display/gutters.js @@ -1,34 +1,44 @@ import { elt, removeChildren } from "../util/dom.js" -import { indexOf } from "../util/misc.js" - +import { regChange } from "./view_tracking.js" +import { alignHorizontally } from "./line_numbers.js" import { updateGutterSpace } from "./update_display.js" +export function getGutters(gutters, lineNumbers) { + let result = [], sawLineNumbers = false + for (let i = 0; i < gutters.length; i++) { + let name = gutters[i], style = null + if (typeof name != "string") { style = name.style; name = name.className } + if (name == "CodeMirror-linenumbers") { + if (!lineNumbers) continue + else sawLineNumbers = true + } + result.push({className: name, style}) + } + if (lineNumbers && !sawLineNumbers) result.push({className: "CodeMirror-linenumbers", style: null}) + return result +} + // Rebuild the gutter elements, ensure the margin to the left of the // code matches their width. -export function updateGutters(cm) { - let gutters = cm.display.gutters, specs = cm.options.gutters +export function renderGutters(display) { + let gutters = display.gutters, specs = display.gutterSpecs removeChildren(gutters) - let i = 0 - for (; i < specs.length; ++i) { - let gutterClass = specs[i] - let gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)) - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt - gElt.style.width = (cm.display.lineNumWidth || 1) + "px" + display.lineGutter = null + for (let i = 0; i < specs.length; ++i) { + let {className, style} = specs[i] + let gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className)) + if (style) gElt.style.cssText = style + if (className == "CodeMirror-linenumbers") { + display.lineGutter = gElt + gElt.style.width = (display.lineNumWidth || 1) + "px" } } - gutters.style.display = i ? "" : "none" - updateGutterSpace(cm) + gutters.style.display = specs.length ? "" : "none" + updateGutterSpace(display) } -// Make sure the gutters options contains the element -// "CodeMirror-linenumbers" when the lineNumbers option is true. -export function setGuttersForLineNumbers(options) { - let found = indexOf(options.gutters, "CodeMirror-linenumbers") - if (found == -1 && options.lineNumbers) { - options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]) - } else if (found > -1 && !options.lineNumbers) { - options.gutters = options.gutters.slice(0) - options.gutters.splice(found, 1) - } +export function updateGutters(cm) { + renderGutters(cm.display) + regChange(cm) + alignHorizontally(cm) } diff --git a/src/display/line_numbers.js b/src/display/line_numbers.js index 3ab957509c..073cbade05 100644 --- a/src/display/line_numbers.js +++ b/src/display/line_numbers.js @@ -41,7 +41,7 @@ export function maybeUpdateLineNumberWidth(cm) { display.lineNumWidth = display.lineNumInnerWidth + padding display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 display.lineGutter.style.width = display.lineNumWidth + "px" - updateGutterSpace(cm) + updateGutterSpace(cm.display) return true } return false diff --git a/src/display/update_display.js b/src/display/update_display.js index 86c7132131..20798f590b 100644 --- a/src/display/update_display.js +++ b/src/display/update_display.js @@ -248,9 +248,9 @@ function patchDisplay(cm, updateNumbersFrom, dims) { while (cur) cur = rm(cur) } -export function updateGutterSpace(cm) { - let width = cm.display.gutters.offsetWidth - cm.display.sizer.style.marginLeft = width + "px" +export function updateGutterSpace(display) { + let width = display.gutters.offsetWidth + display.sizer.style.marginLeft = width + "px" } export function setDocumentHeight(cm, measure) { diff --git a/src/display/update_line.js b/src/display/update_line.js index db9df26d92..a436973ea9 100644 --- a/src/display/update_line.js +++ b/src/display/update_line.js @@ -113,8 +113,8 @@ function updateLineGutter(cm, lineView, lineN, dims) { elt("div", lineNumberFor(cm.options, lineN), "CodeMirror-linenumber CodeMirror-gutter-elt", `left: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`)) - if (markers) for (let k = 0; k < cm.options.gutters.length; ++k) { - let id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id] + if (markers) for (let k = 0; k < cm.display.gutterSpecs.length; ++k) { + let id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id] if (found) gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", `left: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`)) diff --git a/src/edit/CodeMirror.js b/src/edit/CodeMirror.js index 2888f42205..9188e1b255 100644 --- a/src/edit/CodeMirror.js +++ b/src/edit/CodeMirror.js @@ -1,6 +1,5 @@ import { Display } from "../display/Display.js" import { onFocus, onBlur } from "../display/focus.js" -import { setGuttersForLineNumbers, updateGutters } from "../display/gutters.js" import { maybeUpdateLineNumberWidth } from "../display/line_numbers.js" import { endOperation, operation, startOperation } from "../display/operations.js" import { initScrollbars } from "../display/scrollbars.js" @@ -33,7 +32,6 @@ export function CodeMirror(place, options) { this.options = options = options ? copyObj(options) : {} // Determine effective options based on given values and defaults. copyObj(defaults, options, false) - setGuttersForLineNumbers(options) let doc = options.value if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) @@ -41,9 +39,8 @@ export function CodeMirror(place, options) { this.doc = doc let input = new CodeMirror.inputStyles[options.inputStyle](this) - let display = this.display = new Display(place, doc, input) + let display = this.display = new Display(place, doc, input, options) display.wrapper.CodeMirror = this - updateGutters(this) themeChanged(this) if (options.lineWrapping) this.display.wrapper.className += " CodeMirror-wrap" diff --git a/src/edit/methods.js b/src/edit/methods.js index 7ed6381e48..e40012e75b 100644 --- a/src/edit/methods.js +++ b/src/edit/methods.js @@ -414,7 +414,7 @@ export default function(CodeMirror) { this.curOp.forceUpdate = true clearCaches(this) scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) - updateGutterSpace(this) + updateGutterSpace(this.display) if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) estimateLineHeights(this) signal(this, "refresh", this) diff --git a/src/edit/mouse_events.js b/src/edit/mouse_events.js index 5975fd4428..d0bbfba1d3 100644 --- a/src/edit/mouse_events.js +++ b/src/edit/mouse_events.js @@ -380,12 +380,12 @@ function gutterEvent(cm, e, type, prevent) { if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e) mY -= lineBox.top - display.viewOffset - for (let i = 0; i < cm.options.gutters.length; ++i) { + for (let i = 0; i < cm.display.gutterSpecs.length; ++i) { let g = display.gutters.childNodes[i] if (g && g.getBoundingClientRect().right >= mX) { let line = lineAtHeight(cm.doc, mY) - let gutter = cm.options.gutters[i] - signal(cm, type, cm, line, gutter, e) + let gutter = cm.display.gutterSpecs[i] + signal(cm, type, cm, line, gutter.className, e) return e_defaultPrevented(e) } } diff --git a/src/edit/options.js b/src/edit/options.js index 27ecac7cff..ccca45dd3c 100644 --- a/src/edit/options.js +++ b/src/edit/options.js @@ -1,6 +1,5 @@ import { onBlur } from "../display/focus.js" -import { setGuttersForLineNumbers, updateGutters } from "../display/gutters.js" -import { alignHorizontally } from "../display/line_numbers.js" +import { getGutters, updateGutters } from "../display/gutters.js" import { loadMode, resetModeState } from "../display/mode_state.js" import { initScrollbars, updateScrollbars } from "../display/scrollbars.js" import { updateSelection } from "../display/selection.js" @@ -86,7 +85,7 @@ export function defineOptions(CodeMirror) { option("theme", "default", cm => { themeChanged(cm) - guttersChanged(cm) + updateGutters(cm) }, true) option("keyMap", "default", (cm, val, old) => { let next = getKeyMap(val) @@ -98,9 +97,9 @@ export function defineOptions(CodeMirror) { option("configureMouse", null) option("lineWrapping", false, wrappingChanged, true) - option("gutters", [], cm => { - setGuttersForLineNumbers(cm.options) - guttersChanged(cm) + option("gutters", [], (cm, val) => { + cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers) + updateGutters(cm) }, true) option("fixedGutter", true, (cm, val) => { cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" @@ -113,12 +112,12 @@ export function defineOptions(CodeMirror) { cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) }, true) - option("lineNumbers", false, cm => { - setGuttersForLineNumbers(cm.options) - guttersChanged(cm) + option("lineNumbers", false, (cm, val) => { + cm.display.gutterSpecs = getGutters(cm.options.gutters, val) + updateGutters(cm) }, true) - option("firstLineNumber", 1, guttersChanged, true) - option("lineNumberFormatter", integer => integer, guttersChanged, true) + option("firstLineNumber", 1, updateGutters, true) + option("lineNumberFormatter", integer => integer, updateGutters, true) option("showCursorWhenSelecting", false, updateSelection, true) option("resetSelectionOnContextMenu", true) @@ -160,12 +159,6 @@ export function defineOptions(CodeMirror) { option("phrases", null) } -function guttersChanged(cm) { - updateGutters(cm) - regChange(cm) - alignHorizontally(cm) -} - function dragDropChanged(cm, value, old) { let wasOn = old && old != Init if (!value != !wasOn) { diff --git a/src/input/ContentEditableInput.js b/src/input/ContentEditableInput.js index b8d1aff042..b77c7d49db 100644 --- a/src/input/ContentEditableInput.js +++ b/src/input/ContentEditableInput.js @@ -236,7 +236,7 @@ export default class ContentEditableInput { // Because Android doesn't allow us to actually detect backspace // presses in a sane way, this code checks for when that happens // and simulates a backspace press in this case. - if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { + if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) { this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) this.blur() this.focus() diff --git a/src/measurement/position_measurement.js b/src/measurement/position_measurement.js index eb9d18a4cb..eb78287865 100644 --- a/src/measurement/position_measurement.js +++ b/src/measurement/position_measurement.js @@ -618,8 +618,9 @@ export function getDimensions(cm) { let d = cm.display, left = {}, width = {} let gutterLeft = d.gutters.clientLeft for (let n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft - width[cm.options.gutters[i]] = n.clientWidth + let id = cm.display.gutterSpecs[i].className + left[id] = n.offsetLeft + n.clientLeft + gutterLeft + width[id] = n.clientWidth } return {fixedPos: compensateForHScroll(d), gutterTotalWidth: d.gutters.offsetWidth, From ebecdd4d856c54a9ba32fc35990bbd7facebc049 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 2 Apr 2019 04:35:41 -0300 Subject: [PATCH 10/38] [matchesonscrollbar addon] Fix possible discrepancy with search query The scrollbar annotations may fail to match search query under certain circumstances. This happens when the search cursor is `multiline: false`. The matchesonscrollbar addon does not have a way to indicate `multiline: false`, the multiline status is heuristically determined and because of this it may end up being opposite of the status of the search cursor. --- addon/search/matchesonscrollbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/search/matchesonscrollbar.js b/addon/search/matchesonscrollbar.js index 4645f5eb2d..8a4a827584 100644 --- a/addon/search/matchesonscrollbar.js +++ b/addon/search/matchesonscrollbar.js @@ -46,7 +46,7 @@ if (match.from.line >= this.gap.to) break; if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); } - var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold); + var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline}); var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES; while (cursor.findNext()) { var match = {from: cursor.from(), to: cursor.to()}; From 97f6acc3804767ace06d8146a0e2ac24060087d6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 11 Apr 2019 08:18:29 +0200 Subject: [PATCH 11/38] Correctly use the string 'off' for autocapitalize and autocorrect Rather than 'false', which is ignored Issue #3403 --- src/input/input.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/input.js b/src/input/input.js index 344a2a7b23..26bba1d26f 100644 --- a/src/input/input.js +++ b/src/input/input.js @@ -114,8 +114,8 @@ export function copyableRanges(cm) { } export function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { - field.setAttribute("autocorrect", !!autocorrect) - field.setAttribute("autocapitalize", !!autocapitalize) + field.setAttribute("autocorrect", autocorrect ? "" : "off") + field.setAttribute("autocapitalize", autocapitalize ? "" : "off") field.setAttribute("spellcheck", !!spellcheck) } From 1c2b083e3ce7af6ef89ed092635824f9da4d55bc Mon Sep 17 00:00:00 2001 From: Scott Feeney Date: Thu, 11 Apr 2019 00:40:47 -0700 Subject: [PATCH 12/38] [swift mode] Properly handle empty strings --- mode/swift/swift.js | 41 +++++++++++++++++++++-------------------- mode/swift/test.js | 3 ++- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/mode/swift/swift.js b/mode/swift/swift.js index b7b3da56c0..55e31e2708 100644 --- a/mode/swift/swift.js +++ b/mode/swift/swift.js @@ -73,8 +73,9 @@ stream.match("..") return "punctuation" } - if (ch = stream.match(/("{3}|"|')/)) { - var tokenize = tokenString(ch[0]) + var stringMatch + if (stringMatch = stream.match(/("""|"|')/)) { + var tokenize = tokenString.bind(null, stringMatch[0]) state.tokenize.push(tokenize) return tokenize(stream, state) } @@ -115,29 +116,29 @@ } } - function tokenString(quote) { - var singleLine = quote.length == 1 - return function(stream, state) { - var ch, escaped = false - while (ch = stream.next()) { - if (escaped) { - if (ch == "(") { - state.tokenize.push(tokenUntilClosingParen()) - return "string" - } - escaped = false - } else if (stream.match(quote)) { - state.tokenize.pop() + function tokenString(openQuote, stream, state) { + var singleLine = openQuote.length == 1 + var ch, escaped = false + while (ch = stream.peek()) { + if (escaped) { + stream.next() + if (ch == "(") { + state.tokenize.push(tokenUntilClosingParen()) return "string" - } else { - escaped = ch == "\\" } - } - if (singleLine) { + escaped = false + } else if (stream.match(openQuote)) { state.tokenize.pop() + return "string" + } else { + stream.next() + escaped = ch == "\\" } - return "string" } + if (singleLine) { + state.tokenize.pop() + } + return "string" } function tokenComment(stream, state) { diff --git a/mode/swift/test.js b/mode/swift/test.js index dc0a1f8fdc..8c93721d12 100644 --- a/mode/swift/test.js +++ b/mode/swift/test.js @@ -40,7 +40,8 @@ "[string multi]", "[string line]", "[string \"test\"]", - "[string \"\"\"]"); + "[string \"\"\"]", + "[variable print][punctuation (][string \"\"][punctuation )]"); // Comments. MT("comments", From 6a9c89579efeb3ed48dcf151a1a06231229a8a31 Mon Sep 17 00:00:00 2001 From: clso Date: Sat, 20 Apr 2019 22:28:44 +0800 Subject: [PATCH 13/38] [vb mode] Update vb.net keywords --- mode/vb/vb.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mode/vb/vb.js b/mode/vb/vb.js index 5892cc04e9..6e4b476309 100644 --- a/mode/vb/vb.js +++ b/mode/vb/vb.js @@ -25,16 +25,16 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); - var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try']; - var middleKeywords = ['else','elseif','case', 'catch']; + var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try', 'structure', 'synclock', 'using', 'with']; + var middleKeywords = ['else','elseif','case', 'catch', 'finally']; var endKeywords = ['next','loop']; - var operatorKeywords = ['and', 'or', 'not', 'xor', 'in']; + var operatorKeywords = ['and', "andalso", 'or', 'orelse', 'xor', 'in', 'not', 'is', 'isnot', 'like']; var wordOperators = wordRegexp(operatorKeywords); - var commonKeywords = ['as', 'dim', 'break', 'continue','optional', 'then', 'until', - 'goto', 'byval','byref','new','handles','property', 'return', - 'const','private', 'protected', 'friend', 'public', 'shared', 'static', 'true','false']; - var commontypes = ['integer','string','double','decimal','boolean','short','char', 'float','single']; + + var commonKeywords = ["#const", "#else", "#elseif", "#end", "#if", "#region", "addhandler", "addressof", "alias", "as", "byref", "byval", "cbool", "cbyte", "cchar", "cdate", "cdbl", "cdec", "cint", "clng", "cobj", "compare", "const", "continue", "csbyte", "cshort", "csng", "cstr", "cuint", "culng", "cushort", "declare", "default", "delegate", "dim", "directcast", "each", "erase", "error", "event", "exit", "explicit", "false", "for", "friend", "gettype", "goto", "handles", "implements", "imports", "infer", "inherits", "interface", "isfalse", "istrue", "lib", "me", "mod", "mustinherit", "mustoverride", "my", "mybase", "myclass", "namespace", "narrowing", "new", "nothing", "notinheritable", "notoverridable", "of", "off", "on", "operator", "option", "optional", "out", "overloads", "overridable", "overrides", "paramarray", "partial", "private", "protected", "public", "raiseevent", "readonly", "redim", "removehandler", "resume", "return", "shadows", "shared", "static", "step", "stop", "strict", "then", "throw", "to", "true", "trycast", "typeof", "until", "until", "when", "widening", "withevents", "writeonly"]; + + var commontypes = ['object', 'boolean', 'char', 'string', 'byte', 'sbyte', 'short', 'ushort', 'int16', 'uint16', 'integer', 'uinteger', 'int32', 'uint32', 'long', 'ulong', 'int64', 'uint64', 'decimal', 'single', 'double', 'float', 'date', 'datetime', 'intptr', 'uintptr']; var keywords = wordRegexp(commonKeywords); var types = wordRegexp(commontypes); From 901fbd82a326f3b9a63f6d9be5c652bfd58d1c7c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 22 Apr 2019 13:43:41 +0200 Subject: [PATCH 14/38] Mark version 5.46.0 --- AUTHORS | 5 +++++ CHANGELOG.md | 16 ++++++++++++++++ doc/manual.html | 2 +- doc/releases.html | 11 +++++++++++ index.html | 2 +- package.json | 2 +- src/edit/main.js | 2 +- 7 files changed, 36 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index a0db64dabb..0e4c895d3c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -156,6 +156,7 @@ Christopher Pfohl Christopher Wallis Chunliang Lyu ciaranj +clso CodeAnimal CodeBitt coderaiser @@ -223,6 +224,7 @@ Emmanuel Schanzer Enam Mijbah Noor Eric Allam Eric Bogard +Erik Demaine Erik Welander eustas Fabien Dubosson @@ -446,6 +448,7 @@ Lorenzo Stoakes Louis Mauchet Luca Fabbri Luciano Longo +Luciano Santana Lu Fangjian Luke Browning Luke Granger-Brown @@ -671,6 +674,7 @@ Saul Costa S. Chris Colbert SCLINIC\jdecker Scott Aikin +Scott Feeney Scott Goodhew Sebastian Wilzbach Sebastian Zaha @@ -761,6 +765,7 @@ TSUYUSATO Kitsune Tugrul Elmas twifkak Tyler Long +Vadim Dyachenko Vadzim Ramanenka Vaibhav Sagar VapidWorx diff --git a/CHANGELOG.md b/CHANGELOG.md index 970d857014..fb64003aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +### Bug fixes + +Properly turn off `autocorrect` and `autocapitalize` in the editor's input field. + +Fix issue where calling [`swapDoc`](https://codemirror.net/doc/manual.html#swapDoc) during a mouse drag would cause an error. + +Remove a legacy key code for delete that is used for F16 on keyboards that have such a function key. + +[matchesonscrollbar addon](https://codemirror.net/doc/manual.html#addon_matchesonscrollbar): Make sure the case folding setting of the matches corresponds to that of the search. + +[swift mode](https://codemirror.net/mode/swift): Fix handling of empty strings. + +### New features + +Allow [gutters](https://codemirror.net/doc/manual.html#option_gutters) to specify direct CSS stings. + ## 5.45.0 (2019-03-20) ### Bug fixes diff --git a/doc/manual.html b/doc/manual.html index 260b989ef8..f7271e2675 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -69,7 +69,7 @@

User manual and reference guide - version 5.45.1 + version 5.46.0

CodeMirror is a code-editor component that can be embedded in diff --git a/doc/releases.html b/doc/releases.html index 2bbe63240a..6ecaef0e1b 100644 --- a/doc/releases.html +++ b/doc/releases.html @@ -30,6 +30,17 @@

Release notes and version history

Version 5.x

+

22-04-2019: Version 5.46.0:

+ +
    +
  • Allow gutters to specify direct CSS stings.
  • +
  • Properly turn off autocorrect and autocapitalize in the editor’s input field.
  • +
  • Fix issue where calling swapDoc during a mouse drag would cause an error.
  • +
  • Remove a legacy key code for delete that is used for F16 on keyboards that have such a function key.
  • +
  • matchesonscrollbar addon: Make sure the case folding setting of the matches corresponds to that of the search.
  • +
  • swift mode: Fix handling of empty strings.
  • +
+

20-03-2019: Version 5.45.0:

    diff --git a/index.html b/index.html index 1d45762a9f..4e4f16404d 100644 --- a/index.html +++ b/index.html @@ -99,7 +99,7 @@

    This is CodeMirror

    - Get the current version: 5.45.0.
    + Get the current version: 5.46.0.
    You can see the code,
    read the release notes,
    or study the user manual. diff --git a/package.json b/package.json index b7e4a810c0..3330694da1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version": "5.45.1", + "version": "5.46.0", "main": "lib/codemirror.js", "style": "lib/codemirror.css", "author": { diff --git a/src/edit/main.js b/src/edit/main.js index 04f138d66a..b887f8cb06 100644 --- a/src/edit/main.js +++ b/src/edit/main.js @@ -66,4 +66,4 @@ import { addLegacyProps } from "./legacy.js" addLegacyProps(CodeMirror) -CodeMirror.version = "5.45.1" +CodeMirror.version = "5.46.0" From b92f818fbd7d4ac502b2a7bfa598cb8ae5c71876 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 22 Apr 2019 13:45:27 +0200 Subject: [PATCH 15/38] Bump version number post-5.46.0 --- doc/manual.html | 2 +- package.json | 2 +- src/edit/main.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index f7271e2675..37fef13f49 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -69,7 +69,7 @@

    User manual and reference guide - version 5.46.0 + version 5.46.1

    CodeMirror is a code-editor component that can be embedded in diff --git a/package.json b/package.json index 3330694da1..69f5d346e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version": "5.46.0", + "version": "5.46.1", "main": "lib/codemirror.js", "style": "lib/codemirror.css", "author": { diff --git a/src/edit/main.js b/src/edit/main.js index b887f8cb06..be98c68218 100644 --- a/src/edit/main.js +++ b/src/edit/main.js @@ -66,4 +66,4 @@ import { addLegacyProps } from "./legacy.js" addLegacyProps(CodeMirror) -CodeMirror.version = "5.46.0" +CodeMirror.version = "5.46.1" From b84f1602db157f551fab222723d0d63fdd412132 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 22 Apr 2019 13:47:16 +0200 Subject: [PATCH 16/38] Add missing header to change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb64003aa5..f99848fea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 5.46.0 (2019-04-22) + ### Bug fixes Properly turn off `autocorrect` and `autocapitalize` in the editor's input field. From 02a607b8cf5d653ec5915cfdc9d3df7f58e25320 Mon Sep 17 00:00:00 2001 From: Max Wu Date: Mon, 22 Apr 2019 09:39:49 -0400 Subject: [PATCH 17/38] Fix a small typo in the CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f99848fea2..c6e6068f4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Remove a legacy key code for delete that is used for F16 on keyboards that have ### New features -Allow [gutters](https://codemirror.net/doc/manual.html#option_gutters) to specify direct CSS stings. +Allow [gutters](https://codemirror.net/doc/manual.html#option_gutters) to specify direct CSS strings. ## 5.45.0 (2019-03-20) From cfb2d11fd3f182a08aa5f3629b570892460f4cbc Mon Sep 17 00:00:00 2001 From: Denis Ovsienko Date: Wed, 24 Apr 2019 09:11:35 +0100 Subject: [PATCH 18/38] [real-world uses] Add RackTables --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index 8d4e07aea1..b9fad1c601 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -142,6 +142,7 @@

    CodeMirror real-world uses

  • Quantum (code editor for Chrome OS)
  • Qt+Webkit integration (building a desktop CodeMirror app)
  • Quivive File Manager
  • +
  • RackTables (data centre resources manager)
  • Rascal (tiny computer)
  • RealTime.io (Internet-of-Things infrastructure)
  • Refork (animation demo gallery and sharing)
  • From d76b4610039da83f5dc5c35a1c52c95f399dcf11 Mon Sep 17 00:00:00 2001 From: Nouzbe Date: Mon, 22 Apr 2019 15:23:53 +0200 Subject: [PATCH 19/38] [hint addon] Offset the hint to position it correctly when hintOptions.container is given --- addon/hint/show-hint.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index e3cd209d79..0172cc8e7e 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -229,14 +229,26 @@ elt.hintId = i; } + var container = completion.options.container || ownerDocument.body; var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null); var left = pos.left, top = pos.bottom, below = true; - hints.style.left = left + "px"; - hints.style.top = top + "px"; + var offsetLeft = 0, offsetTop = 0; + if (container !== ownerDocument.body) { + // We offset the cursor position because left and top are relative to the offsetParent's top left corner. + var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1; + var offsetParent = isContainerPositioned ? container : container.offsetParent; + var offsetParentPosition = offsetParent.getBoundingClientRect(); + var bodyPosition = ownerDocument.body.getBoundingClientRect(); + offsetLeft = (offsetParentPosition.left - bodyPosition.left); + offsetTop = (offsetParentPosition.top - bodyPosition.top); + } + hints.style.left = (left - offsetLeft) + "px"; + hints.style.top = (top - offsetTop) + "px"; + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth); var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight); - (completion.options.container || ownerDocument.body).appendChild(hints); + container.appendChild(hints); var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; var scrolls = hints.scrollHeight > hints.clientHeight + 1 var startScroll = cm.getScrollInfo(); @@ -244,15 +256,15 @@ if (overlapY > 0) { var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); if (curTop - height > 0) { // Fits above cursor - hints.style.top = (top = pos.top - height) + "px"; + hints.style.top = (top = pos.top - height - offsetTop) + "px"; below = false; } else if (height > winH) { hints.style.height = (winH - 5) + "px"; - hints.style.top = (top = pos.bottom - box.top) + "px"; + hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px"; var cursor = cm.getCursor(); if (data.from.ch != cursor.ch) { pos = cm.cursorCoords(cursor); - hints.style.left = (left = pos.left) + "px"; + hints.style.left = (left = pos.left - offsetLeft) + "px"; box = hints.getBoundingClientRect(); } } @@ -263,7 +275,7 @@ hints.style.width = (winW - 5) + "px"; overlapX -= (box.right - box.left) - winW; } - hints.style.left = (left = pos.left - overlapX) + "px"; + hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px"; } if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) node.style.paddingRight = cm.display.nativeBarWidth + "px" From a6ed84f9b47ec734c6cb9f294ef011d287b40f53 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 26 Apr 2019 10:43:33 +0200 Subject: [PATCH 20/38] [python mode] Parse ... as an operator To avoid treating it like 3 separate dot tokens Closes #5861 --- mode/python/python.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/python/python.js b/mode/python/python.js index 224eb7d5ec..9745103829 100644 --- a/mode/python/python.js +++ b/mode/python/python.js @@ -44,7 +44,7 @@ var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.\\]/; // (Backwards-compatiblity with old, cumbersome config system) var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters, - parserConf.operators || /^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@])/] + parserConf.operators || /^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/] for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1) var hangingIndent = parserConf.hangingIndent || conf.indentUnit; From 858f5dcc2cf19d42393c360721e5c3a7dc547c43 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 26 Apr 2019 14:58:52 +0200 Subject: [PATCH 21/38] [clojure mode] Treat commas as whitespace Closes #5582 --- mode/clojure/clojure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/clojure/clojure.js b/mode/clojure/clojure.js index 9823d812c5..25d308ab4c 100644 --- a/mode/clojure/clojure.js +++ b/mode/clojure/clojure.js @@ -166,7 +166,7 @@ CodeMirror.defineMode("clojure", function (options) { var qualifiedSymbol = /^(?:(?:[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*(?:\.[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)*\/)?(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)*(?=[\\\[\]\s"(),;@^`{}~]|$))/; function base(stream, state) { - if (stream.eatSpace()) return ["space", null]; + if (stream.eatSpace() || stream.eat(",")) return ["space", null]; if (stream.match(numberLiteral)) return [null, "number"]; if (stream.match(characterLiteral)) return [null, "string-2"]; if (stream.eat(/^"/)) return (state.tokenize = inString)(stream, state); From bf771992c93949a47d229a07d358df8424c2a35c Mon Sep 17 00:00:00 2001 From: Axel Lewenhaupt Date: Fri, 3 May 2019 09:43:27 +0200 Subject: [PATCH 22/38] [soy mode] Add a config object for tags and a tag context * Added support for restoring previous local modes by using a stack for the states. * The indentation was previously added to the first token of the inner mode. * Add tag config object --- mode/soy/soy.js | 154 ++++++++++++++++++++++++++++++----------------- mode/soy/test.js | 40 ++++++++++++ 2 files changed, 140 insertions(+), 54 deletions(-) diff --git a/mode/soy/soy.js b/mode/soy/soy.js index 59105ceee3..b42d0da103 100644 --- a/mode/soy/soy.js +++ b/mode/soy/soy.js @@ -11,9 +11,43 @@ })(function(CodeMirror) { "use strict"; - var indentingTags = ["template", "literal", "msg", "fallbackmsg", "let", "if", "elseif", - "else", "switch", "case", "default", "foreach", "ifempty", "for", - "call", "param", "deltemplate", "delcall", "log", "element"]; + var paramData = { noEndTag: true, soyState: "param-def" }; + var tags = { + "alias": { noEndTag: true }, + "delpackage": { noEndTag: true }, + "namespace": { noEndTag: true, soyState: "namespace-def" }, + "@param": paramData, + "@param?": paramData, + "@inject": paramData, + "@inject?": paramData, + "@state": paramData, + "@state?": paramData, + "template": { soyState: "templ-def", variableScope: true}, + "literal": { }, + "msg": {}, + "fallbackmsg": { noEndTag: true, reduceIndent: true}, + "let": { soyState: "var-def" }, + "if": {}, + "elseif": { noEndTag: true, reduceIndent: true}, + "else": { noEndTag: true, reduceIndent: true}, + "switch": {}, + "case": { noEndTag: true, reduceIndent: true}, + "default": { noEndTag: true, reduceIndent: true}, + "foreach": { variableScope: true, soyState: "var-def" }, + "ifempty": { noEndTag: true, reduceIndent: true}, + "for": { variableScope: true, soyState: "var-def" }, + "call": { soyState: "templ-ref" }, + "param": { soyState: "param-ref"}, + "print": { noEndTag: true }, + "deltemplate": { soyState: "templ-def", variableScope: true}, + "delcall": { soyState: "templ-ref" }, + "log": {}, + "element": { variableScope: true }, + }; + + var indentingTags = Object.keys(tags).filter(function(tag) { + return !tags[tag].noEndTag || tags[tag].reduceIndent; + }); CodeMirror.defineMode("soy", function(config) { var textMode = CodeMirror.getMode(config, "text/plain"); @@ -68,30 +102,38 @@ }; } + function popcontext(state) { + if (!state.context) return; + if (state.context.scope) { + state.variables = state.context.scope; + } + state.context = state.context.previousContext; + } + // Reference a variable `name` in `list`. // Let `loose` be truthy to ignore missing identifiers. function ref(list, name, loose) { return contains(list, name) ? "variable-2" : (loose ? "variable" : "variable-2 error"); } - function popscope(state) { - if (state.scopes) { - state.variables = state.scopes.element; - state.scopes = state.scopes.next; - } + // Data for an open soy tag. + function Context(previousContext, tag, scope) { + this.previousContext = previousContext; + this.tag = tag; + this.kind = null; + this.scope = scope; } return { startState: function() { return { - kind: [], - kindTag: [], soyState: [], templates: null, variables: prepend(null, 'ij'), scopes: null, indent: 0, quoteKind: null, + context: null, localStates: [{ mode: modes.html, state: CodeMirror.startState(modes.html) @@ -102,12 +144,10 @@ copyState: function(state) { return { tag: state.tag, // Last seen Soy tag. - kind: state.kind.concat([]), // Values of kind="" attributes. - kindTag: state.kindTag.concat([]), // Opened tags with kind="" attributes. soyState: state.soyState.concat([]), templates: state.templates, variables: state.variables, - scopes: state.scopes, + context: state.context, indent: state.indent, // Indentation of the following line. quoteKind: state.quoteKind, localStates: state.localStates.map(function(localState) { @@ -129,7 +169,7 @@ } else { stream.skipToEnd(); } - if (!state.scopes) { + if (!state.context || !state.context.scope) { var paramRe = /@param\??\s+(\S+)/g; var current = stream.current(); for (var match; (match = paramRe.exec(current)); ) { @@ -162,7 +202,6 @@ case "templ-def": if (match = stream.match(/^\.?([\w]+(?!\.[\w]+)*)/)) { state.templates = prepend(state.templates, match[1]); - state.scopes = prepend(state.scopes, state.variables); state.soyState.pop(); return "def"; } @@ -229,25 +268,27 @@ return null; case "tag": + var endTag = state.tag[0] == "/"; + var tagName = endTag ? state.tag.substring(1) : state.tag; + var tag = tags[tagName]; if (stream.match(/^\/?}/)) { + var selfClosed = stream.current() == "/}"; + if (selfClosed && !endTag) { + popcontext(state); + } if (state.tag == "/template" || state.tag == "/deltemplate") { - popscope(state); state.variables = prepend(null, 'ij'); state.indent = 0; } else { - if (state.tag == "/for" || state.tag == "/foreach") { - popscope(state); - } state.indent -= config.indentUnit * - (stream.current() == "/}" || indentingTags.indexOf(state.tag) == -1 ? 2 : 1); + (selfClosed || indentingTags.indexOf(state.tag) == -1 ? 2 : 1); } state.soyState.pop(); return "keyword"; } else if (stream.match(/^([\w?]+)(?==)/)) { if (stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) { var kind = match[1]; - state.kind.push(kind); - state.kindTag.push(state.tag); + state.context.kind = kind; var mode = modes[kind] || modes.html; var localState = last(state.localStates); if (localState.mode.indent) { @@ -296,44 +337,49 @@ if (stream.match(/^\{literal}/)) { state.indent += config.indentUnit; state.soyState.push("literal"); + state.context = new Context(state.context, "literal", state.variables); return "keyword"; // A tag-keyword must be followed by whitespace, comment or a closing tag. } else if (match = stream.match(/^\{([/@\\]?\w+\??)(?=$|[\s}]|\/[/*])/)) { - if (match[1] != "/switch") - state.indent += (/^(\/|(else|elseif|ifempty|case|fallbackmsg|default)$)/.test(match[1]) && state.tag != "switch" ? 1 : 2) * config.indentUnit; + var prevTag = state.tag; state.tag = match[1]; - if (state.tag == "/" + last(state.kindTag)) { - // We found the tag that opened the current kind="". - state.kind.pop(); - state.kindTag.pop(); - state.localStates.pop(); - var localState = last(state.localStates); - if (localState.mode.indent) { - state.indent -= localState.mode.indent(localState.state, "", ""); - } - } + var endTag = state.tag[0] == "/"; + var indentingTag = !!tags[state.tag]; + var tagName = endTag ? state.tag.substring(1) : state.tag; + var tag = tags[tagName]; + if (state.tag != "/switch") + state.indent += ((endTag || tag && tag.reduceIndent) && prevTag != "switch" ? 1 : 2) * config.indentUnit; + state.soyState.push("tag"); - if (state.tag == "template" || state.tag == "deltemplate") { - state.soyState.push("templ-def"); - } else if (state.tag == "call" || state.tag == "delcall") { - state.soyState.push("templ-ref"); - } else if (state.tag == "let") { - state.soyState.push("var-def"); - } else if (state.tag == "for" || state.tag == "foreach") { - state.scopes = prepend(state.scopes, state.variables); - state.soyState.push("var-def"); - } else if (state.tag == "namespace") { - state.soyState.push("namespace-def"); - if (!state.scopes) { - state.variables = prepend(null, 'ij'); + var tagError = false; + if (tag) { + if (!endTag) { + if (tag.soyState) state.soyState.push(tag.soyState); + } + // If a new tag, open a new context. + if (!tag.noEndTag && (indentingTag || !endTag)) { + state.context = new Context(state.context, state.tag, tag.variableScope ? state.variables : null); + // Otherwise close the current context. + } else if (endTag) { + if (!state.context || state.context.tag != tagName) { + tagError = true; + } else if (state.context) { + if (state.context.kind) { + state.localStates.pop(); + var localState = last(state.localStates); + if (localState.mode.indent) { + state.indent -= localState.mode.indent(localState.state, "", ""); + } + } + popcontext(state); + } } - } else if (state.tag.match(/^@(?:param\??|inject|state)/)) { - state.soyState.push("param-def"); - } else if (state.tag.match(/^(?:param)/)) { - state.soyState.push("param-ref"); + } else if (endTag) { + // Assume all tags with a closing tag are defined in the config. + tagError = true; } - return "keyword"; + return (tagError ? "error " : "") + "keyword"; // Not a tag-keyword; it's an implicit print tag. } else if (stream.eat('{')) { @@ -382,8 +428,8 @@ CodeMirror.registerHelper("wordChars", "soy", /[\w$]/); - CodeMirror.registerHelper("hintWords", "soy", indentingTags.concat( - ["delpackage", "namespace", "alias", "print", "css", "debugger"])); + CodeMirror.registerHelper("hintWords", "soy", Object.keys(tags).concat( + ["css", "debugger"])); CodeMirror.defineMIME("text/x-soy", "soy"); }); diff --git a/mode/soy/test.js b/mode/soy/test.js index f057f4feff..6c1fd8d630 100644 --- a/mode/soy/test.js +++ b/mode/soy/test.js @@ -161,4 +161,44 @@ MT('highlight-command-at-eol', '[keyword {msg]', ' [keyword }]'); + + MT('switch-indent-test', + '[keyword {let] [def $marbles]: [atom 5] [keyword /}]', + '[keyword {switch] [variable-2 $marbles][keyword }]', + ' [keyword {case] [atom 0][keyword }]', + ' No marbles', + ' [keyword {default}]', + ' At least 1 marble', + '[keyword {/switch}]', + ''); + + MT('if-elseif-else-indent', + '[keyword {if] [atom true][keyword }]', + ' [keyword {let] [def $a]: [atom 5] [keyword /}]', + '[keyword {elseif] [atom false][keyword }]', + ' [keyword {let] [def $bar]: [atom 5] [keyword /}]', + '[keyword {else}]', + ' [keyword {let] [def $bar]: [atom 5] [keyword /}]', + '[keyword {/if}]'); + + MT('msg-fallbackmsg-indent', + '[keyword {msg] [attribute desc]=[string "A message"][keyword }]', + ' A message', + '[keyword {fallbackmsg] [attribute desc]=[string "A message"][keyword }]', + ' Old message', + '[keyword {/msg}]'); + + MT('special-chars', + '[keyword {sp}]', + '[keyword {nil}]', + '[keyword {\\r}]', + '[keyword {\\n}]', + '[keyword {\\t}]', + '[keyword {lb}]', + '[keyword {rb}]'); + + MT('wrong-closing-tag', + '[keyword {if] [atom true][keyword }]', + ' Optional', + '[keyword&error {/badend][keyword }]'); })(); From a52f1bb9b9d4273b76e6944e549e214bc6b923db Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 6 May 2019 19:02:30 +0400 Subject: [PATCH 23/38] [vim] fix repeat for changes made with C-v I --- keymap/vim.js | 25 ++++++++++--------------- test/vim_test.js | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 5758823867..57117d14ae 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1714,6 +1714,7 @@ vim.lastEditActionCommand = actionCommand; macroModeState.lastInsertModeChanges.changes = []; macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; + macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0; } }; @@ -2092,7 +2093,6 @@ change: function(cm, args, ranges) { var finalHead, text; var vim = cm.state.vim; - vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock; if (!vim.visualMode) { var anchor = ranges[0].anchor, head = ranges[0].head; @@ -2353,6 +2353,8 @@ } else if (insertAt == 'firstNonBlank') { head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head); } else if (insertAt == 'startOfSelectedArea') { + if (!vim.visualMode) + return; if (!vim.visualBlock) { if (sel.head.line < sel.anchor.line) { head = sel.head; @@ -2366,6 +2368,8 @@ height = Math.abs(sel.head.line - sel.anchor.line) + 1; } } else if (insertAt == 'endOfSelectedArea') { + if (!vim.visualMode) + return; if (!vim.visualBlock) { if (sel.head.line >= sel.anchor.line) { head = offsetCursor(sel.head, 0, 1); @@ -2811,12 +2815,6 @@ } return Pos(cur.line + offsetLine, cur.ch + offsetCh); } - function getOffset(anchor, head) { - return { - line: head.line - anchor.line, - ch: head.line - anchor.line - }; - } function commandMatches(keys, keyMap, context, inputState) { // Partial matches are not applied. They inform the key handler // that the current key sequence is a subsequence of a valid key @@ -5436,18 +5434,15 @@ return true; } var head = cm.getCursor('head'); - var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock; - if (inVisualBlock) { + var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock; + if (visualBlock) { // Set up block selection again for repeating the changes. - var vim = cm.state.vim; - var lastSel = vim.lastSelection; - var offset = getOffset(lastSel.anchor, lastSel.head); - selectForInsert(cm, head, offset.line + 1); + selectForInsert(cm, head, visualBlock + 1); repeat = cm.listSelections().length; cm.setCursor(head); } for (var i = 0; i < repeat; i++) { - if (inVisualBlock) { + if (visualBlock) { cm.setCursor(offsetCursor(head, i, 0)); } for (var j = 0; j < changes.length; j++) { @@ -5464,7 +5459,7 @@ } } } - if (inVisualBlock) { + if (visualBlock) { cm.setCursor(offsetCursor(head, 0, 1)); } } diff --git a/test/vim_test.js b/test/vim_test.js index f38efd0245..5d8740e10a 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1075,6 +1075,28 @@ testVim('c_visual_block_replay', function(cm, vim, helpers) { helpers.doKeys('.'); eq('foo4\nfoo8\nfoodefg', cm.getValue()); }, {value: '1234\n5678\nabcdefg'}); +testVim('I_visual_block_replay', function(cm, vim, helpers) { + cm.setCursor(0, 2); + helpers.doKeys('', '2', 'j', 'l', 'I'); + var replacement = fillArray('+-', 3); + cm.replaceSelections(replacement); + eq('12+-34\n56+-78\nab+-cdefg\nxyz', cm.getValue()); + helpers.doKeys(''); + // ensure that repeat location doesn't depend on last selection + cm.setCursor(3, 2); + helpers.doKeys('g', 'v') + eq("+-34\n+-78\n+-cd", cm.getSelection()) + cm.setCursor(0, 3); + helpers.doKeys('', '1', 'j', '2', 'l'); + eq("-34\n-78", cm.getSelection()); + cm.setCursor(0, 0); + eq("", cm.getSelection()); + helpers.doKeys('g', 'v'); + eq("-34\n-78", cm.getSelection()); + cm.setCursor(1, 1); + helpers.doKeys('.'); + eq('12+-34\n5+-6+-78\na+-b+-cdefg\nx+-yz', cm.getValue()); +}, {value: '1234\n5678\nabcdefg\nxyz'}); testVim('d_visual_block', function(cm, vim, helpers) { cm.setCursor(0, 1); From 65a782b93b1e2897357bbe216a42473e1d8c19c5 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 6 May 2019 19:07:45 +0400 Subject: [PATCH 24/38] [vim] add support for ` text object --- keymap/vim.js | 2 +- test/vim_test.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 57117d14ae..4844b123d3 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2005,7 +2005,7 @@ '{': '}', '}': '{', '[': ']', ']': '[', '<': '>', '>': '<'}; - var selfPaired = {'\'': true, '"': true}; + var selfPaired = {'\'': true, '"': true, '`': true}; var character = motionArgs.selectedCharacter; // 'b' refers to '()' block. diff --git a/test/vim_test.js b/test/vim_test.js index 5d8740e10a..6361e62f45 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1316,6 +1316,10 @@ testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz'); testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz'); testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz'); +testEdit('di`', 'foo `bAr` baz', /`/, 'di`', 'foo `` baz'); +testEdit('di>', 'foo baz', /', 'foo <> baz'); +testEdit('da<', 'foo baz', / Date: Mon, 6 May 2019 19:23:47 +0400 Subject: [PATCH 25/38] [vim] do not reset desired column at eof --- keymap/vim.js | 10 ++++++---- test/vim_test.js | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 4844b123d3..0416c74725 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1845,7 +1845,7 @@ if (line < first && cur.line == first){ return this.moveToStartOfLine(cm, head, motionArgs, vim); }else if (line > last && cur.line == last){ - return this.moveToEol(cm, head, motionArgs, vim); + return this.moveToEol(cm, head, motionArgs, vim, true); } if (motionArgs.toFirstChar){ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); @@ -1947,13 +1947,15 @@ vim.lastHSPos = cm.charCoords(head,'div').left; return moveToColumn(cm, repeat); }, - moveToEol: function(cm, head, motionArgs, vim) { + moveToEol: function(cm, head, motionArgs, vim, keepHPos) { var cur = head; - vim.lastHPos = Infinity; var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity); var end=cm.clipPos(retval); end.ch--; - vim.lastHSPos = cm.charCoords(end,'div').left; + if (!keepHPos) { + vim.lastHPos = Infinity; + vim.lastHSPos = cm.charCoords(end,'div').left; + } return retval; }, moveToFirstNonWhiteSpaceCharacter: function(cm, head) { diff --git a/test/vim_test.js b/test/vim_test.js index 6361e62f45..401f5dcaff 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -323,6 +323,8 @@ testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end); testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4)); testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4)); testMotion('w', 'w', word1.start); +testMotion('keepHPos', ['5', 'j', 'j', '7', 'k'], makeCursor(8, 12), makeCursor(12, 12)); +testMotion('keepHPosEol', ['$', '2', 'j'], makeCursor(2, 18)); testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2)); testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51)); testMotion('w_repeat', ['2', 'w'], word2.start); From 908d8399b893ec6dc89cb33abea7e3f8671d9ef8 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 6 May 2019 19:58:22 +0400 Subject: [PATCH 26/38] [vim] cleanup old fatCursorMarks after C-v c Esc --- keymap/vim.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 0416c74725..e51541ee6d 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -286,7 +286,8 @@ enterVimMode(cm); } - function fatCursorMarks(cm) { + function updateFatCursorMark(cm) { + clearFatCursorMark(cm); var ranges = cm.listSelections(), result = [] for (var i = 0; i < ranges.length; i++) { var range = ranges[i] @@ -302,25 +303,23 @@ } } } - return result + cm.state.fatCursorMarks = result; } - function updateFatCursorMark(cm) { - var marks = cm.state.fatCursorMarks - if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear() - cm.state.fatCursorMarks = fatCursorMarks(cm) + function clearFatCursorMark(cm) { + var marks = cm.state.fatCursorMarks; + if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear(); + cm.state.fatCursorMarks = null; } function enableFatCursorMark(cm) { - cm.state.fatCursorMarks = fatCursorMarks(cm) + updateFatCursorMark(cm) cm.on("cursorActivity", updateFatCursorMark) } function disableFatCursorMark(cm) { - var marks = cm.state.fatCursorMarks - if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear() - cm.state.fatCursorMarks = null - cm.off("cursorActivity", updateFatCursorMark) + clearFatCursorMark(cm); + cm.off("cursorActivity", updateFatCursorMark); } // Deprecated, simply setting the keymap works again. From 8a32a4db1b096c17a8382917e920ea7941d95784 Mon Sep 17 00:00:00 2001 From: Evan Minsk Date: Mon, 6 May 2019 23:12:16 -0700 Subject: [PATCH 27/38] [vim] fix @@ to rerun last run macro (#5868) `vimGlobalState.macroModeState.latestRegister` was only ever updated when defining a macro. This change updates it when running a macro too so that `@@` actually repeats the last macro as it does in vim --- keymap/vim.js | 2 ++ test/vim_test.js | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/keymap/vim.js b/keymap/vim.js index e51541ee6d..8a038805a4 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2316,6 +2316,8 @@ var macroModeState = vimGlobalState.macroModeState; if (registerName == '@') { registerName = macroModeState.latestRegister; + } else { + macroModeState.latestRegister = registerName; } while(repeat--){ executeMacroRegister(cm, vim, macroModeState, registerName); diff --git a/test/vim_test.js b/test/vim_test.js index 401f5dcaff..727daaedc0 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -2672,6 +2672,15 @@ testVim('macro_last_ex_command_register', function (cm, vim, helpers) { eq('bbbaa', cm.getValue()); helpers.assertCursorAt(0, 2); }, { value: 'aaaaa'}); +testVim('macro_last_run_macro', function (cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'C', 'a', '', 'q'); + helpers.doKeys('q', 'b', 'C', 'b', '', 'q'); + helpers.doKeys('@', 'a'); + helpers.doKeys('d', 'd'); + helpers.doKeys('@', '@'); + eq('a', cm.getValue()); +}, { value: ''}); testVim('macro_parens', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('q', 'z', 'i'); From 83c83d19360b0be82efe1fbe360808528fa16e25 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 7 May 2019 08:14:58 +0200 Subject: [PATCH 28/38] [midnight theme] Fix class name for cm-matchhighlight Issue #5866 --- theme/midnight.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme/midnight.css b/theme/midnight.css index 17ed39c8bc..1de827eba7 100644 --- a/theme/midnight.css +++ b/theme/midnight.css @@ -1,8 +1,8 @@ /* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ /**/ -.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949; } -.cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; } +.cm-s-midnight span.cm-matchhighlight { background: #494949; } +.cm-s-midnight.CodeMirror-focused span.cm-matchhighlight { background: #314D67 !important; } /**/ .cm-s-midnight .CodeMirror-activeline-background { background: #253540; } From ff40991e6bad642233f7e9941cc63c697dde1e46 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 8 May 2019 10:57:13 +0200 Subject: [PATCH 29/38] [ruby mode] Require dash or tilde at start of heredoc strings Closes #5871 --- mode/ruby/ruby.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index 5d9288666f..b84dde29e3 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -63,7 +63,7 @@ CodeMirror.defineMode("ruby", function(config) { } else if (ch == "#") { stream.skipToEnd(); return "comment"; - } else if (ch == "<" && (m = stream.match(/^<(-)?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { + } else if (ch == "<" && (m = stream.match(/^<([-~])[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { return chain(readHereDoc(m[2], m[1]), stream, state); } else if (ch == "0") { if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); From 42f919f09c1865fe5313a6bd8a1ed928630f31bf Mon Sep 17 00:00:00 2001 From: Sasha Varlamov Date: Tue, 7 May 2019 10:45:11 -0700 Subject: [PATCH 30/38] Add EXLskills Live Coding Interviews as real-world use case --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index b9fad1c601..ae7ff4dad6 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -76,6 +76,7 @@

    CodeMirror real-world uses

  • Eloquent JavaScript (book)
  • Emmet (fast XML editing)
  • Espruino Web IDE (Chrome App for writing code on Espruino devices)
  • +
  • EXLskills Live Interivews
  • Fastfig (online computation/math tool)
  • Farabi (modern Perl IDE)
  • FathomJS integration (slides with editors, again)
  • From dab6f676107c10ba8d16c654a42f66cae3f27db1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 8 May 2019 10:59:18 +0200 Subject: [PATCH 31/38] [midnight theme] Drop custom matchhighlight styles Issue #5866 --- theme/midnight.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/theme/midnight.css b/theme/midnight.css index 1de827eba7..fc26474a4a 100644 --- a/theme/midnight.css +++ b/theme/midnight.css @@ -1,9 +1,5 @@ /* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ -/**/ -.cm-s-midnight span.cm-matchhighlight { background: #494949; } -.cm-s-midnight.CodeMirror-focused span.cm-matchhighlight { background: #314D67 !important; } - /**/ .cm-s-midnight .CodeMirror-activeline-background { background: #253540; } From 09eeedcf1b9a11e2f88d4dd7fc839014cca660ff Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 May 2019 03:20:18 +0400 Subject: [PATCH 32/38] [vim] remove unnecessary uses of replace from vim test --- test/vim_test.js | 81 ++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/test/vim_test.js b/test/vim_test.js index 727daaedc0..15c2c81eb9 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1056,21 +1056,18 @@ function fillArray(val, times) { testVim('c_visual_block', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 'c'); - var replacement = fillArray('hello', 3); - cm.replaceSelections(replacement); + helpers.doKeys('hello'); eq('1hello\n5hello\nahellofg', cm.getValue()); helpers.doKeys(''); cm.setCursor(2, 3); helpers.doKeys('', '2', 'k', 'h', 'C'); - replacement = fillArray('world', 3); - cm.replaceSelections(replacement); + helpers.doKeys('world'); eq('1hworld\n5hworld\nahworld', cm.getValue()); }, {value: '1234\n5678\nabcdefg'}); testVim('c_visual_block_replay', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('', '2', 'j', 'l', 'c'); - var replacement = fillArray('fo', 3); - cm.replaceSelections(replacement); + helpers.doKeys('fo'); eq('1fo4\n5fo8\nafodefg', cm.getValue()); helpers.doKeys(''); cm.setCursor(0, 0); @@ -1080,8 +1077,7 @@ testVim('c_visual_block_replay', function(cm, vim, helpers) { testVim('I_visual_block_replay', function(cm, vim, helpers) { cm.setCursor(0, 2); helpers.doKeys('', '2', 'j', 'l', 'I'); - var replacement = fillArray('+-', 3); - cm.replaceSelections(replacement); + helpers.doKeys('+-') eq('12+-34\n56+-78\nab+-cdefg\nxyz', cm.getValue()); helpers.doKeys(''); // ensure that repeat location doesn't depend on last selection @@ -1114,14 +1110,12 @@ testVim('D_visual_block', function(cm, vim, helpers) { testVim('s_visual_block', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 's'); - var replacement = fillArray('hello{', 3); - cm.replaceSelections(replacement); + helpers.doKeys('hello{'); eq('1hello{\n5hello{\nahello{fg\n', cm.getValue()); helpers.doKeys(''); cm.setCursor(2, 3); helpers.doKeys('', '1', 'k', 'h', 'S'); - replacement = fillArray('world', 1); - cm.replaceSelections(replacement); + helpers.doKeys('world'); eq('1hello{\n world\n', cm.getValue()); }, {value: '1234\n5678\nabcdefg\n'}); @@ -1511,7 +1505,7 @@ testVim('i', function(cm, vim, helpers) { }); testVim('i_repeat', function(cm, vim, helpers) { helpers.doKeys('3', 'i'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test') helpers.doKeys(''); eq('testtesttest', cm.getValue()); helpers.assertCursorAt(0, 11); @@ -1519,7 +1513,7 @@ testVim('i_repeat', function(cm, vim, helpers) { testVim('i_repeat_delete', function(cm, vim, helpers) { cm.setCursor(0, 4); helpers.doKeys('2', 'i'); - cm.replaceRange('z', cm.getCursor()); + helpers.doKeys('z') helpers.doInsertModeKeys('Backspace', 'Backspace'); helpers.doKeys(''); eq('abe', cm.getValue()); @@ -1584,9 +1578,7 @@ testVim('A', function(cm, vim, helpers) { testVim('A_visual_block', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('', '2', 'j', 'l', 'l', 'A'); - var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); - replacement.pop(); - cm.replaceSelections(replacement); + helpers.doKeys('hello'); eq('testhello\nmehello\npleahellose', cm.getValue()); helpers.doKeys(''); cm.setCursor(0, 0); @@ -1603,7 +1595,7 @@ testVim('I', function(cm, vim, helpers) { testVim('I_repeat', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('3', 'I'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test') helpers.doKeys(''); eq('testtesttestblah', cm.getValue()); helpers.assertCursorAt(0, 11); @@ -1611,9 +1603,7 @@ testVim('I_repeat', function(cm, vim, helpers) { testVim('I_visual_block', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('', '2', 'j', 'l', 'l', 'I'); - var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); - replacement.pop(); - cm.replaceSelections(replacement); + helpers.doKeys('hello'); eq('hellotest\nhellome\nhelloplease', cm.getValue()); }, {value: 'test\nme\nplease'}); testVim('o', function(cm, vim, helpers) { @@ -1626,7 +1616,7 @@ testVim('o', function(cm, vim, helpers) { testVim('o_repeat', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('3', 'o'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test') helpers.doKeys(''); eq('\ntest\ntest\ntest', cm.getValue()); helpers.assertCursorAt(3, 3); @@ -1750,8 +1740,8 @@ testVim('r_visual_block', function(cm, vim, helpers) { eq('1 l\n5 l\nalllefg', cm.getValue()); cm.setCursor(2, 0); helpers.doKeys('o'); + helpers.doKeys('\t\t') helpers.doKeys(''); - cm.replaceRange('\t\t', cm.getCursor()); helpers.doKeys('', 'h', 'h', 'r', 'r'); eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue()); }, {value: '1234\n5678\nabcdefg'}); @@ -2591,7 +2581,7 @@ testVim('g#', function(cm, vim, helpers) { testVim('macro_insert', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('q', 'a', '0', 'i'); - cm.replaceRange('foo', cm.getCursor()); + helpers.doKeys('foo') helpers.doKeys(''); helpers.doKeys('q', '@', 'a'); eq('foofoo', cm.getValue()); @@ -2599,14 +2589,14 @@ testVim('macro_insert', function(cm, vim, helpers) { testVim('macro_insert_repeat', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('q', 'a', '$', 'a'); - cm.replaceRange('larry.', cm.getCursor()); + helpers.doKeys('larry.') helpers.doKeys(''); helpers.doKeys('a'); - cm.replaceRange('curly.', cm.getCursor()); + helpers.doKeys('curly.') helpers.doKeys(''); helpers.doKeys('q'); helpers.doKeys('a'); - cm.replaceRange('moe.', cm.getCursor()); + helpers.doKeys('moe.') helpers.doKeys(''); helpers.doKeys('@', 'a'); // At this point, the most recent edit should be the 2nd insert change @@ -2684,10 +2674,10 @@ testVim('macro_last_run_macro', function (cm, vim, helpers) { testVim('macro_parens', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('q', 'z', 'i'); - cm.replaceRange('(', cm.getCursor()); + helpers.doKeys('(') helpers.doKeys(''); helpers.doKeys('e', 'a'); - cm.replaceRange(')', cm.getCursor()); + helpers.doKeys(')') helpers.doKeys(''); helpers.doKeys('q'); helpers.doKeys('w', '@', 'z'); @@ -2697,13 +2687,13 @@ testVim('macro_parens', function(cm, vim, helpers) { testVim('macro_overwrite', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('q', 'z', '0', 'i'); - cm.replaceRange('I ', cm.getCursor()); + helpers.doKeys('I ') helpers.doKeys(''); helpers.doKeys('q'); helpers.doKeys('e'); // Now replace the macro with something else. helpers.doKeys('q', 'z', 'a'); - cm.replaceRange('.', cm.getCursor()); + helpers.doKeys('.') helpers.doKeys(''); helpers.doKeys('q'); helpers.doKeys('e', '@', 'z'); @@ -2802,11 +2792,11 @@ testVim('yank_append_word_to_line_register', function(cm, vim, helpers) { testVim('macro_register', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('q', 'a', 'i'); - cm.replaceRange('gangnam', cm.getCursor()); + helpers.doKeys('gangnam') helpers.doKeys(''); helpers.doKeys('q'); helpers.doKeys('q', 'b', 'o'); - cm.replaceRange('style', cm.getCursor()); + helpers.doKeys('style') helpers.doKeys(''); helpers.doKeys('q'); cm.openDialog = helpers.fakeOpenDialog('registers'); @@ -2819,7 +2809,7 @@ testVim('macro_register', function(cm, vim, helpers) { testVim('._register', function(cm,vim,helpers) { cm.setCursor(0,0); helpers.doKeys('i'); - cm.replaceRange('foo',cm.getCursor()); + helpers.doKeys('foo') helpers.doKeys(''); cm.openDialog = helpers.fakeOpenDialog('registers'); cm.openNotification = helpers.fakeOpenNotification(function(text) { @@ -3008,13 +2998,13 @@ testVim('._repeat', function(cm, vim, helpers) { }, { value: '1 2 3 4 5 6'}); testVim('._insert', function(cm, vim, helpers) { helpers.doKeys('i'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test') helpers.doKeys(''); helpers.doKeys('.'); eq('testestt', cm.getValue()); helpers.assertCursorAt(0, 6); helpers.doKeys('O'); - cm.replaceRange('xyz', cm.getCursor()); + helpers.doKeys('xyz') helpers.doInsertModeKeys('Backspace'); helpers.doInsertModeKeys('Down'); helpers.doKeys(''); @@ -3024,7 +3014,7 @@ testVim('._insert', function(cm, vim, helpers) { }, { value: ''}); testVim('._insert_repeat', function(cm, vim, helpers) { helpers.doKeys('i'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test') cm.setCursor(0, 4); helpers.doKeys(''); helpers.doKeys('2', '.'); @@ -3033,7 +3023,7 @@ testVim('._insert_repeat', function(cm, vim, helpers) { }, { value: ''}); testVim('._repeat_insert', function(cm, vim, helpers) { helpers.doKeys('3', 'i'); - cm.replaceRange('te', cm.getCursor()); + helpers.doKeys('te') cm.setCursor(0, 2); helpers.doKeys(''); helpers.doKeys('.'); @@ -3042,7 +3032,7 @@ testVim('._repeat_insert', function(cm, vim, helpers) { }, { value: ''}); testVim('._insert_o', function(cm, vim, helpers) { helpers.doKeys('o'); - cm.replaceRange('z', cm.getCursor()); + helpers.doKeys('z') cm.setCursor(1, 1); helpers.doKeys(''); helpers.doKeys('.'); @@ -3051,7 +3041,7 @@ testVim('._insert_o', function(cm, vim, helpers) { }, { value: ''}); testVim('._insert_o_repeat', function(cm, vim, helpers) { helpers.doKeys('o'); - cm.replaceRange('z', cm.getCursor()); + helpers.doKeys('z') helpers.doKeys(''); cm.setCursor(1, 0); helpers.doKeys('2', '.'); @@ -3060,7 +3050,7 @@ testVim('._insert_o_repeat', function(cm, vim, helpers) { }, { value: ''}); testVim('._insert_o_indent', function(cm, vim, helpers) { helpers.doKeys('o'); - cm.replaceRange('z', cm.getCursor()); + helpers.doKeys('z') helpers.doKeys(''); cm.setCursor(1, 2); helpers.doKeys('.'); @@ -3069,7 +3059,7 @@ testVim('._insert_o_indent', function(cm, vim, helpers) { }, { value: '{'}); testVim('._insert_cw', function(cm, vim, helpers) { helpers.doKeys('c', 'w'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test') helpers.doKeys(''); cm.setCursor(0, 3); helpers.doKeys('2', 'l'); @@ -3081,7 +3071,7 @@ testVim('._insert_cw_repeat', function(cm, vim, helpers) { // For some reason, repeat cw in desktop VIM will does not repeat insert mode // changes. Will conform to that behavior. helpers.doKeys('c', 'w'); - cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('test'); helpers.doKeys(''); cm.setCursor(0, 4); helpers.doKeys('l'); @@ -4350,10 +4340,7 @@ testVim('ex_imap', function(cm, vim, helpers) { cm.setCursor(0, 1); CodeMirror.Vim.map('jj', '', 'insert'); helpers.doKeys('', '2', 'j', 'l', 'c'); - var replacement = fillArray('f', 3); - cm.replaceSelections(replacement); - var replacement = fillArray('o', 3); - cm.replaceSelections(replacement); + helpers.doKeys('f', 'o'); eq('1fo4\n5fo8\nafodefg', cm.getValue()); helpers.doKeys('j', 'j'); cm.setCursor(0, 0); From 6974e1a4717b261d02e7db5f55f2b736a8112b06 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 May 2019 03:22:10 +0400 Subject: [PATCH 33/38] [vim] fix broken option tests --- test/vim_test.js | 65 ++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/test/vim_test.js b/test/vim_test.js index 15c2c81eb9..9069bb9e68 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -105,6 +105,15 @@ function forEach(arr, func) { } } +function expectFail(fn) { + try { + fn(); + } catch(expected) { + return; + }; + throw new Error("Expected to throw an error"); +} + function testVim(name, run, opts, expectedFail) { var vimOpts = { lineNumbers: true, @@ -4068,11 +4077,9 @@ testVim('set_boolean', function(cm, vim, helpers) { CodeMirror.Vim.defineOption('testoption', true, 'boolean'); // Test default value is set. is(CodeMirror.Vim.getOption('testoption')); - try { - // Test fail to set to non-boolean - CodeMirror.Vim.setOption('testoption', '5'); - fail(); - } catch (expected) {} + // Test fail to set to non-boolean + var result = CodeMirror.Vim.setOption('testoption', '5'); + is(result instanceof Error); // Test setOption CodeMirror.Vim.setOption('testoption', false); is(!CodeMirror.Vim.getOption('testoption')); @@ -4081,11 +4088,10 @@ testVim('ex_set_boolean', function(cm, vim, helpers) { CodeMirror.Vim.defineOption('testoption', true, 'boolean'); // Test default value is set. is(CodeMirror.Vim.getOption('testoption')); - try { - // Test fail to set to non-boolean - helpers.doEx('set testoption=22'); - fail(); - } catch (expected) {} + is(!cm.state.currentNotificationClose); + // Test fail to set to non-boolean + helpers.doEx('set testoption=22'); + is(cm.state.currentNotificationClose); // Test setOption helpers.doEx('set notestoption'); is(!CodeMirror.Vim.getOption('testoption')); @@ -4094,16 +4100,12 @@ testVim('set_string', function(cm, vim, helpers) { CodeMirror.Vim.defineOption('testoption', 'a', 'string'); // Test default value is set. eq('a', CodeMirror.Vim.getOption('testoption')); - try { - // Test fail to set non-string. - CodeMirror.Vim.setOption('testoption', true); - fail(); - } catch (expected) {} - try { - // Test fail to set 'notestoption' - CodeMirror.Vim.setOption('notestoption', 'b'); - fail(); - } catch (expected) {} + // Test no fail to set non-string. + var result = CodeMirror.Vim.setOption('testoption', true); + is(!result); + // Test fail to set 'notestoption' + result = CodeMirror.Vim.setOption('notestoption', 'b'); + is(result instanceof Error); // Test setOption CodeMirror.Vim.setOption('testoption', 'c'); eq('c', CodeMirror.Vim.getOption('testoption')); @@ -4112,11 +4114,10 @@ testVim('ex_set_string', function(cm, vim, helpers) { CodeMirror.Vim.defineOption('testopt', 'a', 'string'); // Test default value is set. eq('a', CodeMirror.Vim.getOption('testopt')); - try { - // Test fail to set 'notestopt' - helpers.doEx('set notestopt=b'); - fail(); - } catch (expected) {} + // Test fail to set 'notestopt' + is(!cm.state.currentNotificationClose); + helpers.doEx('set notestopt=b'); + is(cm.state.currentNotificationClose); // Test setOption helpers.doEx('set testopt=c') eq('c', CodeMirror.Vim.getOption('testopt')); @@ -4162,11 +4163,10 @@ testVim('ex_set_callback', function(cm, vim, helpers) { CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb); // Test default value is set. eq('a', CodeMirror.Vim.getOption('testopt')); - try { - // Test fail to set 'notestopt' - helpers.doEx('set notestopt=b'); - fail(); - } catch (expected) {} + // Test fail to set 'notestopt' + is(!cm.state.currentNotificationClose); + helpers.doEx('set notestopt=b'); + is(cm.state.currentNotificationClose); // Test setOption (Identical to the string tests, but via callback instead) helpers.doEx('set testopt=c') eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global @@ -4256,10 +4256,9 @@ testVim('ex_unmap_key2key', function(cm, vim, helpers) { CodeMirror.Vim.mapclear(); }, { value: 'abc' }); testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) { - try { + expectFail(function() { helpers.doEx('unmap a'); - fail(); - } catch (expected) {} + }); helpers.doKeys('a'); eq('vim-insert', cm.getOption('keyMap')); CodeMirror.Vim.mapclear(); From 1b3c322c434a61bd326d2b25722597138e649521 Mon Sep 17 00:00:00 2001 From: nightwing Date: Thu, 9 May 2019 03:32:49 +0400 Subject: [PATCH 34/38] [vim] fix fat cursor staying after O --- keymap/vim.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 8a038805a4..988c1681e5 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -287,6 +287,7 @@ } function updateFatCursorMark(cm) { + if (!cm.state.fatCursorMarks) return; clearFatCursorMark(cm); var ranges = cm.listSelections(), result = [] for (var i = 0; i < ranges.length; i++) { @@ -309,10 +310,10 @@ function clearFatCursorMark(cm) { var marks = cm.state.fatCursorMarks; if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear(); - cm.state.fatCursorMarks = null; } function enableFatCursorMark(cm) { + cm.state.fatCursorMarks = []; updateFatCursorMark(cm) cm.on("cursorActivity", updateFatCursorMark) } @@ -320,6 +321,9 @@ function disableFatCursorMark(cm) { clearFatCursorMark(cm); cm.off("cursorActivity", updateFatCursorMark); + // explicitly set fatCursorMarks to null because event listener above + // can be invoke after removing it, if off is called from operation + cm.state.fatCursorMarks = null; } // Deprecated, simply setting the keymap works again. From a86e85864c7d8da4cc4d103183de6d5c9a2d8707 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 14 May 2019 14:18:39 +0400 Subject: [PATCH 35/38] [vim] fix blockwise yank --- keymap/vim.js | 18 +++++++++++------- test/vim_test.js | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 988c1681e5..f8ffb88f40 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2570,7 +2570,17 @@ } var linewise = register.linewise; var blockwise = register.blockwise; - if (linewise) { + if (blockwise) { + text = text.split('\n'); + if (linewise) { + text.pop(); + } + for (var i = 0; i < text.length; i++) { + text[i] = (text[i] == '') ? ' ' : text[i]; + } + cur.ch += actionArgs.after ? 1 : 0; + cur.ch = Math.min(lineLength(cm, cur.line), cur.ch); + } else if (linewise) { if(vim.visualMode) { text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; } else if (actionArgs.after) { @@ -2582,12 +2592,6 @@ cur.ch = 0; } } else { - if (blockwise) { - text = text.split('\n'); - for (var i = 0; i < text.length; i++) { - text[i] = (text[i] == '') ? ' ' : text[i]; - } - } cur.ch += actionArgs.after ? 1 : 0; } var curPosFinal; diff --git a/test/vim_test.js b/test/vim_test.js index 9069bb9e68..e74b2f2cad 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1428,6 +1428,21 @@ testVim('Y', function(cm, vim, helpers) { is(register.linewise); helpers.assertCursorAt(0, 3); }, { value: ' word1\nword2\n word3' }); +testVim('Yy_blockwise', function(cm, vim, helpers) { + helpers.doKeys('', 'j', '2', 'l', 'Y'); + helpers.doKeys('G', 'p', 'g', 'g'); + helpers.doKeys('', 'j', '2', 'l', 'y'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('$', 'p'); + eq('123456123\n123456123\n123456\n123456', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('123\n123', register.toString()); + is(register.blockwise); + helpers.assertCursorAt(0, 6); + helpers.doKeys('$', 'j', 'p'); + helpers.doKeys('$', 'j', 'P'); + eq("123456123\n123456123123\n123456 121233\n123456 123", cm.getValue()); +}, { value: '123456\n123456\n' }); testVim('~', function(cm, vim, helpers) { helpers.doKeys('3', '~'); eq('ABCdefg', cm.getValue()); From 17f48fb56b8c444b8943d9e586110a8a037079bd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 15 May 2019 14:11:09 +0200 Subject: [PATCH 36/38] [ruby mode] Fix matching of closing brackets during indentation Closes #5881 --- mode/ruby/ruby.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index b84dde29e3..dd0e603e51 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -28,7 +28,8 @@ CodeMirror.defineMode("ruby", function(config) { var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "then", "catch", "loop", "proc", "begin"]); var dedentWords = wordObj(["end", "until"]); - var matching = {"[": "]", "{": "}", "(": ")"}; + var opening = {"[": "]", "{": "}", "(": ")"}; + var closing = {"]": "[", "}": "{", ")": "("}; var curPunc; function chain(newtok, stream, state) { @@ -58,7 +59,7 @@ CodeMirror.defineMode("ruby", function(config) { else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } var delim = stream.eat(/[^\w\s=]/); if (!delim) return "operator"; - if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; + if (opening.propertyIsEnumerable(delim)) delim = opening[delim]; return chain(readQuoted(delim, style, embed, true), stream, state); } else if (ch == "#") { stream.skipToEnd(); @@ -280,9 +281,9 @@ CodeMirror.defineMode("ruby", function(config) { if (state.tokenize[state.tokenize.length-1] != tokenBase) return CodeMirror.Pass; var firstChar = textAfter && textAfter.charAt(0); var ct = state.context; - var closing = ct.type == matching[firstChar] || + var closed = ct.type == closing[firstChar] || ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); - return ct.indented + (closing ? 0 : config.indentUnit) + + return ct.indented + (closed ? 0 : config.indentUnit) + (state.continuedLine ? config.indentUnit : 0); }, From 3094f2c86a758eb5ef6570cfaa31ca44a6ff6243 Mon Sep 17 00:00:00 2001 From: Diego Fernandez Date: Wed, 15 May 2019 17:27:37 -0600 Subject: [PATCH 37/38] [emacs] Add "redo" keybinding in Emacs keymap Since "redo" is generally assigned to Ctrl+Y, we lose this binding when using the Emacs keymap. The Emacs default for "redo" is a bit complicated, but since we're using Ctrl-Z for "undo", it makes sense to have Ctrl+Shift-Z for "redo". --- keymap/emacs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/keymap/emacs.js b/keymap/emacs.js index d96a6fbec9..fe62d44fb1 100644 --- a/keymap/emacs.js +++ b/keymap/emacs.js @@ -369,6 +369,7 @@ "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), + "Shift-Ctrl-Z": "redo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", "Ctrl-S": "findPersistentNext", "Ctrl-R": "findPersistentPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace", "Alt-/": "autocomplete", From a7ce80ffc2f1a04b61782473170de776eb68728c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 21 May 2019 10:15:18 +0200 Subject: [PATCH 38/38] Mark version 5.47.0 --- AUTHORS | 6 ++++++ CHANGELOG.md | 14 ++++++++++++++ doc/manual.html | 2 +- doc/releases.html | 9 +++++++++ index.html | 2 +- package.json | 2 +- src/edit/main.js | 2 +- 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0e4c895d3c..f61c7c5bc0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -197,9 +197,11 @@ David Vázquez David Whittington deebugger Deep Thought +Denis Ovsienko Devin Abbott Devon Carew Dick Choi +Diego Fernandez dignifiedquire Dimage Sapelkin dmaclach @@ -227,6 +229,7 @@ Eric Bogard Erik Demaine Erik Welander eustas +Evan Minsk Fabien Dubosson Fabien O'Carroll Fabio Zendhi Nagao @@ -505,6 +508,7 @@ Maximilian Hils Maxim Kraev Max Kirsch Max Schaefer +Max Wu Max Xiantu mbarkhau McBrainy @@ -580,6 +584,7 @@ Nisarg Jhaveri nlwillia noragrossman Norman Rzepka +Nouzbe Oleksandr Yakovenko opl- Oreoluwa Onatemowo @@ -667,6 +672,7 @@ Sander Verweij santec Sarah McAlear and Wenlin Zhang Sascha Peilicke +Sasha Varlamov satamas satchmorun sathyamoorthi diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e6068f4e..d8c523c7e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 5.47.0 (2019-05-21) + +### Bug fixes + +[python mode](https://codemirror.net/mode/python/): Properly handle `...` syntax. + +[ruby mode](https://codemirror.net/mode/ruby): Fix indenting before closing brackets. + +[vim bindings](https://codemirror.net/demo/vim.html): Fix repeat for `C-v I`, fix handling of fat cursor `C-v c Esc` and `0`, fix `@@`, fix block-wise yank. + +### New features + +[vim bindings](https://codemirror.net/demo/vim.html): Add support for `` ` `` text object. + ## 5.46.0 (2019-04-22) ### Bug fixes diff --git a/doc/manual.html b/doc/manual.html index 37fef13f49..abcb1e29e1 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -69,7 +69,7 @@

    User manual and reference guide - version 5.46.1 + version 5.47.0

    CodeMirror is a code-editor component that can be embedded in diff --git a/doc/releases.html b/doc/releases.html index 6ecaef0e1b..0066778ecf 100644 --- a/doc/releases.html +++ b/doc/releases.html @@ -30,6 +30,15 @@

    Release notes and version history

    Version 5.x

    +

    21-05-2019: Version 5.47.0:

    + +
      +
    • python mode: Properly handle ... syntax.
    • +
    • ruby mode: Fix indenting before closing brackets.
    • +
    • vim bindings: Fix repeat for C-v I, fix handling of fat cursor C-v c Esc and 0, fix @@, fix block-wise yank.
    • +
    • vim bindings: Add support for ` text object.
    • +
    +

    22-04-2019: Version 5.46.0:

      diff --git a/index.html b/index.html index 4e4f16404d..2c68263882 100644 --- a/index.html +++ b/index.html @@ -99,7 +99,7 @@

      This is CodeMirror

    - Get the current version: 5.46.0.
    + Get the current version: 5.47.0.
    You can see the code,
    read the release notes,
    or study the user manual. diff --git a/package.json b/package.json index 69f5d346e3..86aa4861b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version": "5.46.1", + "version": "5.47.0", "main": "lib/codemirror.js", "style": "lib/codemirror.css", "author": { diff --git a/src/edit/main.js b/src/edit/main.js index be98c68218..615372b118 100644 --- a/src/edit/main.js +++ b/src/edit/main.js @@ -66,4 +66,4 @@ import { addLegacyProps } from "./legacy.js" addLegacyProps(CodeMirror) -CodeMirror.version = "5.46.1" +CodeMirror.version = "5.47.0"