From 82752ca5d1e3bac5473de9936d864b58171ab2d8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 20 May 2013 15:55:40 +0200 Subject: [PATCH 001/143] Bump version number post-3.13 --- lib/codemirror.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index b42c321527..732f549084 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5629,7 +5629,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.13"; + CodeMirror.version = "3.13 +"; return CodeMirror; })(); diff --git a/package.json b/package.json index 05c946b726..7e0d64a71c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"3.13.00", + "version":"3.13.01", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", From 51292d0b8d2c632eeeccbd6ff83a88316a83c038 Mon Sep 17 00:00:00 2001 From: lynschinzer Date: Mon, 20 May 2013 14:11:56 +0200 Subject: [PATCH 002/143] [vim keymap] Minor fixup for macro normalMode --- keymap/vim.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 42e6ecaac0..532e640a1f 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -536,9 +536,9 @@ if (macroModeState.enteredMacroMode) { if (key == 'q') { actions.exitMacroRecordMode(); + vim.inputState = new InputState(); return; } - logKey(macroModeState, key); } if (key == '') { // Clear input state and get back to normal mode. @@ -575,6 +575,9 @@ this.handleKey(cm, command.toKeys[i]); } } else { + if (macroModeState.enteredMacroMode) { + logKey(macroModeState, key); + } commandDispatcher.processCommand(cm, vim, command); } } @@ -3251,7 +3254,7 @@ var macroKeyBuffer = macroModeState.macroKeyBuffer; emptyMacroKeyBuffer(macroModeState); do { - match = text.match(/<\w+-.+>|<\w+>|.|\n/); + match = (/<\w+-.+?>|<\w+>|./).exec(text); if(match === null)break; key = match[0]; text = text.substring(match.index + key.length); From f8b2dcfb6e4ae86d6a67a9e5706783a8998fe4f7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 21 May 2013 14:37:15 +0200 Subject: [PATCH 003/143] [package.json] Use 'repository' rather than 'repositories' field Apparently the second is no longer supported in npm 1.2 (?). --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7e0d64a71c..80bd128efc 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,6 @@ "maintainers":[{"name": "Marijn Haverbeke", "email": "marijnh@gmail.com", "web": "http://marijnhaverbeke.nl"}], - "repositories": [{"type": "git", - "url": "http://marijnhaverbeke.nl/git/codemirror"}, - {"type": "git", - "url": "https://github.com/marijnh/CodeMirror.git"}] + "repository": {"type": "git", + "url": "http://marijnhaverbeke.nl/git/codemirror"} } From 3818137291d94bfb168e037309727040d693b756 Mon Sep 17 00:00:00 2001 From: 4r2r Date: Wed, 22 May 2013 19:12:07 +0400 Subject: [PATCH 004/143] CodeMirror.multiplexingMode: added an innerStyle option and test. Example usage: applying different color schemes to submodes. --- addon/mode/multiplex.js | 8 +++++++- addon/mode/multiplex_test.js | 30 ++++++++++++++++++++++++++++++ demo/multiplex.html | 6 ++++++ doc/manual.html | 18 ++++++++++-------- test/index.html | 2 ++ 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 addon/mode/multiplex_test.js diff --git a/addon/mode/multiplex.js b/addon/mode/multiplex.js index 3ff3a929ed..32cc579c3a 100644 --- a/addon/mode/multiplex.js +++ b/addon/mode/multiplex.js @@ -1,5 +1,5 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { - // Others should be {open, close, mode [, delimStyle]} objects + // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects var others = Array.prototype.slice.call(arguments, 1); var n_others = others.length; @@ -58,6 +58,12 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { if (found > -1) stream.string = oldContent; var cur = stream.current(), found = cur.indexOf(curInner.close); if (found > -1) stream.backUp(cur.length - found); + + if (curInner.innerStyle) { + if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle; + else innerToken = curInner.innerStyle; + } + return innerToken; } }, diff --git a/addon/mode/multiplex_test.js b/addon/mode/multiplex_test.js new file mode 100644 index 0000000000..c0656357c7 --- /dev/null +++ b/addon/mode/multiplex_test.js @@ -0,0 +1,30 @@ +(function() { + CodeMirror.defineMode("markdown_with_stex", function(){ + var inner = CodeMirror.getMode({}, "stex"); + var outer = CodeMirror.getMode({}, "markdown"); + + var innerOptions = { + open: '$', + close: '$', + mode: inner, + delimStyle: 'delim', + innerStyle: 'inner' + }; + + return CodeMirror.multiplexingMode(outer, innerOptions); + }); + + var mode = CodeMirror.getMode({}, "markdown_with_stex"); + + function MT(name) { + test.mode( + name, + mode, + Array.prototype.slice.call(arguments, 1), + 'multiplexing'); + } + + MT( + "stexInsideMarkdown", + "[strong **Equation:**] [delim $][inner&tag \\pi][delim $]"); +})(); diff --git a/demo/multiplex.html b/demo/multiplex.html index 9ebe8f357b..ec0519cb98 100644 --- a/demo/multiplex.html +++ b/demo/multiplex.html @@ -56,5 +56,11 @@ the source for more information.

+

+ Parsing/Highlighting Tests: + normal, + verbose. +

+ diff --git a/doc/manual.html b/doc/manual.html index a7d6a67948..5d029aebc1 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -788,7 +788,7 @@

Content manipulation methods

doc.lastLine() → integer
Get the last line of the editor. This will usually be doc.lineCount() - 1, - but for linked sub-views, + but for linked sub-views, it might return other values.
doc.getLineHandle(num: integer) → LineHandle
@@ -1150,7 +1150,7 @@

Text-marking methods

Widget, gutter, and decoration methods

- +
cm.setGutterMarker(line: integer|LineHandle, gutterID: string, value: Element) → LineHandle
Sets the gutter marker for the given gutter (identified by @@ -1160,7 +1160,7 @@

Widget, gutter, and decoration methods

clear the marker, or a DOM element, to set it. The DOM element will be shown in the specified gutter next to the specified line.
- +
cm.clearGutter(gutterID: string)
Remove all gutter markers in the gutter with the given ID.
@@ -1643,7 +1643,7 @@

Add-ons

between several modes. Defines CodeMirror.multiplexingMode which, when given as first argument a mode object, and as other arguments - any number of {open, close, mode [, delimStyle]} + any number of {open, close, mode [, delimStyle, innerStyle]} objects, will return a mode object that starts parsing using the mode passed as first argument, but will switch to another mode as soon as it encounters a string that occurs in one of @@ -1652,9 +1652,11 @@

Add-ons

the close string is encountered. Pass "\n" for open or close if you want to switch on a blank line. - When delimStyle is specified, it will be the token - style returned for the delimiter tokens. The outer mode will not - see the content between the delimiters. +
  • When delimStyle is specified, it will be the token + style returned for the delimiter tokens.
  • +
  • When innerStyle is specified, it will be the token + style added for each inner mode token.
+ The outer mode will not see the content between the delimiters. See this demo for an example. @@ -1699,7 +1701,7 @@

Add-ons

selection/active-line.js
Defines a styleActiveLine option that, when enabled, - gives the wrapper of the active line the class CodeMirror-activeline, + gives the wrapper of the active line the class CodeMirror-activeline, and adds a background with the class CodeMirror-activeline-background. is enabled. See the demo.
diff --git a/test/index.html b/test/index.html index 3eb691576a..4667d589e8 100644 --- a/test/index.html +++ b/test/index.html @@ -8,6 +8,7 @@ + @@ -73,6 +74,7 @@ + -

A plain text/Smarty mode which allows for custom delimiter tags (defaults to { and }).

+
+ +

Smarty 3

+ + + + + + +

A plain text/Smarty version 2 or 3 mode, which allows for custom delimiter tags.

MIME types defined: text/x-smarty

diff --git a/mode/smarty/smarty.js b/mode/smarty/smarty.js index 7d7e62f86e..00c1df5aa8 100644 --- a/mode/smarty/smarty.js +++ b/mode/smarty/smarty.js @@ -1,140 +1,186 @@ +/** + * Smarty 2 and 3 mode. + */ CodeMirror.defineMode("smarty", function(config) { - var keyFuncs = ["debug", "extends", "function", "include", "literal"]; + "use strict"; + + // our default settings; check to see if they're overridden + var settings = { + rightDelimiter: '}', + leftDelimiter: '{', + smartyVersion: 2 // for backward compatibility + }; + if (config.hasOwnProperty("leftDelimiter")) { + settings.leftDelimiter = config.leftDelimiter; + } + if (config.hasOwnProperty("rightDelimiter")) { + settings.rightDelimiter = config.rightDelimiter; + } + if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) { + settings.smartyVersion = 3; + } + + var keyFunctions = ["debug", "extends", "function", "include", "literal"]; var last; var regs = { operatorChars: /[+\-*&%=<>!?]/, - validIdentifier: /[a-zA-Z0-9\_]/, - stringChar: /[\'\"]/ + validIdentifier: /[a-zA-Z0-9_]/, + stringChar: /['"]/ }; - var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{"; - var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}"; - function ret(style, lst) { last = lst; return style; } - - function tokenizer(stream, state) { - function chain(parser) { + var helpers = { + continue: function(style, lastType) { + last = lastType; + return style; + }, + chain: function(stream, state, parser) { state.tokenize = parser; return parser(stream, state); } + }; - if (stream.match(leftDelim, true)) { - if (stream.eat("*")) { - return chain(inBlock("comment", "*" + rightDelim)); - } - else { - state.tokenize = inSmarty; - return "tag"; - } - } - else { - // I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char - stream.next(); - return null; - } - } - function inSmarty(stream, state) { - if (stream.match(rightDelim, true)) { - state.tokenize = tokenizer; - return ret("tag", null); - } + // our various parsers + var parsers = { - var ch = stream.next(); - if (ch == "$") { - stream.eatWhile(regs.validIdentifier); - return ret("variable-2", "variable"); - } - else if (ch == ".") { - return ret("operator", "property"); - } - else if (regs.stringChar.test(ch)) { - state.tokenize = inAttribute(ch); - return ret("string", "string"); - } - else if (regs.operatorChars.test(ch)) { - stream.eatWhile(regs.operatorChars); - return ret("operator", "operator"); - } - else if (ch == "[" || ch == "]") { - return ret("bracket", "bracket"); - } - else if (/\d/.test(ch)) { - stream.eatWhile(/\d/); - return ret("number", "number"); - } - else { - if (state.last == "variable") { - if (ch == "@") { - stream.eatWhile(regs.validIdentifier); - return ret("property", "property"); + // the main tokenizer + tokenizer: function(stream, state) { + if (stream.match(settings.leftDelimiter, true)) { + if (stream.eat("*")) { + return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter)); + } else { + // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode + state.depth++; + var isEol = stream.eol(); + var isFollowedByWhitespace = /\s/.test(stream.peek()); + if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) { + state.depth--; + return null; + } else { + state.tokenize = parsers.smarty; + last = "startTag"; + return "tag"; + } } - else if (ch == "|") { - stream.eatWhile(regs.validIdentifier); - return ret("qualifier", "modifier"); - } - } - else if (state.last == "whitespace") { - stream.eatWhile(regs.validIdentifier); - return ret("attribute", "modifier"); - } - else if (state.last == "property") { - stream.eatWhile(regs.validIdentifier); - return ret("property", null); - } - else if (/\s/.test(ch)) { - last = "whitespace"; + } else { + stream.next(); return null; } + }, - var str = ""; - if (ch != "/") { - str += ch; - } - var c = ""; - while ((c = stream.eat(regs.validIdentifier))) { - str += c; - } - var i, j; - for (i=0, j=keyFuncs.length; i Date: Thu, 23 May 2013 12:06:16 +0200 Subject: [PATCH 010/143] [trailingspace addon] Add --- addon/edit/trailingspace.js | 15 +++++++++++++++ demo/trailingspace.html | 39 +++++++++++++++++++++++++++++++++++++++ doc/manual.html | 15 +++++++++++---- lib/codemirror.js | 3 ++- 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 addon/edit/trailingspace.js create mode 100644 demo/trailingspace.html diff --git a/addon/edit/trailingspace.js b/addon/edit/trailingspace.js new file mode 100644 index 0000000000..f6bb02645d --- /dev/null +++ b/addon/edit/trailingspace.js @@ -0,0 +1,15 @@ +CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) { + if (prev == CodeMirror.Init) prev = false; + if (prev && !val) + cm.removeOverlay("trailingspace"); + else if (!prev && val) + cm.addOverlay({ + token: function(stream) { + for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {} + if (i > stream.pos) { stream.pos = i; return null; } + stream.pos = l; + return "trailingspace"; + }, + name: "trailingspace" + }); +}); diff --git a/demo/trailingspace.html b/demo/trailingspace.html new file mode 100644 index 0000000000..ca74152eca --- /dev/null +++ b/demo/trailingspace.html @@ -0,0 +1,39 @@ + + + + + CodeMirror: Trailing Whitespace Demo + + + + + + + + +

CodeMirror: Trailing Whitespace Demo

+ +
+ + + +

Uses +the trailingspace +addon to highlight trailing whitespace.

+ + + diff --git a/doc/manual.html b/doc/manual.html index 5d029aebc1..33625b577c 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -939,10 +939,10 @@

Configuration methods

override the styling of the base mode entirely, instead of the two being applied together.
cm.removeOverlay(mode: string|object)
-
Pass this the exact argument passed for - the mode parameter - to addOverlay to remove - an overlay again.
+
Pass this the exact value passed for the mode + parameter to addOverlay, + or a string that corresponds to the name propery of + that value, to remove an overlay again.
cm.on(type: string, func: (...args))
Register an event handler for the given event type (a @@ -1558,6 +1558,13 @@

Add-ons

to customize it. Demo here.
+
edit/trailingspace.js
+
Adds an option showTrailingSpace which, when + enabled, adds the CSS class cm-trailingspace to + stretches of whitespace at the end of lines. + The demo has a nice + squiggly underline style for this class.
+
comment/comment.js
Addon for commenting and uncommenting code. Adds three methods to CodeMirror instances: diff --git a/lib/codemirror.js b/lib/codemirror.js index 1b124a432e..a2324e7679 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2780,7 +2780,8 @@ window.CodeMirror = (function() { removeOverlay: operation(null, function(spec) { var overlays = this.state.overlays; for (var i = 0; i < overlays.length; ++i) { - if (overlays[i].modeSpec == spec) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { overlays.splice(i, 1); this.state.modeGen++; regChange(this); From 2daf724ed578726f2e37ef22dc8eebebd44b2a64 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 23 May 2013 13:11:07 +0200 Subject: [PATCH 011/143] [smarty mode] Fix lint errors --- mode/smarty/smarty.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/mode/smarty/smarty.js b/mode/smarty/smarty.js index 00c1df5aa8..856f7bb3be 100644 --- a/mode/smarty/smarty.js +++ b/mode/smarty/smarty.js @@ -29,7 +29,7 @@ CodeMirror.defineMode("smarty", function(config) { }; var helpers = { - continue: function(style, lastType) { + cont: function(style, lastType) { last = lastType; return style; }, @@ -79,47 +79,47 @@ CodeMirror.defineMode("smarty", function(config) { } else { state.tokenize = parsers.tokenizer; } - return helpers.continue("tag", null); + return helpers.cont("tag", null); } if (stream.match(settings.leftDelimiter, true)) { state.depth++; - return helpers.continue("tag", "startTag"); + return helpers.cont("tag", "startTag"); } var ch = stream.next(); if (ch == "$") { stream.eatWhile(regs.validIdentifier); - return helpers.continue("variable-2", "variable"); + return helpers.cont("variable-2", "variable"); } else if (ch == ".") { - return helpers.continue("operator", "property"); + return helpers.cont("operator", "property"); } else if (regs.stringChar.test(ch)) { state.tokenize = parsers.inAttribute(ch); - return helpers.continue("string", "string"); + return helpers.cont("string", "string"); } else if (regs.operatorChars.test(ch)) { stream.eatWhile(regs.operatorChars); - return helpers.continue("operator", "operator"); + return helpers.cont("operator", "operator"); } else if (ch == "[" || ch == "]") { - return helpers.continue("bracket", "bracket"); + return helpers.cont("bracket", "bracket"); } else if (/\d/.test(ch)) { stream.eatWhile(/\d/); - return helpers.continue("number", "number"); + return helpers.cont("number", "number"); } else { if (state.last == "variable") { if (ch == "@") { stream.eatWhile(regs.validIdentifier); - return helpers.continue("property", "property"); + return helpers.cont("property", "property"); } else if (ch == "|") { stream.eatWhile(regs.validIdentifier); - return helpers.continue("qualifier", "modifier"); + return helpers.cont("qualifier", "modifier"); } } else if (state.last == "whitespace") { stream.eatWhile(regs.validIdentifier); - return helpers.continue("attribute", "modifier"); + return helpers.cont("attribute", "modifier"); } if (state.last == "property") { stream.eatWhile(regs.validIdentifier); - return helpers.continue("property", null); + return helpers.cont("property", null); } else if (/\s/.test(ch)) { last = "whitespace"; return null; @@ -129,19 +129,19 @@ CodeMirror.defineMode("smarty", function(config) { if (ch != "/") { str += ch; } - var c = null; + var c = null; while (c = stream.eat(regs.validIdentifier)) { str += c; } for (var i=0, j=keyFunctions.length; i Date: Fri, 24 May 2013 08:31:56 +0200 Subject: [PATCH 012/143] Remove unneeded cursor set at end of drag (The mousemove events should already have taken care of that.) Issue #1488 --- lib/codemirror.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index a2324e7679..f21f76a34e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1728,8 +1728,6 @@ window.CodeMirror = (function() { function done(e) { counter = Infinity; - var cur = posFromMouse(cm, e); - if (cur) doSelect(cur); e_preventDefault(e); focusInput(cm); off(document, "mousemove", move); From 6edd771495c6a333166b7d22a5ebe0e0df47483e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 24 May 2013 09:22:39 +0200 Subject: [PATCH 013/143] Also fire beforeChange for undo/redo changes But disable its update method in that case. Issue #1539 --- doc/manual.html | 27 ++++++++++++++++----------- lib/codemirror.js | 23 +++++++++++++++-------- test/test.js | 13 +++++++++++++ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 33625b577c..6fa58c6e30 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -406,17 +406,22 @@

Events

properties, as with the "change" event, but never a next property, since this is fired for each - individual change, and not batched per operation. It also - has update(from, to, text) - and cancel() methods, which may be used to modify - or cancel the change. All three arguments to update - are optional, and can be left off to leave the existing value - for that field intact. Note: you may not do - anything from a "beforeChange" handler that would - cause changes to the document or its visualization. Doing so - will, since this handler is called directly from the bowels of - the CodeMirror implementation, probably cause the editor to - become corrupted.
+ individual change, and not batched per operation. It also has + a cancel() method, which can be called to cancel + the change, and, if the change isn't coming + from an undo or redo event, an update(from, to, + text) method, which may be used to modify the change. + Undo or redo changes can't be modified, because they hold some + metainformation for restoring old marked ranges that is only + valid for that specific change. All three arguments + to update are optional, and can be left off to + leave the existing value for that field + intact. Note: you may not do anything from + a "beforeChange" handler that would cause changes + to the document or its visualization. Doing so will, since this + handler is called directly from the bowels of the CodeMirror + implementation, probably cause the editor to become + corrupted.
"cursorActivity" (instance: CodeMirror)
Will be fired when the cursor or selection moves, or any diff --git a/lib/codemirror.js b/lib/codemirror.js index f21f76a34e..b92b3a9bbc 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2186,21 +2186,21 @@ window.CodeMirror = (function() { return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; } - function filterChange(doc, change) { + function filterChange(doc, change, update) { var obj = { canceled: false, from: change.from, to: change.to, text: change.text, origin: change.origin, - update: function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }, cancel: function() { this.canceled = true; } }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; signal(doc, "beforeChange", doc, obj); if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); @@ -2217,7 +2217,7 @@ window.CodeMirror = (function() { } if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change); + change = filterChange(doc, change, true); if (!change) return; } @@ -2262,9 +2262,16 @@ window.CodeMirror = (function() { anchorAfter: event.anchorBefore, headAfter: event.headBefore}; (type == "undo" ? hist.undone : hist.done).push(anti); + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + for (var i = event.changes.length - 1; i >= 0; --i) { var change = event.changes[i]; change.origin = type; + if (filter && !filterChange(doc, change, false)) { + (type == "undo" ? hist.done : hist.undone).length = 0; + return; + } + anti.changes.push(historyChangeFromChange(doc, change)); var after = i ? computeSelAfterChange(doc, change, null) diff --git a/test/test.js b/test/test.js index 637f6e79ca..d42a76bd41 100644 --- a/test/test.js +++ b/test/test.js @@ -1358,6 +1358,19 @@ testCM("beforeChange", function(cm) { eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey"); }, {value: "abcdefghijk"}); +testCM("beforeChangeUndo", function(cm) { + cm.setLine(0, "hi"); + cm.setLine(0, "bye"); + eq(cm.historySize().undo, 2); + cm.on("beforeChange", function(cm, change) { + is(!change.update); + change.cancel(); + }); + cm.undo(); + eq(cm.historySize().undo, 0); + eq(cm.getValue(), "bye\ntwo"); +}, {value: "one\ntwo"}); + testCM("beforeSelectionChange", function(cm) { function notAtEnd(cm, pos) { var len = cm.getLine(pos.line).length; From 83fdc1e77cc078029504de549b2eef8e1bb83f12 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 25 May 2013 06:59:11 +0200 Subject: [PATCH 014/143] Fix patch d07c5471ca649b78366b4c10c3ef698504a454a6 The argument order to Delayed.set was wrong, and we actually want to ensure that the resize fires at least every 100ms during a resize, so that the display doesn't lag too much. --- lib/codemirror.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index b92b3a9bbc..aa0de5de43 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1513,14 +1513,15 @@ window.CodeMirror = (function() { // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - var resizeTimer = new Delayed(); + var resizeTimer; function onResize() { - resizeTimer.set(function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; // Might be a text scaling operation, clear size caches. d.cachedCharWidth = d.cachedTextHeight = null; clearCaches(cm); runInOp(cm, bind(regChange, cm)); - }, 200); + }, 100); } on(window, "resize", onResize); // Above handler holds on to the editor and its data structures. From 8e6b9f5e15d16f388fa15f88a930013a87f6c304 Mon Sep 17 00:00:00 2001 From: Ben Keen Date: Fri, 24 May 2013 22:11:59 -0700 Subject: [PATCH 015/143] [smarty mode] bugfixes - escaped quotes in strings now displayed properly - whitespace no longer affects qualifiers - parentheses properly highlighted --- mode/smarty/smarty.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mode/smarty/smarty.js b/mode/smarty/smarty.js index 856f7bb3be..826c2b966c 100644 --- a/mode/smarty/smarty.js +++ b/mode/smarty/smarty.js @@ -91,6 +91,8 @@ CodeMirror.defineMode("smarty", function(config) { if (ch == "$") { stream.eatWhile(regs.validIdentifier); return helpers.cont("variable-2", "variable"); + } else if (ch == "|") { + return helpers.cont("operator", "pipe"); } else if (ch == ".") { return helpers.cont("operator", "property"); } else if (regs.stringChar.test(ch)) { @@ -101,6 +103,8 @@ CodeMirror.defineMode("smarty", function(config) { return helpers.cont("operator", "operator"); } else if (ch == "[" || ch == "]") { return helpers.cont("bracket", "bracket"); + } else if (ch == "(" || ch == ")") { + return helpers.cont("bracket", "operator"); } else if (/\d/.test(ch)) { stream.eatWhile(/\d/); return helpers.cont("number", "number"); @@ -114,6 +118,9 @@ CodeMirror.defineMode("smarty", function(config) { stream.eatWhile(regs.validIdentifier); return helpers.cont("qualifier", "modifier"); } + } else if (state.last == "pipe") { + stream.eatWhile(regs.validIdentifier); + return helpers.cont("qualifier", "modifier"); } else if (state.last == "whitespace") { stream.eatWhile(regs.validIdentifier); return helpers.cont("attribute", "modifier"); @@ -147,11 +154,15 @@ CodeMirror.defineMode("smarty", function(config) { inAttribute: function(quote) { return function(stream, state) { + var prevChar = null; + var currChar = null; while (!stream.eol()) { - if (stream.next() == quote) { + currChar = stream.peek(); + if (stream.next() == quote && prevChar !== '\\') { state.tokenize = parsers.smarty; break; } + prevChar = currChar; } return "string"; }; From 3f203d7b83d58a701ee9c980408fe1aab018795d Mon Sep 17 00:00:00 2001 From: John Connor Date: Sat, 25 May 2013 13:41:25 -0400 Subject: [PATCH 016/143] [vim keymap] 'dd' now handles last line corretly. Current behavior: ``` word1 word2 ``` If the cursor is on the last line and 'dd' is executed, the buffer does not change. Expected behavior: ``` word1 word2 ``` --- keymap/vim.js | 10 ++++++++++ test/vim_test.js | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/keymap/vim.js b/keymap/vim.js index 532e640a1f..51b93aec30 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -663,6 +663,9 @@ } RegisterController.prototype = { pushText: function(registerName, operator, text, linewise) { + if (linewise && text.charAt(0) == '\n') { + text = text.slice(1) + '\n'; + } // Lowercase and uppercase registers refer to the same register. // Uppercase just means append. var register = this.isValidRegister(registerName) ? @@ -1476,6 +1479,13 @@ }, // delete is a javascript keyword. 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { + // If the ending line is past the last line, inclusive, instead of + // including the trailing \n, include the \n before the starting line + if (operatorArgs.linewise && + curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) { + curStart.line--; + curStart.ch = lineLength(cm, curStart.line); + } getVimGlobalState().registerController.pushText( operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), operatorArgs.linewise); diff --git a/test/vim_test.js b/test/vim_test.js index 69714079de..f3b53508f6 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -782,6 +782,13 @@ testVim('dd_multiply_repeat', function(cm, vim, helpers) { is(register.linewise); helpers.assertCursorAt(0, lines[6].textStart); }); +testVim('dd_lastline', function(cm, vim, helpers) { + cm.setCursor(cm.lineCount(), 0); + var expectedLineCount = cm.lineCount() - 1; + helpers.doKeys('d', 'd'); + eq(expectedLineCount, cm.lineCount()); + helpers.assertCursorAt(cm.lineCount() - 1, 0); +}); // Yank commands should behave the exact same as d commands, expect that nothing // gets deleted. testVim('yw_repeat', function(cm, vim, helpers) { From 4c2fa0767a4168250bde05015ced6ec31f697686 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 27 May 2013 12:01:19 +0200 Subject: [PATCH 017/143] Update Webkit spanAffectsWrapping hack to recognize some common Unicode punctuation Issue #1219 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index aa0de5de43..a68417598c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5287,7 +5287,7 @@ window.CodeMirror = (function() { spanAffectsWrapping = function(str, i) { if (i > 1 && str.charCodeAt(i - 1) == 45 && /\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; - return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|\?[\w~`@#$%\^&*(_=+{[|><]/.test(str.slice(i - 1, i + 1)); + return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); }; var knownScrollbarWidth; From 9a50176800dfd9f4a5cdc19564a26381e71ed96f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 08:17:47 +0200 Subject: [PATCH 018/143] [comment addon] Fix bug in uncommenting line-commented blocks (http://stackoverflow.com/questions/16702574/codemirror-comment-js-addon-wont-uncomment-in-ruby-mode) --- addon/comment/comment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/comment/comment.js b/addon/comment/comment.js index 4f590f2870..b25bd96f22 100644 --- a/addon/comment/comment.js +++ b/addon/comment/comment.js @@ -97,7 +97,7 @@ var line = self.getLine(i); var found = line.indexOf(lineString); if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment; - if (i != start && nonWS.test(line.slice(0, found))) break lineComment; + if (i != start && found > -1 && nonWS.test(line.slice(0, found))) break lineComment; lines.push(line); } self.operation(function() { From 8aaac4445a317fff82ab314e9dd6f6d8404c8ff7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 10:06:37 +0200 Subject: [PATCH 019/143] [xml mode] Make else indentation style correspond to rest of project --- mode/xml/xml.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/mode/xml/xml.js b/mode/xml/xml.js index b04248c6c6..ae71c64130 100644 --- a/mode/xml/xml.js +++ b/mode/xml/xml.js @@ -58,20 +58,19 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { if (stream.eat("[")) { if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); else return null; - } - else if (stream.match("--")) return chain(inBlock("comment", "-->")); - else if (stream.match("DOCTYPE", true, true)) { + } else if (stream.match("--")) { + return chain(inBlock("comment", "-->")); + } else if (stream.match("DOCTYPE", true, true)) { stream.eatWhile(/[\w\._\-]/); return chain(doctype(1)); + } else { + return null; } - else return null; - } - else if (stream.eat("?")) { + } else if (stream.eat("?")) { stream.eatWhile(/[\w\._\-]/); state.tokenize = inBlock("meta", "?>"); return "meta"; - } - else { + } else { var isClose = stream.eat("/"); tagName = ""; var c; @@ -81,8 +80,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { state.tokenize = inTag; return "tag"; } - } - else if (ch == "&") { + } else if (ch == "&") { var ok; if (stream.eat("#")) { if (stream.eat("x")) { @@ -94,8 +92,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); } return ok ? "atom" : "error"; - } - else { + } else { stream.eatWhile(/[^&<]/); return null; } @@ -107,16 +104,15 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { state.tokenize = inText; type = ch == ">" ? "endTag" : "selfcloseTag"; return "tag"; - } - else if (ch == "=") { + } else if (ch == "=") { type = "equals"; return null; - } - else if (/[\'\"]/.test(ch)) { + } else if (ch == "<") { + return "error"; + } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); return state.tokenize(stream, state); - } - else { + } else { stream.eatWhile(/[^\s\u00a0=<>\"\']/); return "word"; } From a85d89c082cb5764ad137b6d1a3d79cffbb7f275 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 13:49:18 +0200 Subject: [PATCH 020/143] [xml-hint addon] Rewrite from scratch Using some of the ideas from the html5 hinter. Can now use a much richer source of hinting information, and also completes properties and property values. --- addon/hint/show-hint.js | 13 ++-- addon/hint/xml-hint.js | 168 ++++++++++++++++-------------------------------- demo/xmlcomplete.html | 119 ++++++++++++++++++++-------------- doc/manual.html | 33 +++++++++- 4 files changed, 164 insertions(+), 169 deletions(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 2c54dcd77e..504fda786b 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -1,7 +1,7 @@ CodeMirror.showHint = function(cm, getHints, options) { if (!options) options = {}; var startCh = cm.getCursor().ch, continued = false; - var closeOn = options.closeCharacters || /[\s()\[\]{};:]/; + var closeOn = options.closeCharacters || /[\s()\[\]{};:>]/; function startHinting() { // We want a single cursor position. @@ -114,6 +114,7 @@ CodeMirror.showHint = function(cm, getHints, options) { } } else ourMap = baseMap; + cm.state.completionActive = true; cm.addKeyMap(ourMap); cm.on("cursorActivity", cursorActivity); var closingOnBlur; @@ -154,7 +155,10 @@ CodeMirror.showHint = function(cm, getHints, options) { cm.off("blur", onBlur); cm.off("focus", onFocus); cm.off("scroll", onScroll); - if (willContinue !== true) CodeMirror.signal(data, "close"); + if (willContinue !== true) { + CodeMirror.signal(data, "close"); + cm.state.completionActive = false; + } } function pick() { pickCompletion(cm, data, completions[selectedHint]); @@ -169,8 +173,9 @@ CodeMirror.showHint = function(cm, getHints, options) { pos.ch < startCh || cm.somethingSelected() || (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) close(); - else - once = setTimeout(function(){close(true); continued = true; startHinting();}, 70); + else { + once = setTimeout(function(){close(true); continued = true; startHinting();}, 170); + } } CodeMirror.signal(data, "select", completions[0], hints.firstChild); return true; diff --git a/addon/hint/xml-hint.js b/addon/hint/xml-hint.js index 42eab6f3b7..bc26a09c6d 100644 --- a/addon/hint/xml-hint.js +++ b/addon/hint/xml-hint.js @@ -1,118 +1,60 @@ (function() { - - CodeMirror.xmlHints = []; - - CodeMirror.xmlHint = function(cm) { - - var cursor = cm.getCursor(); - - if (cursor.ch > 0) { - - var text = cm.getRange(CodeMirror.Pos(0, 0), cursor); - var typed = ''; - var simbol = ''; - for(var i = text.length - 1; i >= 0; i--) { - if(text[i] == ' ' || text[i] == '<') { - simbol = text[i]; - break; - } - else { - typed = text[i] + typed; - } - } - - text = text.slice(0, text.length - typed.length); - - var path = getActiveElement(text) + simbol; - var hints = CodeMirror.xmlHints[path]; - - if(typeof hints === 'undefined') - hints = ['']; - else { - hints = hints.slice(0); - for (var i = hints.length - 1; i >= 0; i--) { - if(hints[i].indexOf(typed) != 0) - hints.splice(i, 1); - } - } - - return { - list: hints, - from: CodeMirror.Pos(cursor.line, cursor.ch - typed.length), - to: cursor - }; + "use strict"; + + var Pos = CodeMirror.Pos; + + CodeMirror.xmlHint = function(cm, options) { + var tags = options && options.schemaInfo; + if (!tags) return; + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var inner = CodeMirror.innerMode(cm.getMode(), token.state); + if (inner.mode.name != "xml") return; + var result = [], replaceToken = false, prefix; + var isTag = token.string.charAt(0) == "<"; + if (!inner.state.tagName || isTag) { // Tag completion + if (isTag) { + prefix = token.string.slice(1); + replaceToken = true; + } + var cx = inner.state.context, curTag = cx && tags[cx.tagName]; + var childList = cx ? curTag && curTag.children : tags["!top"]; + if (childList) { + for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].indexOf(prefix) == 0) + result.push("<" + childList[i]); + } else { + for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.indexOf(prefix) == 0)) + result.push("<" + name); + } + if (cx && (!prefix || ("/" + cx.tagName).indexOf(prefix) == 0)) + result.push(""); + } else { + // Attribute completion + var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs; + if (!attrs) return; + if (token.type == "string" || token.string == "=") { // A value + var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)), + Pos(cur.line, token.type == "string" ? token.start : token.end)); + var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues; + if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return; + if (token.type == "string") { + prefix = token.string.charAt(0) == '"' ? token.string.slice(1) : token.string; + replaceToken = true; } - }; - - var getActiveElement = function(text) { - - var element = ''; - - if(text.length >= 0) { - - var regex = new RegExp('<([^!?][^\\s/>]*)[\\s\\S]*?>', 'g'); - - var matches = []; - var match; - while ((match = regex.exec(text)) != null) { - matches.push({ - tag: match[1], - selfclose: (match[0].slice(match[0].length - 2) === '/>') - }); - } - - for (var i = matches.length - 1, skip = 0; i >= 0; i--) { - - var item = matches[i]; - - if (item.tag[0] == '/') - { - skip++; - } - else if (item.selfclose == false) - { - if (skip > 0) - { - skip--; - } - else - { - element = '<' + item.tag + '>' + element; - } - } - } - - element += getOpenTag(text); + for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].indexOf(prefix) == 0) + result.push('"' + atValues[i] + '"'); + } else { // An attribute name + if (token.type == "attribute") { + prefix = token.string; + replaceToken = true; } - - return element; + for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.indexOf(prefix) == 0)) + result.push(attr); + } + } + return { + list: result, + from: replaceToken ? Pos(cur.line, token.start) : cur, + to: replaceToken ? Pos(cur.line, token.end) : cur }; - - var getOpenTag = function(text) { - - var open = text.lastIndexOf('<'); - var close = text.lastIndexOf('>'); - - if (close < open) - { - text = text.slice(open); - - if(text != '<') { - - var space = text.indexOf(' '); - if(space < 0) - space = text.indexOf('\t'); - if(space < 0) - space = text.indexOf('\n'); - - if (space < 0) - space = text.length; - - return text.slice(0, space); - } - } - - return ''; - }; - + }; })(); diff --git a/demo/xmlcomplete.html b/demo/xmlcomplete.html index 28a50638f7..a03b4c5fcc 100644 --- a/demo/xmlcomplete.html +++ b/demo/xmlcomplete.html @@ -7,75 +7,96 @@ -

CodeMirror: XML Autocomplete demo

-
+
-

Type '<' or space inside tag or - press ctrl-space to activate autocompletion. See - the code (here - and here) to figure out how - it works.

+

Press ctrl-space, or type a '<' character to + activate autocompletion. This demo defines a simple schema that + guides completion. The schema can be customized—see + the manual.

diff --git a/doc/manual.html b/doc/manual.html index 6fa58c6e30..07e72c7b63 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1682,12 +1682,39 @@

Add-ons

is an array of strings (the completions), and from and to give the start and end of the token that is being completed. Depends - on addon/hint/show-hint.css. See the other files in - the addon/hint for - hint sources for various languages. Check + on addon/hint/show-hint.css. Check out the demo for an example.
+
hint/javascript-hint.js
+
Defines a simple hinting function for JavaScript + (CodeMirror.javascriptHint) and CoffeeScript + (CodeMirror.coffeescriptHint) code. This will + simply use the JavaScript environment that the editor runs in as + a source of information about objects and their properties.
+ +
hint/xml-hint.js
+
Defines CodeMirror.xmlHint, which produces + hints for XML tagnames, attribute names, and attribute values, + guided by a schemaInfo option (a property of the + second argument passed to the hinting function, or the third + argument passed to CodeMirror.showHint).
The + schema info should be an object mapping tag names to information + about these tags, with optionally a "!top" property + containing a list of the names of valid top-level tags. The + values of the properties should be objects with optional + properties children (an array of valid child + element names, omit to simply allow all tags to appear) + and attrs (an object mapping attribute names + to null for free-form attributes, and an array of + valid values for restricted + attributes). Demo + here.
+ +
hint/python-hint.js
+
A very simple hinting function for Python code. + Defines CodeMirror.pythonHint.
+
match-highlighter.js
Adds a highlightSelectionMatches option that can be enabled to highlight all instances of a currently From d23d0589aae8d98f236ef0eb0e29b435fcad3b9f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 15:06:36 +0200 Subject: [PATCH 021/143] [html-hint addon] Rewrite to reuse xml-hint instead of own hinting function --- addon/hint/html-hint.js | 895 ++++++++++++++++++------------------------------ demo/html5complete.html | 76 +--- doc/manual.html | 11 + 3 files changed, 349 insertions(+), 633 deletions(-) diff --git a/addon/hint/html-hint.js b/addon/hint/html-hint.js index 8b5dc6f002..23238df054 100755 --- a/addon/hint/html-hint.js +++ b/addon/hint/html-hint.js @@ -1,582 +1,335 @@ (function () { - function htmlHint(editor, htmlStructure, getToken) { - var cur = editor.getCursor(); - var token = getToken(editor, cur); - var keywords = []; - var i = 0; - var j = 0; - var k = 0; - var from = {line: cur.line, ch: cur.ch}; - var to = {line: cur.line, ch: cur.ch}; - var flagClean = true; + var langs = "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" "); + var targets = ["_blank", "_self", "_top", "_parent"]; + var charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"]; + var methods = ["get", "post", "put", "delete"]; + var encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]; + var media = ["all", "screen", "print", "embossed", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "speech", + "3d-glasses", "resolution [>][<][=] [X]", "device-aspect-ratio: X/Y", "orientation:portrait", + "orientation:landscape", "device-height: [X]", "device-width: [X]"]; + var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags - var text = editor.getRange({line: 0, ch: 0}, cur); - - var open = text.lastIndexOf('<'); - var close = text.lastIndexOf('>'); - var tokenString = token.string.replace("<",""); - - if(open > close) { - var last = editor.getRange({line: cur.line, ch: cur.ch - 1}, cur); - if(last == "<") { - for(i = 0; i < htmlStructure.length; i++) { - keywords.push(htmlStructure[i].tag); - } - from.ch = token.start + 1; - } else { - var counter = 0; - var found = function(token, type, position) { - counter++; - if(counter > 50) return; - if(token.type == type) { - return token; - } else { - position.ch = token.start; - var newToken = editor.getTokenAt(position); - return found(newToken, type, position); - } - }; - - var nodeToken = found(token, "tag", {line: cur.line, ch: cur.ch}); - var node = nodeToken.string.substring(1); - - if(token.type === null && token.string.trim() === "") { - for(i = 0; i < htmlStructure.length; i++) { - if(htmlStructure[i].tag == node) { - for(j = 0; j < htmlStructure[i].attr.length; j++) { - keywords.push(htmlStructure[i].attr[j].key + "=\"\" "); - } - - for(k = 0; k < globalAttributes.length; k++) { - keywords.push(globalAttributes[k].key + "=\"\" "); - } - } - } - } else if(token.type == "string") { - tokenString = tokenString.substring(1, tokenString.length - 1); - var attributeToken = found(token, "attribute", {line: cur.line, ch: cur.ch}); - var attribute = attributeToken.string; - - for(i = 0; i < htmlStructure.length; i++) { - if(htmlStructure[i].tag == node) { - for(j = 0; j < htmlStructure[i].attr.length; j++) { - if(htmlStructure[i].attr[j].key == attribute) { - for(k = 0; k < htmlStructure[i].attr[j].values.length; k++) { - keywords.push(htmlStructure[i].attr[j].values[k]); - } - } - } - - for(j = 0; j < globalAttributes.length; j++) { - if(globalAttributes[j].key == attribute) { - for(k = 0; k < globalAttributes[j].values.length; k++) { - keywords.push(globalAttributes[j].values[k]); - } - } - } - } - } - from.ch = token.start + 1; - } else if(token.type == "attribute") { - for(i = 0; i < htmlStructure.length; i++) { - if(htmlStructure[i].tag == node) { - for(j = 0; j < htmlStructure[i].attr.length; j++) { - keywords.push(htmlStructure[i].attr[j].key + "=\"\" "); - } - - for(k = 0; k < globalAttributes.length; k++) { - keywords.push(globalAttributes[k].key + "=\"\" "); - } - } - } - from.ch = token.start; - } else if(token.type == "tag") { - for(i = 0; i < htmlStructure.length; i++) { - keywords.push(htmlStructure[i].tag); - } - - from.ch = token.start + 1; - } + var data = { + a: { + attrs: { + href: null, ping: null, type: null, + media: media, + target: targets, + hreflang: langs } - } else { - for(i = 0; i < htmlStructure.length; i++) { - keywords.push("<" + htmlStructure[i].tag); + }, + abbr: s, + acronym: s, + address: s, + applet: s, + area: { + attrs: { + alt: null, coords: null, href: null, target: null, ping: null, + media: media, hreflang: langs, type: null, + shape: ["default", "rect", "circle", "poly"] } - - tokenString = ("<" + tokenString).trim(); - from.ch = token.start; - } - - if(flagClean === true && tokenString.trim() === "") { - flagClean = false; - } - - if(flagClean) { - keywords = cleanResults(tokenString, keywords); - } - - return {list: keywords, from: from, to: to}; - } - - - var cleanResults = function(text, keywords) { - var results = []; - var i = 0; - - for(i = 0; i < keywords.length; i++) { - if(keywords[i].substring(0, text.length) == text) { - results.push(keywords[i]); + }, + article: s, + aside: s, + audio: { + attrs: { + src: null, mediagroup: null, + crossorigin: ["anonymous", "use-credentials"], + preload: ["none", "metadata", "auto"], + autoplay: ["", "autoplay"], + loop: ["", "loop"], + controls: ["", "controls"] } - } - - return results; + }, + b: s, + base: { attrs: { href: null, target: targets } }, + basefont: s, + bdi: s, + bdo: s, + big: s, + blockquote: { attrs: { cite: null } }, + body: s, + br: s, + button: { + attrs: { + form: null, formaction: null, name: null, value: null, + autofocus: ["", "autofocus"], + disabled: ["", "autofocus"], + formenctype: encs, + formmethod: methods, + formnovalidate: ["", "novalidate"], + formtarget: targets, + type: ["submit", "reset", "button"] + } + }, + canvas: { attrs: { width: null, height: null } }, + caption: s, + center: s, + cite: s, + code: s, + col: { attrs: { span: null } }, + colgroup: { attrs: { span: null } }, + command: { + attrs: { + type: ["command", "checkbox", "radio"], + label: null, icon: null, radiogroup: null, command: null, title: null, + disabled: ["", "disabled"], + checked: ["", "checked"] + } + }, + data: { attrs: { value: null } }, + datagrid: { attrs: { disabled: ["", "disabled"], multiple: ["", "multiple"] } }, + datalist: { attrs: { data: null } }, + dd: s, + del: { attrs: { cite: null, datetime: null } }, + details: { attrs: { open: ["", "open"] } }, + dfn: s, + dir: s, + div: s, + dl: s, + dt: s, + em: s, + embed: { attrs: { src: null, type: null, width: null, height: null } }, + eventsource: { attrs: { src: null } }, + fieldset: { attrs: { disabled: ["", "disabled"], form: null, name: null } }, + figcaption: s, + figure: s, + font: s, + footer: s, + form: { + attrs: { + action: null, name: null, + "accept-charset": charsets, + autocomplete: ["on", "off"], + enctype: encs, + method: methods, + novalidate: ["", "novalidate"], + target: targets + } + }, + frame: s, + frameset: s, + h1: s, h2: s, h3: s, h4: s, h5: s, h6: s, + head: { + attrs: {}, + children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"] + }, + header: s, + hgroup: s, + hr: s, + html: { + attrs: { manifest: null }, + children: ["head", "body"] + }, + i: s, + iframe: { + attrs: { + src: null, srcdoc: null, name: null, width: null, height: null, + sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"], + seamless: ["", "seamless"] + } + }, + img: { + attrs: { + alt: null, src: null, ismap: null, usemap: null, width: null, height: null, + crossorigin: ["anonymous", "use-credentials"] + } + }, + input: { + attrs: { + alt: null, dirname: null, form: null, formaction: null, + height: null, list: null, max: null, maxlength: null, min: null, + name: null, pattern: null, placeholder: null, size: null, src: null, + step: null, value: null, width: null, + accept: ["audio/*", "video/*", "image/*"], + autocomplete: ["on", "off"], + autofocus: ["", "autofocus"], + checked: ["", "checked"], + disabled: ["", "disabled"], + formenctype: encs, + formmethod: methods, + formnovalidate: ["", "novalidate"], + formtarget: targets, + multiple: ["", "multiple"], + readonly: ["", "readonly"], + required: ["", "required"], + type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month", + "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", + "file", "submit", "image", "reset", "button"] + } + }, + ins: { attrs: { cite: null, datetime: null } }, + kbd: s, + keygen: { + attrs: { + challenge: null, form: null, name: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + keytype: ["RSA"] + } + }, + label: { attrs: { "for": null, form: null } }, + legend: s, + li: { attrs: { value: null } }, + link: { + attrs: { + href: null, type: null, + hreflang: langs, + media: media, + sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"] + } + }, + map: { attrs: { name: null } }, + mark: s, + menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } }, + meta: { + attrs: { + content: null, + charset: charsets, + name: ["viewport", "application-name", "author", "description", "generator", "keywords"], + "http-equiv": ["content-language", "content-type", "default-style", "refresh"] + } + }, + meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } }, + nav: s, + noframes: s, + noscript: s, + object: { + attrs: { + data: null, type: null, name: null, usemap: null, form: null, width: null, height: null, + typemustmatch: ["", "typemustmatch"] + } + }, + ol: { attrs: { reversed: ["", "reversed"], start: null, type: ["1", "a", "A", "i", "I"] } }, + optgroup: { attrs: { disabled: ["", "disabled"], label: null } }, + option: { attrs: { disabled: ["", "disabled"], label: null, selected: ["", "selected"], value: null } }, + output: { attrs: { "for": null, form: null, name: null } }, + p: s, + param: { attrs: { name: null, value: null } }, + pre: s, + progress: { attrs: { value: null, max: null } }, + q: { attrs: { cite: null } }, + rp: s, + rt: s, + ruby: s, + s: s, + samp: s, + script: { + attrs: { + type: ["text/javascript"], + src: null, + async: ["", "async"], + defer: ["", "defer"], + charset: charsets + } + }, + section: s, + select: { + attrs: { + form: null, name: null, size: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + multiple: ["", "multiple"] + } + }, + small: s, + source: { attrs: { src: null, type: null, media: null } }, + span: s, + strike: s, + strong: s, + style: { + attrs: { + type: ["text/css"], + media: media, + scoped: null + } + }, + sub: s, + summary: s, + sup: s, + table: s, + tbody: s, + td: { attrs: { colspan: null, rowspan: null, headers: null } }, + textarea: { + attrs: { + dirname: null, form: null, maxlength: null, name: null, placeholder: null, + rows: null, cols: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + readonly: ["", "readonly"], + required: ["", "required"], + wrap: ["soft", "hard"] + } + }, + tfoot: s, + th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } }, + thead: s, + time: { attrs: { datetime: null } }, + title: s, + tr: s, + track: { + attrs: { + src: null, label: null, "default": null, + kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"], + srclang: langs + } + }, + tt: s, + u: s, + ul: s, + "var": s, + video: { + attrs: { + src: null, poster: null, width: null, height: null, + crossorigin: ["anonymous", "use-credentials"], + preload: ["auto", "metadata", "none"], + autoplay: ["", "autoplay"], + mediagroup: ["movie"], + muted: ["", "muted"], + controls: ["", "controls"] + } + }, + wbr: s }; - var htmlStructure = [ - {tag: '!DOCTYPE', attr: []}, - {tag: 'a', attr: [ - {key: 'href', values: ["#"]}, - {key: 'target', values: ["_blank","_self","_top","_parent"]}, - {key: 'ping', values: [""]}, - {key: 'media', values: ["#"]}, - {key: 'hreflang', values: ["en","es"]}, - {key: 'type', values: []} - ]}, - {tag: 'abbr', attr: []}, - {tag: 'acronym', attr: []}, - {tag: 'address', attr: []}, - {tag: 'applet', attr: []}, - {tag: 'area', attr: [ - {key: 'alt', values: [""]}, - {key: 'coords', values: ["rect: left, top, right, bottom","circle: center-x, center-y, radius","poly: x1, y1, x2, y2, ..."]}, - {key: 'shape', values: ["default","rect","circle","poly"]}, - {key: 'href', values: ["#"]}, - {key: 'target', values: ["#"]}, - {key: 'ping', values: []}, - {key: 'media', values: []}, - {key: 'hreflang', values: []}, - {key: 'type', values: []} - - ]}, - {tag: 'article', attr: []}, - {tag: 'aside', attr: []}, - {tag: 'audio', attr: [ - {key: 'src', values: []}, - {key: 'crossorigin', values: ["anonymous","use-credentials"]}, - {key: 'preload', values: ["none","metadata","auto"]}, - {key: 'autoplay', values: ["","autoplay"]}, - {key: 'mediagroup', values: []}, - {key: 'loop', values: ["","loop"]}, - {key: 'controls', values: ["","controls"]} - ]}, - {tag: 'b', attr: []}, - {tag: 'base', attr: [ - {key: 'href', values: ["#"]}, - {key: 'target', values: ["_blank","_self","_top","_parent"]} - ]}, - {tag: 'basefont', attr: []}, - {tag: 'bdi', attr: []}, - {tag: 'bdo', attr: []}, - {tag: 'big', attr: []}, - {tag: 'blockquote', attr: [ - {key: 'cite', values: ["http://"]} - ]}, - {tag: 'body', attr: []}, - {tag: 'br', attr: []}, - {tag: 'button', attr: [ - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'form', values: []}, - {key: 'formaction', values: []}, - {key: 'formenctype', values: ["application/x-www-form-urlencoded","multipart/form-data","text/plain"]}, - {key: 'formmethod', values: ["get","post","put","delete"]}, - {key: 'formnovalidate', values: ["","novalidate"]}, - {key: 'formtarget', values: ["_blank","_self","_top","_parent"]}, - {key: 'name', values: []}, - {key: 'type', values: ["submit","reset","button"]}, - {key: 'value', values: []} - ]}, - {tag: 'canvas', attr: [ - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'caption', attr: []}, - {tag: 'center', attr: []}, - {tag: 'cite', attr: []}, - {tag: 'code', attr: []}, - {tag: 'col', attr: [ - {key: 'span', values: []} - ]}, - {tag: 'colgroup', attr: [ - {key: 'span', values: []} - ]}, - {tag: 'command', attr: [ - {key: 'type', values: ["command","checkbox","radio"]}, - {key: 'label', values: []}, - {key: 'icon', values: []}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'checked', values: ["","checked"]}, - {key: 'radiogroup', values: []}, - {key: 'command', values: []}, - {key: 'title', values: []} - ]}, - {tag: 'data', attr: [ - {key: 'value', values: []} - ]}, - {tag: 'datagrid', attr: [ - {key: 'disabled', values: ["","disabled"]}, - {key: 'multiple', values: ["","multiple"]} - ]}, - {tag: 'datalist', attr: [ - {key: 'data', values: []} - ]}, - {tag: 'dd', attr: []}, - {tag: 'del', attr: [ - {key: 'cite', values: []}, - {key: 'datetime', values: []} - ]}, - {tag: 'details', attr: [ - {key: 'open', values: ["","open"]} - ]}, - {tag: 'dfn', attr: []}, - {tag: 'dir', attr: []}, - {tag: 'div', attr: [ - {key: 'id', values: []}, - {key: 'class', values: []}, - {key: 'style', values: []} - ]}, - {tag: 'dl', attr: []}, - {tag: 'dt', attr: []}, - {tag: 'em', attr: []}, - {tag: 'embed', attr: [ - {key: 'src', values: []}, - {key: 'type', values: []}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'eventsource', attr: [ - {key: 'src', values: []} - ]}, - {tag: 'fieldset', attr: [ - {key: 'disabled', values: ["","disabled"]}, - {key: 'form', values: []}, - {key: 'name', values: []} - ]}, - {tag: 'figcaption', attr: []}, - {tag: 'figure', attr: []}, - {tag: 'font', attr: []}, - {tag: 'footer', attr: []}, - {tag: 'form', attr: [ - {key: 'accept-charset', values: ["UNKNOWN","utf-8"]}, - {key: 'action', values: []}, - {key: 'autocomplete', values: ["on","off"]}, - {key: 'enctype', values: ["application/x-www-form-urlencoded","multipart/form-data","text/plain"]}, - {key: 'method', values: ["get","post","put","delete","dialog"]}, - {key: 'name', values: []}, - {key: 'novalidate', values: ["","novalidate"]}, - {key: 'target', values: ["_blank","_self","_top","_parent"]} - ]}, - {tag: 'frame', attr: []}, - {tag: 'frameset', attr: []}, - {tag: 'h1', attr: []}, - {tag: 'h2', attr: []}, - {tag: 'h3', attr: []}, - {tag: 'h4', attr: []}, - {tag: 'h5', attr: []}, - {tag: 'h6', attr: []}, - {tag: 'head', attr: []}, - {tag: 'header', attr: []}, - {tag: 'hgroup', attr: []}, - {tag: 'hr', attr: []}, - {tag: 'html', attr: [ - {key: 'manifest', values: []} - ]}, - {tag: 'i', attr: []}, - {tag: 'iframe', attr: [ - {key: 'src', values: []}, - {key: 'srcdoc', values: []}, - {key: 'name', values: []}, - {key: 'sandbox', values: ["allow-top-navigation","allow-same-origin","allow-forms","allow-scripts"]}, - {key: 'seamless', values: ["","seamless"]}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'img', attr: [ - {key: 'alt', values: []}, - {key: 'src', values: []}, - {key: 'crossorigin', values: ["anonymous","use-credentials"]}, - {key: 'ismap', values: []}, - {key: 'usemap', values: []}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'input', attr: [ - {key: 'accept', values: ["audio/*","video/*","image/*"]}, - {key: 'alt', values: []}, - {key: 'autocomplete', values: ["on","off"]}, - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'checked', values: ["","checked"]}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'dirname', values: []}, - {key: 'form', values: []}, - {key: 'formaction', values: []}, - {key: 'formenctype', values: ["application/x-www-form-urlencoded","multipart/form-data","text/plain"]}, - {key: 'formmethod', values: ["get","post","put","delete"]}, - {key: 'formnovalidate', values: ["","novalidate"]}, - {key: 'formtarget', values: ["_blank","_self","_top","_parent"]}, - {key: 'height', values: []}, - {key: 'list', values: []}, - {key: 'max', values: []}, - {key: 'maxlength', values: []}, - {key: 'min', values: []}, - {key: 'multiple', values: ["","multiple"]}, - {key: 'name', values: []}, - {key: 'pattern', values: []}, - {key: 'placeholder', values: []}, - {key: 'readonly', values: ["","readonly"]}, - {key: 'required', values: ["","required"]}, - {key: 'size', values: []}, - {key: 'src', values: []}, - {key: 'step', values: []}, - {key: 'type', values: [ - "hidden","text","search","tel","url","email","password","datetime","date","month","week","time","datetime-local", - "number","range","color","checkbox","radio","file","submit","image","reset","button" - ]}, - {key: 'value', values: []}, - {key: 'width', values: []} - ]}, - {tag: 'ins', attr: [ - {key: 'cite', values: []}, - {key: 'datetime', values: []} - ]}, - {tag: 'kbd', attr: []}, - {tag: 'keygen', attr: [ - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'challenge', values: []}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'form', values: []}, - {key: 'keytype', values: ["RSA"]}, - {key: 'name', values: []} - ]}, - {tag: 'label', attr: [ - {key: 'for', values: []}, - {key: 'form', values: []} - ]}, - {tag: 'legend', attr: []}, - {tag: 'li', attr: [ - {key: 'value', values: []} - ]}, - {tag: 'link', attr: [ - {key: 'href', values: []}, - {key: 'hreflang', values: ["en","es"]}, - {key: 'media', values: [ - "all","screen","print","embossed","braille","handheld","print","projection","screen","tty","tv","speech","3d-glasses", - "resolution [>][<][=] [X]dpi","resolution [>][<][=] [X]dpcm","device-aspect-ratio: 16/9","device-aspect-ratio: 4/3", - "device-aspect-ratio: 32/18","device-aspect-ratio: 1280/720","device-aspect-ratio: 2560/1440","orientation:portrait", - "orientation:landscape","device-height: [X]px","device-width: [X]px","-webkit-min-device-pixel-ratio: 2" - ]}, - {key: 'type', values: []}, - {key: 'sizes', values: ["all","16x16","16x16 32x32","16x16 32x32 64x64"]} - ]}, - {tag: 'map', attr: [ - {key: 'name', values: []} - ]}, - {tag: 'mark', attr: []}, - {tag: 'menu', attr: [ - {key: 'type', values: ["list","context","toolbar"]}, - {key: 'label', values: []} - ]}, - {tag: 'meta', attr: [ - {key: 'charset', attr: ["utf-8"]}, - {key: 'name', attr: ["viewport","application-name","author","description","generator","keywords"]}, - {key: 'content', attr: ["","width=device-width","initial-scale=1, maximum-scale=1, minimun-scale=1, user-scale=no"]}, - {key: 'http-equiv', attr: ["content-language","content-type","default-style","refresh"]} - ]}, - {tag: 'meter', attr: [ - {key: 'value', values: []}, - {key: 'min', values: []}, - {key: 'low', values: []}, - {key: 'high', values: []}, - {key: 'max', values: []}, - {key: 'optimum', values: []} - ]}, - {tag: 'nav', attr: []}, - {tag: 'noframes', attr: []}, - {tag: 'noscript', attr: []}, - {tag: 'object', attr: [ - {key: 'data', values: []}, - {key: 'type', values: []}, - {key: 'typemustmatch', values: ["","typemustmatch"]}, - {key: 'name', values: []}, - {key: 'usemap', values: []}, - {key: 'form', values: []}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'ol', attr: [ - {key: 'reversed', values: ["", "reversed"]}, - {key: 'start', values: []}, - {key: 'type', values: ["1","a","A","i","I"]} - ]}, - {tag: 'optgroup', attr: [ - {key: 'disabled', values: ["","disabled"]}, - {key: 'label', values: []} - ]}, - {tag: 'option', attr: [ - {key: 'disabled', values: ["", "disabled"]}, - {key: 'label', values: []}, - {key: 'selected', values: ["", "selected"]}, - {key: 'value', values: []} - ]}, - {tag: 'output', attr: [ - {key: 'for', values: []}, - {key: 'form', values: []}, - {key: 'name', values: []} - ]}, - {tag: 'p', attr: []}, - {tag: 'param', attr: [ - {key: 'name', values: []}, - {key: 'value', values: []} - ]}, - {tag: 'pre', attr: []}, - {tag: 'progress', attr: [ - {key: 'value', values: []}, - {key: 'max', values: []} - ]}, - {tag: 'q', attr: [ - {key: 'cite', values: []} - ]}, - {tag: 'rp', attr: []}, - {tag: 'rt', attr: []}, - {tag: 'ruby', attr: []}, - {tag: 's', attr: []}, - {tag: 'samp', attr: []}, - {tag: 'script', attr: [ - {key: 'type', values: ["text/javascript"]}, - {key: 'src', values: []}, - {key: 'async', values: ["","async"]}, - {key: 'defer', values: ["","defer"]}, - {key: 'charset', values: ["utf-8"]} - ]}, - {tag: 'section', attr: []}, - {tag: 'select', attr: [ - {key: 'autofocus', values: ["", "autofocus"]}, - {key: 'disabled', values: ["", "disabled"]}, - {key: 'form', values: []}, - {key: 'multiple', values: ["", "multiple"]}, - {key: 'name', values: []}, - {key: 'size', values: []} - ]}, - {tag: 'small', attr: []}, - {tag: 'source', attr: [ - {key: 'src', values: []}, - {key: 'type', values: []}, - {key: 'media', values: []} - ]}, - {tag: 'span', attr: []}, - {tag: 'strike', attr: []}, - {tag: 'strong', attr: []}, - {tag: 'style', attr: [ - {key: 'type', values: ["text/css"]}, - {key: 'media', values: ["all","braille","print","projection","screen","speech"]}, - {key: 'scoped', values: []} - ]}, - {tag: 'sub', attr: []}, - {tag: 'summary', attr: []}, - {tag: 'sup', attr: []}, - {tag: 'table', attr: [ - {key: 'border', values: []} - ]}, - {tag: 'tbody', attr: []}, - {tag: 'td', attr: [ - {key: 'colspan', values: []}, - {key: 'rowspan', values: []}, - {key: 'headers', values: []} - ]}, - {tag: 'textarea', attr: [ - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'dirname', values: []}, - {key: 'form', values: []}, - {key: 'maxlength', values: []}, - {key: 'name', values: []}, - {key: 'placeholder', values: []}, - {key: 'readonly', values: ["","readonly"]}, - {key: 'required', values: ["","required"]}, - {key: 'rows', values: []}, - {key: 'cols', values: []}, - {key: 'wrap', values: ["soft","hard"]} - ]}, - {tag: 'tfoot', attr: []}, - {tag: 'th', attr: [ - {key: 'colspan', values: []}, - {key: 'rowspan', values: []}, - {key: 'headers', values: []}, - {key: 'scope', values: ["row","col","rowgroup","colgroup"]} - ]}, - {tag: 'thead', attr: []}, - {tag: 'time', attr: [ - {key: 'datetime', values: []} - ]}, - {tag: 'title', attr: []}, - {tag: 'tr', attr: []}, - {tag: 'track', attr: [ - {key: 'kind', values: ["subtitles","captions","descriptions","chapters","metadata"]}, - {key: 'src', values: []}, - {key: 'srclang', values: ["en","es"]}, - {key: 'label', values: []}, - {key: 'default', values: []} - ]}, - {tag: 'tt', attr: []}, - {tag: 'u', attr: []}, - {tag: 'ul', attr: []}, - {tag: 'var', attr: []}, - {tag: 'video', attr: [ - {key: "src", values: []}, - {key: "crossorigin", values: ["anonymous","use-credentials"]}, - {key: "poster", values: []}, - {key: "preload", values: ["auto","metadata","none"]}, - {key: "autoplay", values: ["","autoplay"]}, - {key: "mediagroup", values: ["movie"]}, - {key: "loop", values: ["","loop"]}, - {key: "muted", values: ["","muted"]}, - {key: "controls", values: ["","controls"]}, - {key: "width", values: []}, - {key: "height", values: []} - ]}, - {tag: 'wbr', attr: []} - ]; + var globalAttrs = { + accesskey: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "class": null, + contenteditable: ["true", "false"], + contextmenu: null, + dir: ["ltr", "rtl", "auto"], + draggable: ["true", "false", "auto"], + dropzone: ["copy", "move", "link", "string:", "file:"], + hidden: ["hidden"], + id: null, + inert: ["inert"], + itemid: null, + itemprop: null, + itemref: null, + itemscope: ["itemscope"], + itemtype: null, + lang: ["en", "es"], + spellcheck: ["true", "false"], + style: null, + tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + title: null, + translate: ["yes", "no"], + onclick: null, + rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"] + }; + function populate(obj) { + for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr)) + obj.attrs[attr] = globalAttrs[attr]; + } - var globalAttributes = [ - {key: "accesskey", values: ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"]}, - {key: "class", values: []}, - {key: "contenteditable", values: ["true", "false"]}, - {key: "contextmenu", values: []}, - {key: "dir", values: ["ltr","rtl","auto"]}, - {key: "draggable", values: ["true","false","auto"]}, - {key: "dropzone", values: ["copy","move","link","string:","file:"]}, - {key: "hidden", values: ["hidden"]}, - {key: "id", values: []}, - {key: "inert", values: ["inert"]}, - {key: "itemid", values: []}, - {key: "itemprop", values: []}, - {key: "itemref", values: []}, - {key: "itemscope", values: ["itemscope"]}, - {key: "itemtype", values: []}, - {key: "lang", values: ["en","es"]}, - {key: "spellcheck", values: ["true","false"]}, - {key: "style", values: []}, - {key: "tabindex", values: ["1","2","3","4","5","6","7","8","9"]}, - {key: "title", values: []}, - {key: "translate", values: ["yes","no"]}, - {key: "onclick", values: []}, - {key: 'rel', values: ["stylesheet","alternate","author","bookmark","help","license","next","nofollow","noreferrer","prefetch","prev","search","tag"]} - ]; + populate(s); + for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s) + populate(data[tag]); - CodeMirror.htmlHint = function(editor) { - if(String.prototype.trim == undefined) { - String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');}; - } - return htmlHint(editor, htmlStructure, function (e, cur) { return e.getTokenAt(cur); }); + CodeMirror.htmlSchema = data; + CodeMirror.htmlHint = function(cm, options) { + var local = {schemaInfo: data}; + if (options) for (var opt in options) local[opt] = options[opt]; + return CodeMirror.xmlHint(cm, local); }; })(); diff --git a/demo/html5complete.html b/demo/html5complete.html index 5091354ae9..5b2dd0de1d 100644 --- a/demo/html5complete.html +++ b/demo/html5complete.html @@ -2,12 +2,12 @@ - CodeMirror: Close-Tag Demo + CodeMirror: HTML completion demo - + @@ -21,72 +21,24 @@ -

HTML5 code completation demo

-
    -
  • Type an html tag. If you press Ctrl+Space a hint panel show the code suggest. You can type to autocomplete tags, attributes if your cursor are inner a tag or attribute values if your cursor are inner a attribute value.
  • -
+

HTML completion demo

+

Shows the XML completer + parameterized with information about the tags in HTML. + Press ctrl-space to activate completion.

-
diff --git a/doc/manual.html b/doc/manual.html index 07e72c7b63..3bfc3d0e91 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1711,6 +1711,17 @@

Add-ons

attributes). Demo here.
+
hint/html-hint.js
+
Provides schema info to + the xml-hint addon for HTML + documents. Defines a schema + object CodeMirror.htmlSchema that you can pass to + as a schemaInfo option, and + a CodeMirror.htmlHint hinting function that + automatically calls CodeMirror.xmlHint with this + schema data. See + the demo.
+
hint/python-hint.js
A very simple hinting function for Python code. Defines CodeMirror.pythonHint.
From a22787fa013b6027d8c2ec94b75124a9c19dbb7b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 19:35:29 +0200 Subject: [PATCH 022/143] Work around IE bug where drag events get fired twice Closes #1551 --- lib/codemirror.js | 57 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index a68417598c..37f7a1100d 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1744,11 +1744,41 @@ window.CodeMirror = (function() { on(document, "mouseup", up); } + function clickInGutter(cm, e) { + var display = cm.display; + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + + if (mX >= Math.floor(getRect(display.gutters).right)) return false; + e_preventDefault(e); + if (!hasHandler(cm, "gutterClick")) return true; + + var lineBox = getRect(display.lineDiv); + if (mY > lineBox.bottom) return true; + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && getRect(g).right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalLater(cm, "gutterClick", cm, line, gutter, e); + break; + } + } + return true; + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + function onDrop(e) { var cm = this; if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) return; e_preventDefault(e); + if (ie) lastDrop = +new Date; var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; if (!pos || isReadOnly(cm)) return; if (files && files.length && window.FileReader && window.File) { @@ -1788,33 +1818,8 @@ window.CodeMirror = (function() { } } - function clickInGutter(cm, e) { - var display = cm.display; - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - - if (mX >= Math.floor(getRect(display.gutters).right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - - var lineBox = getRect(display.lineDiv); - if (mY > lineBox.bottom) return true; - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && getRect(g).right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signalLater(cm, "gutterClick", cm, line, gutter, e); - break; - } - } - return true; - } - function onDragStart(cm, e) { - if (ie && !cm.state.draggingText) { e_stop(e); return; } + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } if (eventInWidget(cm.display, e)) return; var txt = cm.getSelection(); From c71f96fbf30af73c19c8453ea32cfc9d2d053975 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 19:59:48 +0200 Subject: [PATCH 023/143] [javascript mode] More comma-handling tweaks Issue #1443 --- mode/javascript/javascript.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index fabe1c42b9..ab76a57e81 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -270,17 +270,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return pass(pushlex("stat"), expression, expect(";"), poplex); } function expression(type) { - return expressionInner(type, maybeoperatorComma); + return expressionInner(type, false); } function expressionNoComma(type) { - return expressionInner(type, maybeoperatorNoComma); + return expressionInner(type, true); } - function expressionInner(type, maybeop) { + function expressionInner(type, noComma) { + var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); if (type == "function") return cont(functiondef); - if (type == "keyword c") return cont(maybeexpression); + if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); - if (type == "operator") return cont(expression); + if (type == "operator") return cont(noComma ? expressionNoComma : expression); if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop); if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop); return cont(); @@ -289,9 +290,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type.match(/[;\}\)\],]/)) return pass(); return pass(expression); } + function maybeexpressionNoComma(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expressionNoComma); + } function maybeoperatorComma(type, value) { - if (type == ",") return pass(); + if (type == ",") return cont(expression); return maybeoperatorNoComma(type, value, maybeoperatorComma); } function maybeoperatorNoComma(type, value, me) { From a828fa125508eb7c6e59894af20ff5918811da37 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 28 May 2013 21:57:40 +0200 Subject: [PATCH 024/143] [xml-hint demo] Fix minor bug, add sponsor link --- demo/xmlcomplete.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demo/xmlcomplete.html b/demo/xmlcomplete.html index a03b4c5fcc..5363b39611 100644 --- a/demo/xmlcomplete.html +++ b/demo/xmlcomplete.html @@ -25,6 +25,10 @@ guides completion. The schema can be customized—see the manual.

+

Development of the xml-hint addon was kindly + sponsored + by www.xperiment.mobi.

+ From ba5554ddeaae8e6525d7ea196ec8da3ba1dac6a3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 29 May 2013 10:05:15 +0200 Subject: [PATCH 028/143] Fix partial-updating of lines with line widgets above them Issue #1554 --- lib/codemirror.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 0cbfbb1917..0865b55889 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -656,25 +656,25 @@ window.CodeMirror = (function() { if (reuse) { reuse.alignable = null; - var isOk = true, widgetsSeen = 0; + var isOk = true, widgetsSeen = 0, insertBefore = null; for (var n = reuse.firstChild, next; n; n = next) { next = n.nextSibling; if (!/\bCodeMirror-linewidget\b/.test(n.className)) { reuse.removeChild(n); } else { for (var i = 0, first = true; i < line.widgets.length; ++i) { - var widget = line.widgets[i], isFirst = false; - if (!widget.above) { isFirst = first; first = false; } + var widget = line.widgets[i]; + if (!widget.above) { insertBefore = n; first = false; } if (widget.node == n.firstChild) { positionLineWidget(widget, n, reuse, dims); ++widgetsSeen; - if (isFirst) reuse.insertBefore(lineElement, n); break; } } if (i == line.widgets.length) { isOk = false; break; } } } + reuse.insertBefore(lineElement, insertBefore); if (isOk && widgetsSeen == line.widgets.length) { wrap = reuse; reuse.className = line.wrapClass || ""; From 3328ed0ebc31182f3853d6564d45bcabcc8c1806 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 12:14:24 +0200 Subject: [PATCH 029/143] Reset scrollbarwidth cache when window is resized Zooming can produce different measurements. --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 0865b55889..96a84d3d89 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1518,7 +1518,7 @@ window.CodeMirror = (function() { if (resizeTimer == null) resizeTimer = setTimeout(function() { resizeTimer = null; // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = null; + d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null; clearCaches(cm); runInOp(cm, bind(regChange, cm)); }, 100); From 0bfab07985e5734a2d8eb364d9d12d4ddcfe1719 Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 23 May 2013 12:41:39 -0700 Subject: [PATCH 030/143] forgive one pixel in scrollbars Sometimes I find that CodeMirror adds scrollbars inappropriately (most often due to browser zoom). Digging around suggested that it is some non-integers resulting in rounding up the measured size, and I haven't been able to find a case where the two values differ by more than one pixel, so simply setting the threshold one pixel higher seems to address the issue. This PR forgives one pixel in the size comparison before drawing the scrollbars. --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 96a84d3d89..717ac6f4d5 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -323,8 +323,8 @@ window.CodeMirror = (function() { d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px"; var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); - var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; - var needsV = scrollHeight > d.scroller.clientHeight; + var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1); + var needsV = scrollHeight > (d.scroller.clientHeight + 1); if (needsV) { d.scrollbarV.style.display = "block"; d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; From cd6540cb3b6d6e5de55eab2c4b692cbe768d8e97 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 12:36:11 +0200 Subject: [PATCH 031/143] Add handleMouseEvents option to markText and addLineWidget --- doc/manual.html | 11 +++++++++++ lib/codemirror.js | 17 ++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 3bfc3d0e91..2b3874aaa5 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1090,6 +1090,12 @@

Text-marking methods

Use a given node to display this range. Implies both collapsed and atomic. The given DOM node must be an inline element (as opposed to a block element).
+
handleMouseEvents: boolean
+
When replacedWith is given, this determines + whether the editor will capture mouse and drag events + occurring in this widget. Default is false—the events will be + left alone for the default browser handler, or specific + handlers on the widget, to capture.
readOnly: boolean
A read-only span can, as long as it is not cleared, not be modified except by @@ -1231,6 +1237,11 @@

Widget, gutter, and decoration methods

showIfHidden: boolean
When true, will cause the widget to be rendered even if the line it is associated with is hidden.
+
handleMouseEvents: boolean
+
Determines whether the editor will capture mouse and + drag events occurring in this widget. Default is false—the + events will be left alone for the default browser handler, + or specific handlers on the widget, to capture.
Note that the widget node will become a descendant of nodes with CodeMirror-specific CSS classes, and those classes might in some diff --git a/lib/codemirror.js b/lib/codemirror.js index 717ac6f4d5..9b6b2e1ecd 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -602,8 +602,9 @@ window.CodeMirror = (function() { if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); if (lineIsHidden(cm.doc, line)) { if (line.height != 0) updateLineHeight(line, 0); - if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) - if (line.widgets[i].showIfHidden) { + if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i]; + if (w.showIfHidden) { var prev = cur.previousSibling; if (/pre/i.test(prev.nodeName)) { var wrap = elt("div", null, null, "position: relative"); @@ -611,9 +612,11 @@ window.CodeMirror = (function() { wrap.appendChild(prev); prev = wrap; } - var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget")); - positionLineWidget(line.widgets[i], wnode, prev, dims); + var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget")); + if (!w.handleMouseEvents) wnode.ignoreEvents = true; + positionLineWidget(w, wnode, prev, dims); } + } } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { // This line is intact. Skip to the actual node. Update its // line number if needed. @@ -709,6 +712,7 @@ window.CodeMirror = (function() { if (ie_lt8) wrap.style.zIndex = 2; if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.ignoreEvents = true; positionLineWidget(widget, node, wrap, dims); if (widget.above) wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); @@ -1584,9 +1588,7 @@ window.CodeMirror = (function() { function eventInWidget(display, e) { for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n) return true; - if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || - n.parentNode == display.sizer && n != display.mover) return true; + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; } } @@ -3664,6 +3666,7 @@ window.CodeMirror = (function() { if (marker.replacedWith) { marker.collapsed = true; marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; } if (marker.collapsed) sawCollapsedSpans = true; From 41aeb8a512bf2fe7cf52e75465e1701b9124e753 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 13:32:05 +0200 Subject: [PATCH 032/143] Take top padding into account in "local" coordinate space, fix bug in fromCoordSystem Issue #1557 --- lib/codemirror.js | 18 +++++++++--------- test/test.js | 17 ++++++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 9b6b2e1ecd..6b452d699d 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1109,7 +1109,8 @@ window.CodeMirror = (function() { if (context == "line") return rect; if (!context) context = "local"; var yOff = heightAtLine(cm, lineObj); - if (context != "local") yOff -= cm.display.viewOffset; + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; if (context == "page") { var lOff = getRect(cm.display.lineSpace); yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); @@ -1125,19 +1126,18 @@ window.CodeMirror = (function() { function fromCoordSystem(cm, coords, context) { if (context == "div") return coords; var left = coords.left, top = coords.top; + // First move into "page" coordinate system if (context == "page") { left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft; top -= window.pageYOffset || (document.documentElement || document.body).scrollTop; + } else if (context == "local" || !context) { + var localBox = getRect(cm.display.sizer); + left += localBox.left; + top += localBox.top; } + var lineSpaceBox = getRect(cm.display.lineSpace); - left -= lineSpaceBox.left; - top -= lineSpaceBox.top; - if (context == "local" || !context) { - var editorBox = getRect(cm.display.wrapper); - left += editorBox.left; - top += editorBox.top; - } - return {left: left, top: top}; + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; } function charCoords(cm, pos, context, lineObj) { diff --git a/test/test.js b/test/test.js index d42a76bd41..ec309107bf 100644 --- a/test/test.js +++ b/test/test.js @@ -211,15 +211,18 @@ testCM("coords", function(cm) { testCM("coordsChar", function(cm) { addDoc(cm, 35, 70); - for (var ch = 0; ch <= 35; ch += 5) { - for (var line = 0; line < 70; line += 5) { - cm.setCursor(line, ch); - var coords = cm.charCoords(Pos(line, ch)); - var pos = cm.coordsChar({left: coords.left, top: coords.top + 5}); - eqPos(pos, Pos(line, ch)); + for (var i = 0; i < 2; ++i) { + var sys = i ? "local" : "page"; + for (var ch = 0; ch <= 35; ch += 5) { + for (var line = 0; line < 70; line += 5) { + cm.setCursor(line, ch); + var coords = cm.charCoords(Pos(line, ch), sys); + var pos = cm.coordsChar({left: coords.left + 1, top: coords.top + 1}, sys); + eqPos(pos, Pos(line, ch)); + } } } -}); +}, {lineNumbers: true}); testCM("posFromIndex", function(cm) { cm.setValue( From b800af364642d1e053cfb3e6f2c196bdd1bc1cd0 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 13:39:07 +0200 Subject: [PATCH 033/143] Add a lineAtHeight method Issue #1556 --- doc/manual.html | 7 ++++++- lib/codemirror.js | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/manual.html b/doc/manual.html index 2b3874aaa5..2d78dbe454 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1299,7 +1299,7 @@

Sizing, scrolling and positioning methods

end (false) of the selection, or, if a {line, ch} object is given, it specifies the precise position at which you want to measure. -
cm.charCoords(pos: {line, ch}, mode: string) → {left, right, top, bottom}
+
cm.charCoords(pos: {line, ch}, ?mode: string) → {left, right, top, bottom}
Returns the position and dimensions of an arbitrary character. pos should be a {line, ch} object. This differs from cursorCoords in that @@ -1313,6 +1313,11 @@

Sizing, scrolling and positioning methods

the coordinates are interpreted. It may be "window", "page" (the default), or "local".
+
cm.lineAtHeight(height: number, ?mode: string) → number
+
Computes the line at the given pixel + height. mode can be one of the same strings + that coordsChar + accepts.
cm.defaultTextHeight() → number
Returns the line height of the default font for the editor.
cm.defaultCharWidth() → number
diff --git a/lib/codemirror.js b/lib/codemirror.js index 6b452d699d..191804ccc0 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2851,6 +2851,11 @@ window.CodeMirror = (function() { return coordsChar(this, coords.left, coords.top); }, + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + defaultTextHeight: function() { return textHeight(this.display); }, defaultCharWidth: function() { return charWidth(this.display); }, From fb6df7fb3bb796eb26185e9dda894b0b868f92aa Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 13:47:45 +0200 Subject: [PATCH 034/143] [vim keymap] Fix reliance on bug in coordsChar --- keymap/vim.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 51b93aec30..d29f39cac4 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2811,10 +2811,10 @@ } function getUserVisibleLines(cm) { var scrollInfo = cm.getScrollInfo(); - var occludeTorleranceTop = 6; - var occludeTorleranceBottom = 10; - var from = cm.coordsChar({left:0, top: occludeTorleranceTop}, 'local'); - var bottomY = scrollInfo.clientHeight - occludeTorleranceBottom; + var occludeToleranceTop = 6; + var occludeToleranceBottom = 10; + var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; var to = cm.coordsChar({left:0, top: bottomY}, 'local'); return {top: from.line, bottom: to.line}; } From a8088496ad582499fe7fb9a22b1f6ae66f6b88bd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 14:09:01 +0200 Subject: [PATCH 035/143] [closebrackets addon] Add 'explode' feature Issue #1552 --- addon/edit/closebrackets.js | 40 +++++++++++++++++++++++++++++++++------- doc/manual.html | 12 ++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index 2abc8c5fe6..32819516fb 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -1,23 +1,36 @@ (function() { var DEFAULT_BRACKETS = "()[]{}''\"\""; + var DEFAULT_EXPLODE_ON_ENTER = "[]{}"; var SPACE_CHAR_REGEX = /\s/; CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { - var wasOn = old && old != CodeMirror.Init; - if (val && !wasOn) - cm.addKeyMap(buildKeymap(typeof val == "string" ? val : DEFAULT_BRACKETS)); - else if (!val && wasOn) + if (old != CodeMirror.Init && old) cm.removeKeyMap("autoCloseBrackets"); + if (!val) return; + var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER; + if (typeof val == "string") pairs = val; + else if (typeof val == "object") { + if (val.pairs != null) pairs = val.pairs; + if (val.explode != null) explode = val.explode; + } + var map = buildKeymap(pairs); + if (explode) map.Enter = buildExplodeHandler(explode); + cm.addKeyMap(map); }); + function charsAround(cm, pos) { + var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1), + CodeMirror.Pos(pos.line, pos.ch + 1)); + return str.length == 2 ? str : null; + } + function buildKeymap(pairs) { var map = { name : "autoCloseBrackets", Backspace: function(cm) { if (cm.somethingSelected()) return CodeMirror.Pass; - var cur = cm.getCursor(), line = cm.getLine(cur.line); - if (cur.ch && cur.ch < line.length && - pairs.indexOf(line.slice(cur.ch - 1, cur.ch + 1)) % 2 == 0) + var cur = cm.getCursor(), around = charsAround(cm, cur); + if (around && pairs.indexOf(line.slice(cur.ch - 1, cur.ch + 1)) % 2 == 0) cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1)); else return CodeMirror.Pass; @@ -51,4 +64,17 @@ })(pairs.charAt(i), pairs.charAt(i + 1)); return map; } + + function buildExplodeHandler(pairs) { + return function(cm) { + var cur = cm.getCursor(), around = charsAround(cm, cur); + if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; + cm.operation(function() { + var newPos = CodeMirror.Pos(cur.line + 1, 0); + cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input"); + cm.indentLine(cur.line + 1, null, true); + cm.indentLine(cur.line + 2, null, true); + }); + }; + } })(); diff --git a/doc/manual.html b/doc/manual.html index 2d78dbe454..cc9a1d8b86 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1574,10 +1574,14 @@

Add-ons

edit/closebrackets.js
Defines an option autoCloseBrackets that will auto-close brackets and quotes when typed. By default, it'll - auto-close ()[]{}''"", but you can pass it a - string similar to that (containing pairs of matching characters) - to customize it. Demo - here.
+ auto-close ()[]{}''"", but you can pass it a string + similar to that (containing pairs of matching characters), or an + object with pairs and + optionally explode properties to customize + it. explode should be a similar string that gives + the pairs of characters that, when enter is pressed between + them, should have the second character also moved to its own + line. Demo here.
edit/trailingspace.js
Adds an option showTrailingSpace which, when From a092d987583dbc677deae277707037ef23c1476f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 14:35:21 +0200 Subject: [PATCH 036/143] [show-hint addon] More complete documentation --- doc/manual.html | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index cc9a1d8b86..ffa7c6f09c 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1695,14 +1695,69 @@

Add-ons

hint/show-hint.js
Provides a framework for showing autocompletion hints. Defines CodeMirror.showHint, which takes a - CodeMirror instance and a hinting function, and pops up a widget - that allows the user to select a completion. Hinting functions - are function that take an editor instance, and return + CodeMirror instance, a hinting function, and optionally an + options object, and pops up a widget that allows the user to + select a completion. Hinting functions are function that take an + editor instance and an optional options object, and return a {list, from, to} object, where list - is an array of strings (the completions), and from - and to give the start and end of the token that is - being completed. Depends - on addon/hint/show-hint.css. Check + is an array of strings or objects (the completions), + and from and to give the start and end + of the token that is being completed. When completions aren't + simple strings, they should be objects with the folowing + properties: +
+
text: string
+
The completion text. This is the only required + property.
+
displayText: string
+
The text that should be displayed in the menu.
+
className: string
+
A CSS class name to apply to the completion's line in the + menu.
+
render: fn(Element, self, data)
+
A method used to create the DOM structure for showing the + completion by appending it to its first argument.
+
hint: fn(CodeMirror, self, data)
+
A method used to actually apply the completion, instead of + the default behavior.
+
+ The plugin understands the following options (the options object + will also be passed along to the hinting function, which may + understand additional options): +
+
async: boolean
+
When set to true, the hinting function's signature should + be (cm, callback, ?options), and the completion + interface will only be popped up when the hinting function + calls the callback, passing it the object holding the + completions.
+
completeSingle: boolean
+
Determines whether, when only a single completion is + available, it is completed without showing the dialog. + Defaults to true.
+
alignWithWord: boolean
+
Whether the pop-up should be horizontally aligned with the + start of the word (true, default), or with the cursor (false).
+
customKeys: keymap
+
Allows you to provide a custom keymap of keys to be active + when the pop-up is active. To bind a key to a behavior that is + already present in the default keymap, bind it to the name of + the key in the default keymap (for example "Up").
+
+ The following events will be fired on the completions object + during completion: +
+
"shown" ()
+
Fired when the pop-up is shown.
+
"select" (completion, Element)
+
Fired when a completion is selected. Passed the completion + value (string or object) and the DOM node that represents it + in the menu.
+
"close" ()
+
Fired when the completion is finished.
+
+ This addon depends styles + from addon/hint/show-hint.css. Check out the demo for an example.
From 2e62847db235b3e63a7d75026fb2d6e25e5f798b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 14:47:55 +0200 Subject: [PATCH 037/143] [show-hint addon] Make sure to clear state.completionActive when no match is found --- addon/hint/show-hint.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 504fda786b..a0661e4622 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -24,12 +24,17 @@ CodeMirror.showHint = function(cm, getHints, options) { } function showHints(data) { - if (!data || !data.list.length) return; + if (!data || !data.list.length) { + if (continued) { + cm.state.completionActive = false; + CodeMirror.signal(data, "close"); + } + return; + } + var completions = data.list; - // When there is only one completion, use it directly. - if (!continued && options.completeSingle !== false && completions.length == 1) { + if (!continued && options.completeSingle != false && completions.length == 1) { pickCompletion(cm, data, completions[0]); - CodeMirror.signal(data, "close"); return true; } From 8955ce4817a754674594144b64aa1bac59c227d8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 15:16:50 +0200 Subject: [PATCH 038/143] Add support for "window" coordinate system to charCoords/cursorCoords --- lib/codemirror.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 191804ccc0..a5fc2f2ed3 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1100,6 +1100,9 @@ window.CodeMirror = (function() { cm.display.lineNumChars = null; } + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" function intoCoordSystem(cm, lineObj, rect, context) { if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { @@ -1111,10 +1114,10 @@ window.CodeMirror = (function() { var yOff = heightAtLine(cm, lineObj); if (context == "local") yOff += paddingTop(cm.display); else yOff -= cm.display.viewOffset; - if (context == "page") { + if (context == "page" || context == "window") { var lOff = getRect(cm.display.lineSpace); - yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); - var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); rect.left += xOff; rect.right += xOff; } rect.top += yOff; rect.bottom += yOff; @@ -1128,8 +1131,8 @@ window.CodeMirror = (function() { var left = coords.left, top = coords.top; // First move into "page" coordinate system if (context == "page") { - left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft; - top -= window.pageYOffset || (document.documentElement || document.body).scrollTop; + left -= pageScrollX(); + top -= pageScrollY(); } else if (context == "local" || !context) { var localBox = getRect(cm.display.sizer); left += localBox.left; From 14fc698b95f4229bf4609e84caf2194c9c2c214b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 15:25:58 +0200 Subject: [PATCH 039/143] Remove explicit compensation for paddingTop in scrolling code Local coords now include paddingTop. Issue #1557 --- lib/codemirror.js | 13 ++++++------- test/test.js | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index a5fc2f2ed3..f56c6880e4 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2537,9 +2537,9 @@ window.CodeMirror = (function() { function scrollCursorIntoView(cm) { var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin); if (!cm.state.focused) return; - var display = cm.display, box = getRect(display.sizer), doScroll = null, pTop = paddingTop(cm.display); - if (coords.top + pTop + box.top < 0) doScroll = true; - else if (coords.bottom + pTop + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + var display = cm.display, box = getRect(display.sizer), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; if (doScroll != null && !phantom) { var hidden = display.cursor.style.display == "none"; if (hidden) { @@ -2577,12 +2577,11 @@ window.CodeMirror = (function() { } function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, pt = paddingTop(display); - y1 += pt; y2 += pt; + var display = cm.display, snapMargin = textHeight(cm.display); if (y1 < 0) y1 = 0; var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; var docBottom = cm.doc.height + paddingVert(display); - var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; if (y1 < screentop) { result.scrollTop = atTop ? 0 : y1; } else if (y2 > screentop + screen) { @@ -2950,7 +2949,7 @@ window.CodeMirror = (function() { if (left + node.offsetWidth > hspace) left = hspace - node.offsetWidth; } - node.style.top = (top + paddingTop(display)) + "px"; + node.style.top = top + "px"; node.style.left = node.style.right = ""; if (horiz == "right") { left = display.sizer.clientWidth - node.offsetWidth; diff --git a/test/test.js b/test/test.js index ec309107bf..07e2d67130 100644 --- a/test/test.js +++ b/test/test.js @@ -516,6 +516,24 @@ testCM("scrollSnap", function(cm) { is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom"); }); +testCM("scrollIntoView", function(cm) { + var outer = cm.getWrapperElement().getBoundingClientRect(); + function test(line, ch) { + var pos = Pos(line, ch); + cm.scrollIntoView(pos); + var box = cm.charCoords(pos, "window"); + is(box.left >= outer.left && box.right <= outer.right && + box.top >= outer.top && box.bottom <= outer.bottom); + } + addDoc(cm, 200, 200); + test(199, 199); + test(0, 0); + test(100, 100); + test(199, 0); + test(0, 199); + test(100, 100); +}); + testCM("selectionPos", function(cm) { if (phantom) return; cm.setSize(100, 100); From 97b3110549e3e68a5519cff294929d149bdbff2c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 May 2013 15:36:30 +0200 Subject: [PATCH 040/143] Another test that fails mysteriously on Travis' Phantom (but not on my local one, or in a real browser) --- test/test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test.js b/test/test.js index 07e2d67130..cefe14cc90 100644 --- a/test/test.js +++ b/test/test.js @@ -517,6 +517,7 @@ testCM("scrollSnap", function(cm) { }); testCM("scrollIntoView", function(cm) { + if (phantom) return; var outer = cm.getWrapperElement().getBoundingClientRect(); function test(line, ch) { var pos = Pos(line, ch); From c2f527ecea98b99eb5d21fbf666e672412a0a115 Mon Sep 17 00:00:00 2001 From: Mike Ivanov Date: Thu, 30 May 2013 20:43:09 -0300 Subject: [PATCH 041/143] Fix a non-word --- doc/manual.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index ffa7c6f09c..b36053a4fd 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -757,14 +757,14 @@

Constructor

Content manipulation methods

-
doc.getValue(?seperator: string) → string
+
doc.getValue(?separator: string) → string
Get the current editor content. You can pass it an optional argument to specify the string to be used to separate lines (defaults to "\n").
doc.setValue(content: string)
Set the editor content.
-
doc.getRange(from: {line, ch}, to: {line, ch}, ?seperator: string) → string
+
doc.getRange(from: {line, ch}, to: {line, ch}, ?separator: string) → string
Get the text between the given points in the editor, which should be {line, ch} objects. An optional third argument can be given to indicate the line separator string to From 83709e32d44941bfef4e3f7bce02b68bde9ef663 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 May 2013 10:07:38 +0200 Subject: [PATCH 042/143] Fix bug in measuring of lines with only collapsed elements Closes #1559 --- lib/codemirror.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index f56c6880e4..ab09ad7f2c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4153,9 +4153,8 @@ window.CodeMirror = (function() { } function lineContent(cm, realLine, measure) { - var merged, line = realLine, lineBefore, sawBefore, simple = true; + var merged, line = realLine, lineBefore, sawBefore, empty = true; while (merged = collapsedSpanAtStart(line)) { - simple = false; line = getLine(cm.doc, merged.find().from.line); if (!lineBefore) lineBefore = line; } @@ -4165,6 +4164,7 @@ window.CodeMirror = (function() { if (line.textClass) builder.pre.className = line.textClass; do { + if (line.text) empty = false; builder.measure = line == realLine && measure; builder.pos = 0; builder.addToken = builder.measure ? buildTokenMeasure : buildToken; @@ -4176,14 +4176,11 @@ window.CodeMirror = (function() { } var next = insertLineContent(line, builder, getLineStyles(cm, line)); sawBefore = line == lineBefore; - if (next) { - line = getLine(cm.doc, next.to.line); - simple = false; - } + if (next) line = getLine(cm.doc, next.to.line); } while (next); if (measure && !builder.addedOne) - measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); + measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) builder.pre.appendChild(document.createTextNode("\u00a0")); From f338135f2f35dd18d3f6e948edcaeae393150035 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 May 2013 10:50:39 +0200 Subject: [PATCH 043/143] Support a disableInput property on keymaps Stop suppressing key events for nofallthrough keymaps. Issue #1558 --- keymap/emacs.js | 2 +- keymap/vim.js | 1 + lib/codemirror.js | 21 +++++++++++++-------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/keymap/emacs.js b/keymap/emacs.js index fab3ab9fe6..39bea17dcf 100644 --- a/keymap/emacs.js +++ b/keymap/emacs.js @@ -25,6 +25,6 @@ CodeMirror.keyMap["emacs-Ctrl-X"] = { "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close", - auto: "emacs", nofallthrough: true + auto: "emacs", nofallthrough: true, disableInput: true }; })(); diff --git a/keymap/vim.js b/keymap/vim.js index d29f39cac4..bd1c16c292 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -3208,6 +3208,7 @@ var modifiers = ['Shift', 'Ctrl']; var cmToVimKeymap = { 'nofallthrough': true, + 'disableInput': true, 'style': 'fat-cursor' }; function bindKeys(keys, modifier) { diff --git a/lib/codemirror.js b/lib/codemirror.js index ab09ad7f2c..9a8bd37379 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -236,9 +236,11 @@ window.CodeMirror = (function() { } function keyMapChanged(cm) { - var style = keyMap[cm.options.keyMap].style; + var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); + if (map.disableInput) cm.doc.cantEdit |= cantEdit_keymap; + else cm.doc.cantEdit &= ~cantEdit_keymap; } function themeChanged(cm) { @@ -2003,7 +2005,6 @@ window.CodeMirror = (function() { } else { handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); } - if (handled == "stop") handled = false; if (handled) { e_preventDefault(e); @@ -2485,7 +2486,7 @@ window.CodeMirror = (function() { function skipAtomic(doc, pos, bias, mayClear) { var flipped = false, curPos = pos; var dir = bias || 1; - doc.cantEdit = false; + doc.cantEdit &= ~cantEdit_inatomic; search: for (;;) { var line = getLine(doc, curPos.line); if (line.markedSpans) { @@ -2517,7 +2518,7 @@ window.CodeMirror = (function() { // -- try again *with* clearing, if we didn't already if (!mayClear) return skipAtomic(doc, pos, bias, true); // Otherwise, turn off editing until further notice, and return the start of the doc - doc.cantEdit = true; + doc.cantEdit |= cantEdit_inatomic; return Pos(doc.first, 0); } flipped = true; newPos = pos; dir = -dir; @@ -3428,7 +3429,7 @@ window.CodeMirror = (function() { for (var i = 0; i < maps.length; ++i) { var done = lookup(maps[i]); - if (done) return done; + if (done) return done != "stop"; } } function isModifierKey(event) { @@ -3610,8 +3611,8 @@ window.CodeMirror = (function() { if (min != null && cm) regChange(cm, min, max + 1); this.lines.length = 0; this.explicitlyCleared = true; - if (this.collapsed && this.doc.cantEdit) { - this.doc.cantEdit = false; + if (this.atomic && (this.doc.cantEdit & cantEdit_inatomic)) { + this.doc.cantEdit &= ~cantEdit_inatomic; if (cm) reCheckSelection(cm); } if (withOp) endOperation(cm); @@ -4527,6 +4528,8 @@ window.CodeMirror = (function() { } }; + var cantEdit_inatomic = 1, cantEdit_keymap = 2; + var nextDocId = 0; var Doc = CodeMirror.Doc = function(text, mode, firstLine) { if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); @@ -4535,7 +4538,7 @@ window.CodeMirror = (function() { BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); this.first = firstLine; this.scrollTop = this.scrollLeft = 0; - this.cantEdit = false; + this.cantEdit = 0; this.history = makeHistory(); this.frontier = firstLine; var start = Pos(firstLine, 0); @@ -4783,6 +4786,8 @@ window.CodeMirror = (function() { loadMode(cm); if (!cm.options.lineWrapping) computeMaxLength(cm); cm.options.mode = doc.modeOption; + if (keyMap[cm.options.keyMap].disableInput) doc.cantEdit |= cantEdit_keymap; + else doc.cantEdit &= ~cantEdit_keymap; regChange(cm); } From 63a49817a4522ada322ea81fd8b341d4dd7ee598 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sat, 1 Jun 2013 00:54:26 -0400 Subject: [PATCH 044/143] Change disableInput behavior to allow programmatic edits. --- lib/codemirror.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 9a8bd37379..5f361e4b3e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -239,8 +239,7 @@ window.CodeMirror = (function() { var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); - if (map.disableInput) cm.doc.cantEdit |= cantEdit_keymap; - else cm.doc.cantEdit &= ~cantEdit_keymap; + cm.doc.disableInput = !!map.disableInput; } function themeChanged(cm) { @@ -1423,7 +1422,7 @@ window.CodeMirror = (function() { if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false; var text = input.value; if (text == prevInput && posEq(sel.from, sel.to)) return false; - if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { + if (cm.doc.disableInput || ie && !ie_lt9 && cm.display.inputHasSelection === text) { resetInput(cm, true); return false; } @@ -2486,7 +2485,7 @@ window.CodeMirror = (function() { function skipAtomic(doc, pos, bias, mayClear) { var flipped = false, curPos = pos; var dir = bias || 1; - doc.cantEdit &= ~cantEdit_inatomic; + doc.cantEdit = false; search: for (;;) { var line = getLine(doc, curPos.line); if (line.markedSpans) { @@ -2518,7 +2517,7 @@ window.CodeMirror = (function() { // -- try again *with* clearing, if we didn't already if (!mayClear) return skipAtomic(doc, pos, bias, true); // Otherwise, turn off editing until further notice, and return the start of the doc - doc.cantEdit |= cantEdit_inatomic; + doc.cantEdit = true; return Pos(doc.first, 0); } flipped = true; newPos = pos; dir = -dir; @@ -3611,8 +3610,8 @@ window.CodeMirror = (function() { if (min != null && cm) regChange(cm, min, max + 1); this.lines.length = 0; this.explicitlyCleared = true; - if (this.atomic && (this.doc.cantEdit & cantEdit_inatomic)) { - this.doc.cantEdit &= ~cantEdit_inatomic; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; if (cm) reCheckSelection(cm); } if (withOp) endOperation(cm); @@ -4528,8 +4527,6 @@ window.CodeMirror = (function() { } }; - var cantEdit_inatomic = 1, cantEdit_keymap = 2; - var nextDocId = 0; var Doc = CodeMirror.Doc = function(text, mode, firstLine) { if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); @@ -4538,7 +4535,7 @@ window.CodeMirror = (function() { BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); this.first = firstLine; this.scrollTop = this.scrollLeft = 0; - this.cantEdit = 0; + this.cantEdit = false; this.history = makeHistory(); this.frontier = firstLine; var start = Pos(firstLine, 0); @@ -4786,8 +4783,7 @@ window.CodeMirror = (function() { loadMode(cm); if (!cm.options.lineWrapping) computeMaxLength(cm); cm.options.mode = doc.modeOption; - if (keyMap[cm.options.keyMap].disableInput) doc.cantEdit |= cantEdit_keymap; - else doc.cantEdit &= ~cantEdit_keymap; + doc.disableInput = !!keyMap[cm.options.keyMap].disableInput; regChange(cm); } From c086444347fae9c4bca0d2b98c19b7499539fd57 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 2 Jun 2013 17:42:59 +0200 Subject: [PATCH 045/143] Fixup 63a49817a4522ada322ea81fd8b341d4dd7ee598 Issue #1565 --- lib/codemirror.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 5f361e4b3e..d378040336 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -239,7 +239,7 @@ window.CodeMirror = (function() { var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); - cm.doc.disableInput = !!map.disableInput; + cm.state.disableInput = map.disableInput; } function themeChanged(cm) { @@ -1419,10 +1419,10 @@ window.CodeMirror = (function() { // supported or compatible enough yet to rely on.) function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false; + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; var text = input.value; if (text == prevInput && posEq(sel.from, sel.to)) return false; - if (cm.doc.disableInput || ie && !ie_lt9 && cm.display.inputHasSelection === text) { + if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { resetInput(cm, true); return false; } @@ -4783,7 +4783,6 @@ window.CodeMirror = (function() { loadMode(cm); if (!cm.options.lineWrapping) computeMaxLength(cm); cm.options.mode = doc.modeOption; - doc.disableInput = !!keyMap[cm.options.keyMap].disableInput; regChange(cm); } From 88efc61e32fefde35b845510c6445b9bb1be7bb8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 2 Jun 2013 18:02:37 +0200 Subject: [PATCH 046/143] [closebrackets addon] Fix reference to removed variable Issue #1552 --- addon/edit/closebrackets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index 32819516fb..6fbf38ae67 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -30,7 +30,7 @@ Backspace: function(cm) { if (cm.somethingSelected()) return CodeMirror.Pass; var cur = cm.getCursor(), around = charsAround(cm, cur); - if (around && pairs.indexOf(line.slice(cur.ch - 1, cur.ch + 1)) % 2 == 0) + if (around && pairs.indexOf(around) % 2 == 0) cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1)); else return CodeMirror.Pass; From 5e9bcf71dfe0d449cb045aa6c51404620e5a49e5 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sat, 1 Jun 2013 10:15:02 -0400 Subject: [PATCH 047/143] [vim keymap] Add support for :sort --- keymap/vim.js | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++----- test/vim_test.js | 55 ++++++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 8 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index bd1c16c292..df40bfc02e 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -967,7 +967,9 @@ }, processEx: function(cm, vim, command) { function onPromptClose(input) { - exCommandDispatcher.processCommand(cm, input); + // Give the prompt some time to close so that if processCommand shows + // an error, the elements don't overlap. + window.setTimeout(10, exCommandDispatcher.processCommand(cm, input)); } function onPromptKeyDown(e, input, close) { var keyName = CodeMirror.keyName(e); @@ -1189,7 +1191,7 @@ return null; }, jumpToMark: function(cm, motionArgs, vim) { - var best = cm.getCursor(); + var best = cm.getCursor(); for (var i = 0; i < motionArgs.repeat; i++) { var cursor = best; for (var key in vim.marks) { @@ -2150,7 +2152,7 @@ } }, // TODO: The original Vim implementation only operates on level 1 and 2. - // The current implementation doesn't check for code block level and + // The current implementation doesn't check for code block level and // therefore it operates on any levels. method: { init: function(state) { @@ -2202,7 +2204,7 @@ reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], forward: forward, depth: 0, - curMoveThrough: false + curMoveThrough: false }; var mode = symbolToMode[symb]; if(!mode)return cur; @@ -2828,6 +2830,7 @@ { name: 'write', shortName: 'w', type: 'builtIn' }, { name: 'undo', shortName: 'u', type: 'builtIn' }, { name: 'redo', shortName: 'red', type: 'builtIn' }, + { name: 'sort', shortName: 'sor', type: 'builtIn'}, { name: 'substitute', shortName: 's', type: 'builtIn'}, { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'}, { name: 'delmarks', shortName: 'delm', type: 'builtin'} @@ -2874,7 +2877,11 @@ showConfirm(cm, 'Not an editor command ":' + input + '"'); return; } - exCommands[commandName](cm, params); + try { + exCommands[commandName](cm, params); + } catch(e) { + showConfirm(cm, e); + } }, parseInput_: function(cm, inputStream, result) { inputStream.eatWhile(':'); @@ -2919,7 +2926,7 @@ break; default: inputStream.backUp(1); - return cm.getCursor().line; + return undefined; } }, parseCommandArgs_: function(inputStream, params, command) { @@ -3029,6 +3036,77 @@ linewise: true }, repeatOverride: params.line+1}); }, + sort: function(cm, params) { + var reverse, ignoreCase, unique, number; + function parseArgs() { + if (params.argString) { + var args = new CodeMirror.StringStream(params.argString); + if (args.eat('!')) { reverse = true; } + if (args.eol()) { return; } + if (!args.eatSpace()) { throw 'invalid arguments ' + args.match(/.*/)[0]; } + var opts = args.match(/[a-z]+/); + if (opts) { + opts = opts[0]; + ignoreCase = opts.indexOf('i') != -1; + unique = opts.indexOf('u') != -1; + var decimal = opts.indexOf('d') != -1 && 1; + var hex = opts.indexOf('x') != -1 && 1; + var octal = opts.indexOf('o') != -1 && 1; + if (decimal + hex + octal > 1) { throw 'invalid arguments'; } + number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; + } + if (args.eatSpace() && args.match(/\/.*\//)) { throw 'patterns not supported'; } + } + } + parseArgs(); + var lineStart = params.line || cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + if (lineStart == lineEnd) { return; } + var curStart = { line: lineStart, ch: 0 }; + var curEnd = { line: lineEnd, ch: lineLength(cm, lineEnd) }; + var text = cm.getRange(curStart, curEnd).split('\n'); + var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ : + (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : + (number == 'octal') ? /([0-7]+)/ : null; + var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null; + var numPart = [], textPart = []; + if (number) { + for (var i = 0; i < text.length; i++) { + if (numberRegex.exec(text[i])) { + numPart.push(text[i]); + } else { + textPart.push(text[i]); + } + } + } else { + textPart = text; + } + function compareFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } + var anum = number && numberRegex.exec(a); + var bnum = number && numberRegex.exec(b); + if (!anum) { return a < b ? -1 : 1; } + anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix); + bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix); + return anum - bnum; + } + numPart.sort(compareFn); + textPart.sort(compareFn); + text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); + if (unique) { // Remove duplicate lines + var textOld = text; + var lastLine; + text = [] + for (var i = 0; i < textOld.length; i++) { + if (textOld[i] != lastLine) { + text.push(textOld[i]); + } + lastLine = textOld[i]; + } + } + cm.replaceRange(text.join('\n'), curStart, curEnd); + }, substitute: function(cm, params) { var argString = params.argString; var slashes = findUnescapedSlashes(argString); @@ -3067,7 +3145,7 @@ } var state = getSearchState(cm); var query = state.getQuery(); - var lineStart = params.line || cm.firstLine(); + var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; var lineEnd = params.lineEnd || lineStart; if (count) { lineStart = lineEnd; diff --git a/test/vim_test.js b/test/vim_test.js index f3b53508f6..28c967b8f5 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -11,7 +11,7 @@ var code = '' + ' bufp = buf;\n' + ' }\n' + '\n' + -' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' + +' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' + ' \n' + '}\n'; @@ -1940,6 +1940,59 @@ testVim('ex_write', function(cm, vim, helpers) { } CodeMirror.commands.save = tmp; }); +testVim('ex_sort', function(cm, vim, helpers) { + helpers.doEx('sort'); + eq('Z\na\nb\nc\nd', cm.getValue()); +}, { value: 'b\nZ\nd\nc\na'}); +testVim('ex_sort_reverse', function(cm, vim, helpers) { + helpers.doEx('sort!'); + eq('d\nc\nb\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_range', function(cm, vim, helpers) { + helpers.doEx('2,3sort'); + eq('b\nc\nd\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_oneline', function(cm, vim, helpers) { + helpers.doEx('2sort'); + // Expect no change. + eq('b\nd\nc\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_ignoreCase', function(cm, vim, helpers) { + helpers.doEx('sort i'); + eq('a\nb\nc\nd\nZ', cm.getValue()); +}, { value: 'b\nZ\nd\nc\na'}); +testVim('ex_sort_unique', function(cm, vim, helpers) { + helpers.doEx('sort u'); + eq('Z\na\nb\nc\nd', cm.getValue()); +}, { value: 'b\nZ\na\na\nd\na\nc\na'}); +testVim('ex_sort_decimal', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('d3\n s5\n6\n.9', cm.getValue()); +}, { value: '6\nd3\n s5\n.9'}); +testVim('ex_sort_decimal_negative', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('z-9\nd3\n s5\n6\n.9', cm.getValue()); +}, { value: '6\nd3\n s5\n.9\nz-9'}); +testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! d'); + eq('.9\n6\n s5\nd3', cm.getValue()); +}, { value: '6\nd3\n s5\n.9'}); +testVim('ex_sort_hex', function(cm, vim, helpers) { + helpers.doEx('sort x'); + eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue()); +}, { value: '6\nd3\n s5\n&0xB\n.9'}); +testVim('ex_sort_octal', function(cm, vim, helpers) { + helpers.doEx('sort o'); + eq('.8\n.9\nd3\n s5\n6', cm.getValue()); +}, { value: '6\nd3\n s5\n.9\n.8'}); +testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('y\nz\nc1\nb2\na3', cm.getValue()); +}, { value: 'a3\nz\nc1\ny\nb2'}); +testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! d'); + eq('a3\nb2\nc1\nz\ny', cm.getValue()); +}, { value: 'a3\nz\nc1\ny\nb2'}); testVim('ex_substitute_same_line', function(cm, vim, helpers) { cm.setCursor(1, 0); helpers.doEx('s/one/two'); From 366c18b89eb02b9e6b1e3b7c45e64ae9cb6a71e6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 2 Jun 2013 18:26:44 +0200 Subject: [PATCH 048/143] [show-hint addon] (experiment) Add a container option --- addon/hint/show-hint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index a0661e4622..ab23e97383 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -54,7 +54,7 @@ CodeMirror.showHint = function(cm, getHints, options) { var left = pos.left, top = pos.bottom, below = true; hints.style.left = left + "px"; hints.style.top = top + "px"; - document.body.appendChild(hints); + (options.container || document.body).appendChild(hints); CodeMirror.signal(data, "shown"); // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. From 7c72746248d722fdc8c51f064e18e118d0ae7ad6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Jun 2013 11:36:17 +0200 Subject: [PATCH 049/143] Fix bug that caused zero-length spans to occasionally survive edits Issue #1561 --- lib/codemirror.js | 7 +++++++ test/test.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index d378040336..eab89da931 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3847,6 +3847,13 @@ window.CodeMirror = (function() { } } } + if (sameLine && first) { + // Make sure we didn't create any zero-length spans + for (var i = 0; i < first.length; ++i) + if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") + first.splice(i--, 1); + if (!first.length) first = null; + } var newMarkers = [first]; if (!sameLine) { diff --git a/test/test.js b/test/test.js index cefe14cc90..fe478bb58e 100644 --- a/test/test.js +++ b/test/test.js @@ -450,6 +450,13 @@ testCM("markClearBetween", function(cm) { eq(cm.findMarksAt(Pos(1, 1)).length, 0); }); +testCM("deleteSpanCollapsedInclusiveLeft", function(cm) { + var from = Pos(1, 0), to = Pos(1, 1); + var m = cm.markText(from, to, {collapsed: true, inclusiveLeft: true}); + // Delete collapsed span. + cm.replaceRange("", from, to); +}, {value: "abc\nX\ndef"}); + testCM("bookmark", function(cm) { function p(v) { return v && Pos(v[0], v[1]); } forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]}, From de92ca1bab49a2f18bd6f7e9d8200a9d75270304 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Jun 2013 14:06:42 +0200 Subject: [PATCH 050/143] Allow inline widgets to wrap again, measurement of their start and end --- lib/codemirror.css | 1 - lib/codemirror.js | 55 +++++++++++++++++++++++++++++++++++++----------------- test/test.js | 16 ++++++++++++++++ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/lib/codemirror.css b/lib/codemirror.css index f5379d967c..52881f7dfd 100644 --- a/lib/codemirror.css +++ b/lib/codemirror.css @@ -205,7 +205,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror-widget { - display: inline-block; } .CodeMirror-wrap .CodeMirror-scroll { diff --git a/lib/codemirror.js b/lib/codemirror.js index eab89da931..b32de75c37 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -799,17 +799,17 @@ window.CodeMirror = (function() { function drawForLine(line, fromArg, toArg, retTop) { var lineObj = getLine(doc, line); var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; - function coords(ch) { - return charCoords(cm, Pos(line, ch), "div", lineObj); + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); } iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(from), rightPos, left, right; + var leftPos = coords(from, "left"), rightPos, left, right; if (from == to) { rightPos = leftPos; left = right = leftPos.left; } else { - rightPos = coords(to - 1); + rightPos = coords(to - 1, "right"); if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } left = leftPos.left; right = rightPos.right; @@ -968,7 +968,7 @@ window.CodeMirror = (function() { return e.offsetLeft; } - function measureChar(cm, line, ch, data) { + function measureChar(cm, line, ch, data, bias) { var dir = -1; data = data || measureLine(cm, line); @@ -979,7 +979,8 @@ window.CodeMirror = (function() { } return {left: pos < ch ? r.right : r.left, right: pos > ch ? r.left : r.right, - top: r.top, bottom: r.bottom}; + top: bias == "right" && r.topRight != null ? r.topRight : r.top, + bottom: bias == "right" && r.bottomRight != null ? r.bottomRight : r.bottom}; } function findCachedMeasurement(cm, line) { @@ -1052,9 +1053,9 @@ window.CodeMirror = (function() { if (ie_lt9 && display.measure.first != pre) removeChildrenAndAdd(display.measure, pre); - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { - var size = getRect(cur); - var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot); + function categorizeVSpan(top, bot) { + if (bot > maxBot) bot = maxBot; + if (top < 0) top = 0; for (var j = 0; j < vranges.length; j += 2) { var rtop = vranges[j], rbot = vranges[j+1]; if (rtop > bot || rbot < top) continue; @@ -1063,17 +1064,37 @@ window.CodeMirror = (function() { Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { vranges[j] = Math.min(top, rtop); vranges[j+1] = Math.max(bot, rbot); - break; + return j; } } - if (j == vranges.length) vranges.push(top, bot); + vranges.push(top, bot); + return j; + } + + for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { + var size; + // A widget might wrap, needs special care + if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) { + var rects = cur.getClientRects(), rLeft = rects[0], rRight = rects[rects.length - 1]; + if (rects.length == 1) { + size = rLeft; + } else { + var vCatLeft = categorizeVSpan(rLeft.top - outer.top, rLeft.bottom - outer.top); + var vCatRight = categorizeVSpan(rRight.top - outer.top, rRight.bottom - outer.top); + data[i] = {left: rLeft.left - outer.left, right: rRight.right - outer.left, + top: vCatLeft, topRight: vCatRight}; + continue; + } + } else size = getRect(cur); + var vCat = categorizeVSpan(size.top - outer.top, size.bottom - outer.top); var right = size.right; if (cur.measureRight) right = getRect(cur.measureRight).left; - data[i] = {left: size.left - outer.left, right: right - outer.left, top: j}; + data[i] = {left: size.left - outer.left, right: right - outer.left, top: vCat}; } for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { - var vr = cur.top; + var vr = cur.top, vrRight = cur.topRight; cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; + if (vrRight != null) { cur.topRight = vranges[vrRight]; cur.bottomRight = vranges[vrRight]; } } return data; @@ -1086,7 +1107,7 @@ window.CodeMirror = (function() { if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; } var cached = !hasBadSpan && findCachedMeasurement(cm, line); - if (cached) return measureChar(cm, line, line.text.length, cached.measure).right; + if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right; var pre = lineContent(cm, line); var end = pre.appendChild(zeroWidthElement(cm.display.measure)); @@ -1144,16 +1165,16 @@ window.CodeMirror = (function() { return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; } - function charCoords(cm, pos, context, lineObj) { + function charCoords(cm, pos, context, lineObj, bias) { if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context); } function cursorCoords(cm, pos, context, lineObj, measurement) { lineObj = lineObj || getLine(cm.doc, pos.line); if (!measurement) measurement = measureLine(cm, lineObj); function get(ch, right) { - var m = measureChar(cm, lineObj, ch, measurement); + var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left"); if (right) m.left = m.right; else m.right = m.left; return intoCoordSystem(cm, lineObj, m, context); } diff --git a/test/test.js b/test/test.js index fe478bb58e..8936be8daf 100644 --- a/test/test.js +++ b/test/test.js @@ -786,6 +786,22 @@ testCM("badNestedFold", function(cm) { is(/overlap/i.test(caught.message), "wrong error"); }); +testCM("wrappingInlineWidget", function(cm) { + cm.setSize("10em"); + var w = document.createElement("span"); + w.style.background = "yellow"; + w.innerHTML = "one two three four"; + cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w}); + var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10)); + is(cur0.top < cur1.top); + is(cur0.bottom < cur1.bottom); + var curL = cm.cursorCoords(Pos(0, 6)), curR = cm.cursorCoords(Pos(0, 9)); + eq(curL.top, cur0.top); + eq(curL.bottom, cur0.bottom); + eq(curR.top, cur1.top); + eq(curR.bottom, cur1.bottom); +}, {value: "1 2 3 xxx 4 5 6", lineWrapping: true}); + testCM("inlineWidget", function(cm) { var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")}); cm.setCursor(0, 2); From 0bcc95abe15059403e0bd4d31e839c1267e8b36d Mon Sep 17 00:00:00 2001 From: tfjgeorge Date: Tue, 4 Jun 2013 11:45:39 +0300 Subject: [PATCH 051/143] Update index.html. Fixed typo jsfiddle.net instead of jsfiddle.com --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 0f855ecbb3..59599c46cb 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,7 @@

Usage demos:

  • Light Table
  • Adobe Brackets
  • jsbin.com
  • -
  • jsfiddle.com
  • +
  • jsfiddle.net
  • Bitbucket
  • Google Apps Script
  • Eloquent JavaScript
  • From 9fcee65e590a96b3dff045bf10f7db17d2d38c0d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 4 Jun 2013 11:14:19 +0200 Subject: [PATCH 052/143] Fix bug where measuring the right of a wrapped inline widget broke at end of line --- lib/codemirror.js | 7 ++++--- test/test.js | 10 +++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index b32de75c37..bd5d5d25d3 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -977,10 +977,11 @@ window.CodeMirror = (function() { if (r) break; if (dir < 0 && pos == 0) dir = 1; } + var rightV = (pos < ch || bias == "right") && r.topRight != null; return {left: pos < ch ? r.right : r.left, right: pos > ch ? r.left : r.right, - top: bias == "right" && r.topRight != null ? r.topRight : r.top, - bottom: bias == "right" && r.bottomRight != null ? r.bottomRight : r.bottom}; + top: rightV ? r.topRight : r.top, + bottom: rightV ? r.bottomRight : r.bottom}; } function findCachedMeasurement(cm, line) { @@ -1094,7 +1095,7 @@ window.CodeMirror = (function() { for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { var vr = cur.top, vrRight = cur.topRight; cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; - if (vrRight != null) { cur.topRight = vranges[vrRight]; cur.bottomRight = vranges[vrRight]; } + if (vrRight != null) { cur.topRight = vranges[vrRight]; cur.bottomRight = vranges[vrRight+1]; } } return data; diff --git a/test/test.js b/test/test.js index 8936be8daf..fa8cccdc6f 100644 --- a/test/test.js +++ b/test/test.js @@ -787,9 +787,9 @@ testCM("badNestedFold", function(cm) { }); testCM("wrappingInlineWidget", function(cm) { - cm.setSize("10em"); + cm.setSize("11em"); var w = document.createElement("span"); - w.style.background = "yellow"; + w.style.color = "red"; w.innerHTML = "one two three four"; cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w}); var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10)); @@ -800,7 +800,11 @@ testCM("wrappingInlineWidget", function(cm) { eq(curL.bottom, cur0.bottom); eq(curR.top, cur1.top); eq(curR.bottom, cur1.bottom); -}, {value: "1 2 3 xxx 4 5 6", lineWrapping: true}); + cm.replaceRange("", Pos(0, 9), Pos(0)); + curR = cm.cursorCoords(Pos(0, 9)); + eq(curR.top, cur1.top); + eq(curR.bottom, cur1.bottom); +}, {value: "1 2 3 xxx 4", lineWrapping: true}); testCM("inlineWidget", function(cm) { var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")}); From eafb2cadf67c9e6da560ab948dfdb2cb84baa2e3 Mon Sep 17 00:00:00 2001 From: santec Date: Wed, 5 Jun 2013 08:57:20 +0200 Subject: [PATCH 053/143] [sql mode] Minor fixes * index.html included codemirror.js twice * optimize .tableName syntax check --- mode/sql/index.html | 3 +-- mode/sql/sql.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mode/sql/index.html b/mode/sql/index.html index 8a2495ca24..bc3b3a819c 100644 --- a/mode/sql/index.html +++ b/mode/sql/index.html @@ -5,7 +5,6 @@ SQL Mode for CodeMirror - + + +

    CodeMirror: merge view demo

    + +
    + +

    The merge +addon provides an interface for displaying and merging diffs, +either two-way +or three-way. The left +(or center) pane is editable, and the differences with the other +pane(s) are shown live as you edit it.

    + +

    This addon depends on +the google-diff-match-patch +library to compute the diffs.

    + + diff --git a/doc/manual.html b/doc/manual.html index 4bf94b1c81..2ecd71fbe3 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1957,6 +1957,20 @@

    Add-ons

    Also gives the editor a CodeMirror-empty CSS class whenever it doesn't contain any text. See the demo.
    + +
    merge/merge.js
    +
    Implements an interface for merging changes, using either a + 2-way or a 3-way view. The CodeMirror.MergeView + constructor takes arguments similar to + the CodeMirror + constructor, first a node to append the interface to, and then + an options object. Two extra optional options are + recognized, origLeft and origRight, + which may be strings that provide original versions of the + document, which will be shown to the left and right of the + editor in non-editable CodeMirror instances. The merge interface + will highlight changes between the editable document and the + original(s) (demo).

    Writing CodeMirror Modes

    diff --git a/test/lint/lint.js b/test/lint/lint.js index 5f808922f2..4fc577fa01 100644 --- a/test/lint/lint.js +++ b/test/lint/lint.js @@ -103,7 +103,7 @@ function checkDir(dir) { fs.readdirSync(dir).forEach(function(file) { var fname = dir + "/" + file; if (/\.js$/.test(file)) checkFile(fname); - else if (fs.lstatSync(fname).isDirectory()) checkDir(fname); + else if (file != "dep" && fs.lstatSync(fname).isDirectory()) checkDir(fname); }); } From 4dee296563f3d6e6c1f1d93919e3a4875f60c70a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 19 Jun 2013 19:31:08 +0200 Subject: [PATCH 139/143] [show-hint addon] Fix problem with close event firing too late --- addon/hint/show-hint.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 02c94f4408..35e5cbb721 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -19,7 +19,7 @@ this.cm = cm; this.getHints = getHints; this.options = options; - this.widget = null; + this.widget = this.onClose = null; } Completion.prototype = { @@ -27,6 +27,7 @@ if (!this.active()) return; if (this.widget) this.widget.close(); + if (this.onClose) this.onClose(); this.cm.state.completionActive = null; CodeMirror.signal(this.cm, "endCompletion", this.cm); }, @@ -96,6 +97,7 @@ debounce = setTimeout(update, 170); } this.cm.on("cursorActivity", activity); + this.onClose = done; } }; From 03449c69ffebdac0648a3f4d347cddd365522572 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Wed, 19 Jun 2013 15:53:02 -0400 Subject: [PATCH 140/143] [markdown mode] Fix bug with two links or email addresses on same line. --- mode/markdown/markdown.js | 4 ++-- mode/markdown/test.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index d93d55590e..72b0d6ced2 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -308,11 +308,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return type; } - if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) { + if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { return switchInline(stream, state, inlineElement(linkinline, '>')); } - if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) { + if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { return switchInline(stream, state, inlineElement(linkemail, '>')); } diff --git a/mode/markdown/test.js b/mode/markdown/test.js index c451412fee..99ae056132 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -533,9 +533,15 @@ MT("linkWeb", "[link ] foo"); + MT("linkWebDouble", + "[link ] foo [link ]"); + MT("linkEmail", "[link ] foo"); + MT("linkEmailDouble", + "[link ] foo [link ]"); + MT("emAsterisk", "[em *foo*] bar"); From 112fdb507e33d0e3140124cdc4a689e790b2fec0 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Wed, 19 Jun 2013 16:00:29 -0400 Subject: [PATCH 141/143] [test harness] Escape < and & in test input/results. --- test/mode_test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/mode_test.js b/test/mode_test.js index 79a7752492..c759b257bf 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -84,7 +84,7 @@ var s = ''; if (pass) { s += '
    '; - s += '
    ' + text + '
    '; + s += '
    ' + text.replace('&', '&').replace('<', '<') + '
    '; s += '
    '; s += prettyPrintOutputTable(observedOutput); s += '
    '; @@ -92,7 +92,7 @@ return s; } else { s += '
    '; - s += '
    ' + text + '
    '; + s += '
    ' + text.replace('&', '&').replace('<', '<') + '
    '; s += '
    '; s += 'expected:'; s += prettyPrintOutputTable(expectedOutput); @@ -178,7 +178,7 @@ s += '' + '' + - val.replace(/ /g,'\xb7') + + val.replace(/ /g,'\xb7').replace('&', '&').replace('<', '<') + '' + ''; } From b609e8f0e9041a619ad647d714b6ecdcf908e909 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 20 Jun 2013 10:41:33 +0200 Subject: [PATCH 142/143] Document inputRead and keyHandled events --- doc/manual.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/manual.html b/doc/manual.html index 2ecd71fbe3..ceaad6b44b 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -431,6 +431,17 @@

    Events

    Will be fired when the cursor or selection moves, or any change is made to the editor content.
    +
    "keyHandled" (instance: CodeMirror, name: string, event: Event)
    +
    Fired after a key is handled through a + keymap. name is the name of the handled key (for + example "Ctrl-X" or "'q'"), + and event is the DOM keydown + or keypress event.
    + +
    "inputRead" (instance: CodeMirror, changeObj: object)
    +
    Fired whenever new input is read from the hidden textarea + (typed or pasted by the user).
    +
    "beforeSelectionChange" (instance: CodeMirror, selection: {head, anchor})
    This event is fired before the selection is moved. Its handler may modify the resulting selection head and anchor. From 7e4a1382542273717942961b862dd13da966123b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 20 Jun 2013 10:44:37 +0200 Subject: [PATCH 143/143] Mark release 3.14.0 --- doc/compress.html | 2 ++ doc/manual.html | 10 ++++------ doc/oldrelease.html | 14 ++++++++++++++ index.html | 41 +++++++++++++++++++++++++++-------------- lib/codemirror.js | 2 +- package.json | 2 +- 6 files changed, 49 insertions(+), 22 deletions(-) diff --git a/doc/compress.html b/doc/compress.html index 9c0fafec18..fede6f43fa 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -30,6 +30,7 @@

    Version: