From d894811c14be2f6a74fa31520f74ba1487894054 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 21 Jul 2014 08:52:35 +0200 Subject: [PATCH 01/59] Bump version number post-4.4 --- bower.json | 2 +- doc/manual.html | 2 +- lib/codemirror.js | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index 8c57fcdd4b..3ea24094d7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.4.0", + "version":"4.4.1", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/doc/manual.html b/doc/manual.html index 31d08f5e99..5f1b0dcc46 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -63,7 +63,7 @@

User manual and reference guide - version 4.4.0 + version 4.4.1

CodeMirror is a code-editor component that can be embedded in diff --git a/lib/codemirror.js b/lib/codemirror.js index 95856424c9..96878b219d 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -7795,7 +7795,7 @@ // THE END - CodeMirror.version = "4.4.0"; + CodeMirror.version = "4.4.1"; return CodeMirror; }); diff --git a/package.json b/package.json index d3bcbb7556..467658ddf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.4.0", + "version":"4.4.1", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", From 466319fc1648b6c65d3886ce0f984dc9905fbc56 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 21 Jul 2014 14:56:01 +0200 Subject: [PATCH 02/59] [real-world uses] Add Codeanywhere --- doc/realworld.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/realworld.html b/doc/realworld.html index d730e411b2..5d902a0f6b 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -39,8 +39,7 @@

  • ClickHelp (technical writing tool)
  • CodeWorld (Haskell playground)
  • Complete.ly playground
  • -
  • CrossUI (cross-platform UI builder)
  • -
  • Cruncher (notepad with calculation features)
  • +
  • Codeanywhere (multi-platform cloud editor)
  • Code per Node (Drupal module)
  • Codebug (PHP Xdebug front-end)
  • CodeMirror Eclipse (embed CM in Eclipse)
  • @@ -59,6 +58,8 @@
  • Community Code Camp (code snippet sharing)
  • compilejava.net (online Java sandbox)
  • CKWNC (UML editor)
  • +
  • CrossUI (cross-platform UI builder)
  • +
  • Cruncher (notepad with calculation features)
  • Crudzilla (self-hosted web IDE)
  • CSSDeck (CSS showcase)
  • Deck.js integration (slides with editors)
  • From 2ae5509706110cd679e6640b633a463a2c85b467 Mon Sep 17 00:00:00 2001 From: gekkoe Date: Tue, 22 Jul 2014 06:33:45 -0700 Subject: [PATCH 03/59] [vim] Generalized two-letter ESC sequences for insert mode You can now edit vim.js and set enableEscKeymap to true and tweak any related settings that you see fit and have a two-character sequence to ESC from insert mode. --- keymap/vim.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index a47b005d02..7b980c4222 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -638,7 +638,19 @@ // search history buffer searchHistoryController: new HistoryController({}), // ex Command history buffer - exCommandHistoryController : new HistoryController({}) + exCommandHistoryController : new HistoryController({}), + // To enable a special two-character keymap sequence for ESC, set this to true. + enableEscKeymap: false, + // Use these two variables to customize the two-character ESC keymap. + // If you want to use characters other than i j or k you'll have to add + // lines to the vim-insert and await-second keymaps later in this file. + firstEscCharacter: 'k', + secondEscCharacter: 'j', + // The timeout in milliseconds for the two-character ESC keymap should be + // adjusted according to your typing speed to prevent false positives. + escSequenceTimeout: 100, + // Used by two-character ESC keymap routines. Should not be changed from false here. + awaitingEscapeSecondCharacter: false }; for (var optionName in options) { var option = options[optionName]; @@ -4522,6 +4534,42 @@ } } + function firstEscCharacterHandler(ch){ + if(vimGlobalState.enableEscKeymap === false || vimGlobalState.firstEscCharacter !== ch){ + // This is not the handler you're looking for. Just insert as usual. + return function(cm){ + cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input"); + }; + } else { + return function(cm){ + cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input"); + cm.setOption('keyMap', 'await-second'); + vimGlobalState.awaitingEscapeSecondCharacter = true; + setTimeout(function(){ + if(vimGlobalState.awaitingEscapeSecondCharacter === true) { + vimGlobalState.awaitingEscapeSecondCharacter = false; + cm.setOption('keyMap', 'vim-insert'); + } + }, vimGlobalState.escSequenceTimeout); + }; + } + } + function secondEscCharacterHandler(ch){ + if(vimGlobalState.enableEscKeymap === false || vimGlobalState.secondEscCharacter !== ch) { + // This is not the handler you're looking for. Just insert as usual. + return function(cm){ + cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input"); + }; + } else { + return function(cm) { + vimGlobalState.awaitingEscapeSecondCharacter = false; + cm.replaceRange('', {ch: cm.getCursor().ch - 1, line: cm.getCursor().line}, + cm.getCursor(), "+input"); + exitInsertMode(cm); + }; + } + } + CodeMirror.keyMap['vim-insert'] = { // TODO: override navigation keys so that Esc will cancel automatic // indentation from o, O, i_ @@ -4535,9 +4583,30 @@ CodeMirror.commands.newlineAndIndent; fn(cm); }, + // The next few lines are where you'd add additional handlers if + // you wanted to use keys other than i j and k for two-character + // escape sequences. Don't forget to add them in the await-second + // section as well. + // + // Known Issue: Macro recording becomes polluted with the first + // character of your sequence each time you use it. + // Until this is fixed, use the actual ESC key during + // macro recording. Most likely the removal of the + // first character from the buffer in the function + // secondEscCharacterHandler is what needs fixing. + "'i'": function(cm){var f = firstEscCharacterHandler('i'); f(cm);}, + "'j'": function(cm){var f = firstEscCharacterHandler('j'); f(cm);}, + "'k'": function(cm){var f = firstEscCharacterHandler('k'); f(cm);}, fallthrough: ['default'] }; + CodeMirror.keyMap['await-second'] = { + "'i'": function(cm){var f = secondEscCharacterHandler('i'); f(cm);}, + "'j'": function(cm){var f = secondEscCharacterHandler('j'); f(cm);}, + "'k'": function(cm){var f = secondEscCharacterHandler('k'); f(cm);}, + fallthrough: ['vim-insert'] + }; + CodeMirror.keyMap['vim-replace'] = { 'Backspace': 'goCharLeft', fallthrough: ['vim-insert'] From 4d257877e326fac07f114e43b4b051c9a613ed08 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Tue, 22 Jul 2014 11:23:46 -0700 Subject: [PATCH 04/59] [vim] Add vim options for insert mode esc keys and cleanup --- keymap/vim.js | 83 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 7b980c4222..837c1814bb 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -616,7 +616,9 @@ visualLine: false, visualBlock: false, lastSelection: null, - lastPastedText: null + lastPastedText: null, + // Used by two-character ESC keymap routines. Should not be changed from false here. + awaitingEscapeSecondCharacter: false }; } return cm.state.vim; @@ -638,19 +640,7 @@ // search history buffer searchHistoryController: new HistoryController({}), // ex Command history buffer - exCommandHistoryController : new HistoryController({}), - // To enable a special two-character keymap sequence for ESC, set this to true. - enableEscKeymap: false, - // Use these two variables to customize the two-character ESC keymap. - // If you want to use characters other than i j or k you'll have to add - // lines to the vim-insert and await-second keymaps later in this file. - firstEscCharacter: 'k', - secondEscCharacter: 'j', - // The timeout in milliseconds for the two-character ESC keymap should be - // adjusted according to your typing speed to prevent false positives. - escSequenceTimeout: 100, - // Used by two-character ESC keymap routines. Should not be changed from false here. - awaitingEscapeSecondCharacter: false + exCommandHistoryController : new HistoryController({}) }; for (var optionName in options) { var option = options[optionName]; @@ -4534,39 +4524,48 @@ } } + // Use this option to customize the two-character ESC keymap. + // If you want to use characters other than i j or k you'll have to add + // lines to the vim-insert and await-second keymaps later in this file. + defineOption('enableInsertModeEscKeys', false, 'boolean'); + defineOption('insertModeEscKeys', 'kj', 'string'); + // The timeout in milliseconds for the two-character ESC keymap should be + // adjusted according to your typing speed to prevent false positives. + defineOption('insertModeEscKeysTimeout', 200, 'number'); function firstEscCharacterHandler(ch){ - if(vimGlobalState.enableEscKeymap === false || vimGlobalState.firstEscCharacter !== ch){ - // This is not the handler you're looking for. Just insert as usual. - return function(cm){ - cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input"); - }; - } else { - return function(cm){ + return function(cm){ + var keys = getOption('insertModeEscKeys'); + var firstEscCharacter = keys && keys.length > 1 && keys.charAt(0); + if (!getOption('enableInsertModeEscKeys')|| firstEscCharacter !== ch) { + return CodeMirror.Pass; + } else { cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input"); cm.setOption('keyMap', 'await-second'); - vimGlobalState.awaitingEscapeSecondCharacter = true; - setTimeout(function(){ - if(vimGlobalState.awaitingEscapeSecondCharacter === true) { - vimGlobalState.awaitingEscapeSecondCharacter = false; + cm.state.vim.awaitingEscapeSecondCharacter = true; + setTimeout( + function(){ + if(cm.state.vim.awaitingEscapeSecondCharacter) { + cm.state.vim.awaitingEscapeSecondCharacter = false; cm.setOption('keyMap', 'vim-insert'); } - }, vimGlobalState.escSequenceTimeout); - }; + }, + getOption('insertModeEscKeysTimeout')); + } } } function secondEscCharacterHandler(ch){ - if(vimGlobalState.enableEscKeymap === false || vimGlobalState.secondEscCharacter !== ch) { - // This is not the handler you're looking for. Just insert as usual. - return function(cm){ - cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input"); - }; - } else { - return function(cm) { - vimGlobalState.awaitingEscapeSecondCharacter = false; + return function(cm) { + var keys = getOption('insertModeEscKeys'); + var secondEscCharacter = keys && keys.length > 1 && keys.charAt(1); + if (!getOption('enableInsertModeEscKeys')|| secondEscCharacter !== ch) { + return CodeMirror.Pass; + // This is not the handler you're looking for. Just insert as usual. + } else { + cm.state.vim.awaitingEscapeSecondCharacter = false; cm.replaceRange('', {ch: cm.getCursor().ch - 1, line: cm.getCursor().line}, cm.getCursor(), "+input"); exitInsertMode(cm); - }; + } } } @@ -4594,16 +4593,16 @@ // macro recording. Most likely the removal of the // first character from the buffer in the function // secondEscCharacterHandler is what needs fixing. - "'i'": function(cm){var f = firstEscCharacterHandler('i'); f(cm);}, - "'j'": function(cm){var f = firstEscCharacterHandler('j'); f(cm);}, - "'k'": function(cm){var f = firstEscCharacterHandler('k'); f(cm);}, + "'i'": firstEscCharacterHandler('i'), + "'j'": firstEscCharacterHandler('j'), + "'k'": firstEscCharacterHandler('k'), fallthrough: ['default'] }; CodeMirror.keyMap['await-second'] = { - "'i'": function(cm){var f = secondEscCharacterHandler('i'); f(cm);}, - "'j'": function(cm){var f = secondEscCharacterHandler('j'); f(cm);}, - "'k'": function(cm){var f = secondEscCharacterHandler('k'); f(cm);}, + "'i'": secondEscCharacterHandler('i'), + "'j'": secondEscCharacterHandler('j'), + "'k'": secondEscCharacterHandler('k'), fallthrough: ['vim-insert'] }; From 45fe0a109d02f682ee435a4093843d5124cf95b9 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Tue, 22 Jul 2014 11:46:18 -0700 Subject: [PATCH 05/59] [vim] Fix macro recording for insert mode keys esc --- keymap/vim.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 837c1814bb..0f1f786ea4 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -4527,7 +4527,7 @@ // Use this option to customize the two-character ESC keymap. // If you want to use characters other than i j or k you'll have to add // lines to the vim-insert and await-second keymaps later in this file. - defineOption('enableInsertModeEscKeys', false, 'boolean'); + defineOption('enableInsertModeEscKeys', true, 'boolean'); defineOption('insertModeEscKeys', 'kj', 'string'); // The timeout in milliseconds for the two-character ESC keymap should be // adjusted according to your typing speed to prevent false positives. @@ -4561,6 +4561,12 @@ return CodeMirror.Pass; // This is not the handler you're looking for. Just insert as usual. } else { + if (cm.state.vim.insertMode) { + var lastChange = vimGlobalState.macroModeState.lastInsertModeChanges; + if (lastChange && lastChange.changes.length) { + lastChange.changes.pop(); + } + } cm.state.vim.awaitingEscapeSecondCharacter = false; cm.replaceRange('', {ch: cm.getCursor().ch - 1, line: cm.getCursor().line}, cm.getCursor(), "+input"); From 23302baed4a49e2945d25788c50bb301dab46a1c Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Tue, 22 Jul 2014 11:49:37 -0700 Subject: [PATCH 06/59] [vim] Default insert mode esc keys back to false --- keymap/vim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 0f1f786ea4..b5fa3ab861 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -4527,7 +4527,7 @@ // Use this option to customize the two-character ESC keymap. // If you want to use characters other than i j or k you'll have to add // lines to the vim-insert and await-second keymaps later in this file. - defineOption('enableInsertModeEscKeys', true, 'boolean'); + defineOption('enableInsertModeEscKeys', false, 'boolean'); defineOption('insertModeEscKeys', 'kj', 'string'); // The timeout in milliseconds for the two-character ESC keymap should be // adjusted according to your typing speed to prevent false positives. From e4e7c19551966df7aaf494ec0d01209ed85ec928 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Tue, 22 Jul 2014 14:31:05 -0700 Subject: [PATCH 07/59] [vim] Lint run --- keymap/vim.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index b5fa3ab861..bba5b69b71 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -4532,7 +4532,7 @@ // The timeout in milliseconds for the two-character ESC keymap should be // adjusted according to your typing speed to prevent false positives. defineOption('insertModeEscKeysTimeout', 200, 'number'); - function firstEscCharacterHandler(ch){ + function firstEscCharacterHandler(ch) { return function(cm){ var keys = getOption('insertModeEscKeys'); var firstEscCharacter = keys && keys.length > 1 && keys.charAt(0); @@ -4551,7 +4551,7 @@ }, getOption('insertModeEscKeysTimeout')); } - } + }; } function secondEscCharacterHandler(ch){ return function(cm) { @@ -4572,7 +4572,7 @@ cm.getCursor(), "+input"); exitInsertMode(cm); } - } + }; } CodeMirror.keyMap['vim-insert'] = { From dc97f2a1e9ea3b0e0fe00e2c38ec6e1906d7e501 Mon Sep 17 00:00:00 2001 From: gekkoe Date: Tue, 22 Jul 2014 18:16:24 -0700 Subject: [PATCH 08/59] [vim] Move slightly misplaced comment. --- keymap/vim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index bba5b69b71..235198f4d8 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -4524,10 +4524,10 @@ } } + defineOption('enableInsertModeEscKeys', false, 'boolean'); // Use this option to customize the two-character ESC keymap. // If you want to use characters other than i j or k you'll have to add // lines to the vim-insert and await-second keymaps later in this file. - defineOption('enableInsertModeEscKeys', false, 'boolean'); defineOption('insertModeEscKeys', 'kj', 'string'); // The timeout in milliseconds for the two-character ESC keymap should be // adjusted according to your typing speed to prevent false positives. From 58e1048e4a76ba2547842bffe8b28d2021bb6551 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 23 Jul 2014 11:56:35 +0200 Subject: [PATCH 09/59] Fix problem where a change to the selection causes unwanted history event merging Issue #2717 --- lib/codemirror.js | 10 +++++----- test/test.js | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 96878b219d..b9ddc496b9 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -6495,7 +6495,7 @@ }, changeGeneration: function(forceSplit) { if (forceSplit) - this.history.lastOp = this.history.lastOrigin = null; + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; return this.history.generation; }, isClean: function (gen) { @@ -6812,7 +6812,7 @@ // Used to track when changes can be merged into a single undo // event this.lastModTime = this.lastSelTime = 0; - this.lastOp = null; + this.lastOp = this.lastSelOp = null; this.lastOrigin = this.lastSelOrigin = null; // Used by the isClean() method this.generation = this.maxGeneration = startGen || 1; @@ -6890,7 +6890,7 @@ hist.done.push(selAfter); hist.generation = ++hist.maxGeneration; hist.lastModTime = hist.lastSelTime = time; - hist.lastOp = opId; + hist.lastOp = hist.lastSelOp = opId; hist.lastOrigin = hist.lastSelOrigin = change.origin; if (!last) signal(doc, "historyAdded"); @@ -6916,7 +6916,7 @@ // the current, or the origins don't allow matching. Origins // starting with * are always merged, those starting with + are // merged when similar and close together in time. - if (opId == hist.lastOp || + if (opId == hist.lastSelOp || (origin && hist.lastSelOrigin == origin && (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) @@ -6926,7 +6926,7 @@ hist.lastSelTime = +new Date; hist.lastSelOrigin = origin; - hist.lastOp = opId; + hist.lastSelOp = opId; if (options && options.clearRedo !== false) clearSelectionEvents(hist.undone); } diff --git a/test/test.js b/test/test.js index dcfffb7c07..4e7de08818 100644 --- a/test/test.js +++ b/test/test.js @@ -359,6 +359,15 @@ testCM("undoSelectionAsBefore", function(cm) { eq(cm.getSelection(), "abc"); }); +testCM("selectionChangeConfusesHistory", function(cm) { + cm.replaceSelection("abc", null, "dontmerge"); + cm.operation(function() { + cm.setCursor(Pos(0, 0)); + cm.replaceSelection("abc", null, "dontmerge"); + }); + eq(cm.historySize().undo, 2); +}); + testCM("markTextSingleLine", function(cm) { forEach([{a: 0, b: 1, c: "", f: 2, t: 5}, {a: 0, b: 4, c: "", f: 0, t: 2}, From edb5a983e59c2b89304eb6f4bff6c2e180e82b5e Mon Sep 17 00:00:00 2001 From: binny Date: Tue, 22 Jul 2014 08:27:11 +0530 Subject: [PATCH 10/59] [vim] blockwise insert and append --- keymap/vim.js | 43 +++++++++++++++++++++++++++++++++++-------- test/vim_test.js | 16 ++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 235198f4d8..346f74a37b 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2098,6 +2098,11 @@ vim.insertMode = true; vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; var insertAt = (actionArgs) ? actionArgs.insertAt : null; + if (vim.visualMode) { + var selections = getSelectedAreaRange(cm, vim); + var selectionStart = selections[0]; + var selectionEnd = selections[1]; + } if (insertAt == 'eol') { var cursor = cm.getCursor(); cursor = Pos(cursor.line, lineLength(cm, cursor.line)); @@ -2105,15 +2110,34 @@ } else if (insertAt == 'charAfter') { cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); } else if (insertAt == 'firstNonBlank') { - cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); + if (vim.visualMode && !vim.visualBlock) { + if (selectionEnd.line < selectionStart.line) { + cm.setCursor(selectionEnd); + } else { + selectionStart = Pos(selectionStart.line, 0); + cm.setCursor(selectionStart); + } + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); + } else if (vim.visualBlock) { + selectionEnd = Pos(selectionEnd.line, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + } else { + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); + } } else if (insertAt == 'endOfSelectedArea') { - var selectionEnd = cm.getCursor('head'); - var selectionStart = cm.getCursor('anchor'); - if (selectionEnd.line < selectionStart.line) { + if (vim.visualBlock) { + selectionStart = Pos(selectionStart.line, selectionEnd.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + } else if (selectionEnd.line < selectionStart.line) { selectionEnd = Pos(selectionStart.line, 0); + cm.setCursor(selectionEnd); + } + } else if (insertAt == 'inplace') { + if (vim.visualMode){ + return; } - cm.setCursor(selectionEnd); - exitVisualMode(cm); } cm.setOption('keyMap', 'vim-insert'); cm.setOption('disableInput', false); @@ -2131,6 +2155,9 @@ cm.on('change', onChange); CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); } + if (vim.visualMode) { + exitVisualMode(cm); + } }, toggleVisualMode: function(cm, actionArgs, vim) { var repeat = actionArgs.repeat; @@ -2764,13 +2791,13 @@ updateLastSelection(cm, vim); vim.visualMode = false; vim.visualLine = false; - vim.visualBlock = false; - if (!cursorEqual(selectionStart, selectionEnd)) { + if (!cursorEqual(selectionStart, selectionEnd) && !vim.visualBlock) { // Clear the selection and set the cursor only if the selection has not // already been cleared. Otherwise we risk moving the cursor somewhere // it's not supposed to be. cm.setCursor(clipCursorToContent(cm, selectionEnd)); } + vim.visualBlock = false; CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); if (vim.fakeCursor) { vim.fakeCursor.clear(); diff --git a/test/vim_test.js b/test/vim_test.js index 7c24634a89..d10e364a93 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1214,6 +1214,14 @@ testVim('A', function(cm, vim, helpers) { helpers.assertCursorAt(0, lines[0].length); eq('vim-insert', cm.getOption('keyMap')); }); +testVim('A_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'l', 'A'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('testhello\nmehello\npleahellose', cm.getValue()); +}, {value: 'test\nme\nplease'}); testVim('I', function(cm, vim, helpers) { cm.setCursor(0, 4); helpers.doKeys('I'); @@ -1228,6 +1236,14 @@ testVim('I_repeat', function(cm, vim, helpers) { eq('testtesttestblah', cm.getValue()); helpers.assertCursorAt(0, 11); }, { value: 'blah' }); +testVim('I_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', 'l', 'l', 'I'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('hellotest\nhellome\nhelloplease', cm.getValue()); +}, {value: 'test\nme\nplease'}); testVim('o', function(cm, vim, helpers) { cm.setCursor(0, 4); helpers.doKeys('o'); From 81f771f9b7bf28c9a4fbcb4346467d82152e5c04 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 08:40:04 +0200 Subject: [PATCH 11/59] [ruby mode] Don't treat property names as keywords Closes #2724 --- mode/ruby/ruby.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index e7de7b57f1..6c9977b531 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -141,7 +141,8 @@ CodeMirror.defineMode("ruby", function(config) { } else if (ch == "-" && stream.eat(">")) { return "arrow"; } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { - stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + if (ch == "." && !more) curPunc = "."; return "operator"; } else { return null; @@ -232,20 +233,25 @@ CodeMirror.defineMode("ruby", function(config) { token: function(stream, state) { if (stream.sol()) state.indented = stream.indentation(); var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; + var thisTok = curPunc; if (style == "ident") { var word = stream.current(); - style = keywords.propertyIsEnumerable(stream.current()) ? "keyword" + style = state.lastTok == "." ? "property" + : keywords.propertyIsEnumerable(stream.current()) ? "keyword" : /^[A-Z]/.test(word) ? "tag" : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" : "variable"; - if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; - else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; - else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) - kwtype = "indent"; - else if (word == "do" && state.context.indented < state.indented) - kwtype = "indent"; + if (style == "keyword") { + thisTok = word; + if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; + else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; + else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) + kwtype = "indent"; + else if (word == "do" && state.context.indented < state.indented) + kwtype = "indent"; + } } - if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style; + if (curPunc || (style && style != "comment")) state.lastTok = thisTok; if (curPunc == "|") state.varList = !state.varList; if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) From d20daf6bc1a72d03734687954a237aeb593bfbc1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 08:52:42 +0200 Subject: [PATCH 12/59] Add goLineLeftSmart command Issue #1544 --- doc/manual.html | 9 +++++++-- lib/codemirror.js | 29 ++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 5f1b0dcc46..e2fe2b2964 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -838,12 +838,17 @@

    Module loaders

    goLineEndAlt-Right (PC), Ctrl-E (Mac)
    Move the cursor to the end of the line.
    +
    goLineRightCmd-Right (Mac)
    +
    Move the cursor to the right side of the visual line it is on.
    +
    goLineLeftCmd-Left (Mac)
    Move the cursor to the left side of the visual line it is on. If this line is wrapped, that may not be the start of the line.
    -
    goLineRightCmd-Right (Mac)
    -
    Move the cursor to the right side of the visual line it is on.
    +
    goLineLeftSmart
    +
    Move the cursor to the left side of the visual line it is + on. If that takes it to the start of the line, behave + like goLineStartSmart.
    goLineUpUp, Ctrl-P (Mac)
    Move the cursor up one line.
    diff --git a/lib/codemirror.js b/lib/codemirror.js index b9ddc496b9..5d8c9255ff 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4731,15 +4731,7 @@ }, goLineStartSmart: function(cm) { cm.extendSelectionsBy(function(range) { - var start = lineStart(cm, range.head.line); - var line = cm.getLineHandle(start.line); - var order = getOrder(line); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch; - return Pos(start.line, inWS ? 0 : firstNonWS); - } - return start; + return lineStartSmart(cm, range.head); }, {origin: "+move", bias: 1}); }, goLineEnd: function(cm) { @@ -4758,6 +4750,14 @@ return cm.coordsChar({left: 0, top: top}, "div"); }, sel_move); }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, goLineUp: function(cm) {cm.moveV(-1, "line");}, goLineDown: function(cm) {cm.moveV(1, "line");}, goPageUp: function(cm) {cm.moveV(-1, "page");}, @@ -7556,6 +7556,17 @@ var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); return Pos(lineN == null ? lineNo(line) : lineN, ch); } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } function compareBidiLevel(order, a, b) { var linedir = order[0].level; From de0e0e822f14a9aa21af3b5986b2d329e3734fc2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 08:55:07 +0200 Subject: [PATCH 13/59] [haml mode] Adjust test to correspond to changed ruby mode output --- mode/haml/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mode/haml/test.js b/mode/haml/test.js index 3a6100a352..508458a437 100644 --- a/mode/haml/test.js +++ b/mode/haml/test.js @@ -53,9 +53,9 @@ " [comment -# this is a comment]", " [comment and this is a comment too]", " Date/Time", - " [operator -] [variable now] [operator =] [tag DateTime][operator .][variable now]", + " [operator -] [variable now] [operator =] [tag DateTime][operator .][property now]", " [tag %strong=] [variable now]", - " [operator -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][variable parse]([string \"December 31, 2006\"])", + " [operator -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])", " [operator =][string \"Happy\"]", " [operator =][string \"Belated\"]", " [operator =][string \"Birthday\"]"); From c07ddbbf43f00887f09ea8d01faedb00acd1a018 Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Tue, 15 Jul 2014 18:09:38 -0700 Subject: [PATCH 14/59] Preserve selection after indenting --- lib/codemirror.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 5d8c9255ff..8b2f25c81a 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4070,11 +4070,14 @@ for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (!range.empty()) { - var start = Math.max(end, range.from().line); + var from = range.from(); + var start = Math.max(end, from.line); var to = range.to(); end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; for (var j = start; j < end; ++j) indentLine(this, j, how); + if (from.ch == 0 && to.ch == 0) + replaceOneSelection(this.doc, i, new Range(from, to)); } else if (range.head.line > end) { indentLine(this, range.head.line, how, true); end = range.head.line; From ff472c49392fbeda67a02f06ae9b4b5085aef937 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 12:30:54 +0200 Subject: [PATCH 15/59] Amend c07ddbbf43f00887f09ea8d01faedb00acd1a018 Issue #2715 --- lib/codemirror.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 8b2f25c81a..4898f34e01 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4070,14 +4070,14 @@ for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (!range.empty()) { - var from = range.from(); + var from = range.from(), to = range.to(); var start = Math.max(end, from.line); - var to = range.to(); end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; for (var j = start; j < end; ++j) indentLine(this, j, how); - if (from.ch == 0 && to.ch == 0) - replaceOneSelection(this.doc, i, new Range(from, to)); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to())); } else if (range.head.line > end) { indentLine(this, range.head.line, how, true); end = range.head.line; From 903aab0108948081dfa2485db281778b0b38d7d2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 12:35:05 +0200 Subject: [PATCH 16/59] Fix bad backslashes in wordChars helpers Issue #2721 --- mode/javascript/javascript.js | 2 +- mode/perl/perl.js | 2 +- mode/php/php.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index fdb066eb1f..7523f72920 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -669,7 +669,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { }; }); -CodeMirror.registerHelper("wordChars", "javascript", /[\\w$]/); +CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); CodeMirror.defineMIME("text/javascript", "javascript"); CodeMirror.defineMIME("text/ecmascript", "javascript"); diff --git a/mode/perl/perl.js b/mode/perl/perl.js index ab86e846a4..c12677d249 100644 --- a/mode/perl/perl.js +++ b/mode/perl/perl.js @@ -791,7 +791,7 @@ CodeMirror.defineMode("perl",function(){ return (state.tokenize||tokenPerl)(stream,state);}, electricChars:"{}"};}); -CodeMirror.registerHelper("wordChars", "perl", /[\\w$]/); +CodeMirror.registerHelper("wordChars", "perl", /[\w$]/); CodeMirror.defineMIME("text/x-perl", "perl"); diff --git a/mode/php/php.js b/mode/php/php.js index 72c19d8d11..75b003ff75 100644 --- a/mode/php/php.js +++ b/mode/php/php.js @@ -97,7 +97,7 @@ var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__"; var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once"; CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" ")); - CodeMirror.registerHelper("wordChars", "php", /[\\w$]/); + CodeMirror.registerHelper("wordChars", "php", /[\w$]/); var phpConfig = { name: "clike", From 804498b87bb9a437a69ac4cc7e264d9d96f53918 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 12:40:26 +0200 Subject: [PATCH 17/59] [sublime keymap] Fix bindings for swapLineUp/Down Closes #2723 --- keymap/sublime.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/keymap/sublime.js b/keymap/sublime.js index 7a3a1e5f0e..f70a1f98f2 100644 --- a/keymap/sublime.js +++ b/keymap/sublime.js @@ -17,7 +17,8 @@ var map = CodeMirror.keyMap.sublime = {fallthrough: "default"}; var cmds = CodeMirror.commands; var Pos = CodeMirror.Pos; - var ctrl = CodeMirror.keyMap["default"] == CodeMirror.keyMap.pcDefault ? "Ctrl-" : "Cmd-"; + var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; + var ctrl = mac ? "Cmd-" : "Ctrl-"; // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that. function findPosSubword(doc, start, dir) { @@ -186,7 +187,9 @@ }); }; - cmds[map["Shift-" + ctrl + "Up"] = "swapLineUp"] = function(cm) { + var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-"; + + cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) { var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = []; for (var i = 0; i < ranges.length; i++) { var range = ranges[i], from = range.from().line - 1, to = range.to().line; @@ -212,7 +215,7 @@ }); }; - cmds[map["Shift-" + ctrl + "Down"] = "swapLineDown"] = function(cm) { + cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) { var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1; for (var i = ranges.length - 1; i >= 0; i--) { var range = ranges[i], from = range.to().line + 1, to = range.from().line; From ac1d40005886af31751ed7dbe8147fea45cf385d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 25 Jul 2014 17:04:41 +0200 Subject: [PATCH 18/59] Work around optional-argument bug with updateSelection Closes #2725 --- lib/codemirror.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 4898f34e01..683df8859e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1280,8 +1280,7 @@ return result; } - function updateSelection(cm, drawn) { - if (!drawn) drawn = drawSelection(cm); + function showSelection(cm, drawn) { removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors); removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection); if (drawn.teTop != null) { @@ -1290,6 +1289,10 @@ } } + function updateSelection(cm) { + showSelection(cm, drawSelection(cm)); + } + // Draws a cursor for the given range function drawSelectionCursor(cm, range, output) { var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); @@ -2053,7 +2056,7 @@ } if (op.newSelectionNodes) - updateSelection(cm, op.newSelectionNodes); + showSelection(cm, op.newSelectionNodes); if (op.updatedDisplay) setDocumentHeight(cm, op.barMeasure); if (op.updatedDisplay || op.startHeight != cm.doc.height) From 945360c68c47a22144dcb59f32366a835e31531d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 28 Jul 2014 11:18:19 +0200 Subject: [PATCH 19/59] Fix bug that caused width computation to happen for every operation --- lib/codemirror.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 683df8859e..e9cfd9de9a 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2034,7 +2034,7 @@ // If the max line changed since it was last measured, measure it, // and ensure the document's width matches it. - // updateDisplayIfNeeded will use these properties to do the actual resizing + // updateDisplay_W2 will use these properties to do the actual resizing if (display.maxLineChanged && !cm.options.lineWrapping) { op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left; op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + @@ -2053,6 +2053,7 @@ cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; if (op.maxScrollLeft < cm.doc.scrollLeft) setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; } if (op.newSelectionNodes) From 2f14f52e24b35323081878d598465f390db1e955 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 28 Jul 2014 11:50:40 +0200 Subject: [PATCH 20/59] Fix problem caused by ordering of scroll width measurement and width adjustment Closes #2730 --- lib/codemirror.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index e9cfd9de9a..1ed1391194 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2032,16 +2032,19 @@ var cm = op.cm, display = cm.display; if (op.updatedDisplay) updateHeightsInViewport(cm); + op.barMeasure = measureForScrollbars(cm); + // If the max line changed since it was last measured, measure it, // and ensure the document's width matches it. // updateDisplay_W2 will use these properties to do the actual resizing if (display.maxLineChanged && !cm.options.lineWrapping) { - op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left; + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + scrollerCutOff - display.scroller.clientWidth); + var wDiff = op.adjustWidthTo - parseInt(display.sizer.style.minWidth || "0"); + op.barMeasure.scrollWidth += wDiff; } - op.barMeasure = measureForScrollbars(cm); if (op.updatedDisplay || op.selectionChanged) op.newSelectionNodes = drawSelection(cm); } From fce7eb4b66d0c0deb6c7b24d2d2bf56377a55fba Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 28 Jul 2014 21:33:46 +0200 Subject: [PATCH 21/59] Fix initialization problem introduced by 2f14f52e24b35 --- lib/codemirror.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 1ed1391194..9cfc6dc6be 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2041,8 +2041,9 @@ op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + scrollerCutOff - display.scroller.clientWidth); - var wDiff = op.adjustWidthTo - parseInt(display.sizer.style.minWidth || "0"); - op.barMeasure.scrollWidth += wDiff; + var prevMinWidth = display.sizer.style.minWidth; + if (prevMinWidth) + op.barMeasure.scrollWidth += op.adjustWidthTo - parseInt(prevMinWidth); } if (op.updatedDisplay || op.selectionChanged) From 75e63beef2b8a1542dab7783296d024bc3dd41c8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 28 Jul 2014 22:00:53 +0200 Subject: [PATCH 22/59] Another fix to the width measuring kludge --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 9cfc6dc6be..4dc2a7eaf8 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2042,7 +2042,7 @@ op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + scrollerCutOff - display.scroller.clientWidth); var prevMinWidth = display.sizer.style.minWidth; - if (prevMinWidth) + if (prevMinWidth && op.barMeasure.scrollWidth > op.barMeasure.clientWidth) op.barMeasure.scrollWidth += op.adjustWidthTo - parseInt(prevMinWidth); } From 6e3b8ed1054a2d57907ab259cdec93f7cdeffbc0 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 28 Jul 2014 22:03:06 +0200 Subject: [PATCH 23/59] [closetag addon] Reindent line when a tag is closed by / Issue #2735 --- addon/edit/closetag.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 414498bcd9..69ea4446be 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -109,6 +109,10 @@ replacements[i] = "/" + state.context.tagName + ">"; } cm.replaceSelections(replacements); + ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) + if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line) + cm.indentLine(ranges[i].head.line); } function indexOf(collection, elt) { From b5e19db4d330ce55db369e6d0c9c4f71d3664bc3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 29 Jul 2014 09:58:54 +0200 Subject: [PATCH 24/59] [clojure mode] Fix null dereference bug --- mode/clojure/clojure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/clojure/clojure.js b/mode/clojure/clojure.js index f81bd2c140..73bfcd39b5 100644 --- a/mode/clojure/clojure.js +++ b/mode/clojure/clojure.js @@ -114,7 +114,7 @@ CodeMirror.defineMode("clojure", function (options) { var first = stream.next(); // Read special literals: backspace, newline, space, return. // Just read all lowercase letters. - if (first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) { + if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) { return; } // Read unicode character: \u1000 \uA0a1 From 70ebf9f6674469d97392aaf932a993cbca9364e4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 29 Jul 2014 10:40:24 +0200 Subject: [PATCH 25/59] Sanitize scrolling code, make it deal with being asked to show rect larger than view Issue #2736 --- lib/codemirror.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 4dc2a7eaf8..adad6a25f6 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3750,6 +3750,7 @@ if (y1 < 0) y1 = 0; var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; var screen = display.scroller.clientHeight - scrollerCutOff, result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; var docBottom = cm.doc.height + paddingVert(display); var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; if (y1 < screentop) { @@ -3761,15 +3762,15 @@ var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; var screenw = display.scroller.clientWidth - scrollerCutOff; - x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; - var gutterw = display.gutters.offsetWidth; - var atLeft = x1 < gutterw + 10; - if (x1 < screenleft + gutterw || atLeft) { - if (atLeft) x1 = 0; - result.scrollLeft = Math.max(0, x1 - 10 - gutterw); - } else if (x2 > screenw + screenleft - 3) { - result.scrollLeft = x2 + 10 - screenw; - } + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = y1 + screen; + if (x1 < display.gutters.offsetWidth + 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + return result; } From 95ec53662a6bf47182f69faa372c6d86d3cab630 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 29 Jul 2014 10:43:07 +0200 Subject: [PATCH 26/59] Scroll correct positions into view after undo/redo Issue #2736 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index adad6a25f6..e363c5ed21 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3561,7 +3561,7 @@ var after = i ? computeSelAfterChange(doc, change) : lst(source); makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - if (!i && doc.cm) doc.cm.scrollIntoView(change); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); var rebased = []; // Propagate to the linked documents From d17eadeb9804ee10e65eeec96e611c3fcfdf607c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 29 Jul 2014 10:52:12 +0200 Subject: [PATCH 27/59] [xml-fold addon] Make doMatchTags also return info about self-closing tags This way, the matchtags addon highlights them the same as normal tags. Closes #2737 --- addon/fold/xml-fold.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js index a45da58422..504727f38c 100644 --- a/addon/fold/xml-fold.js +++ b/addon/fold/xml-fold.js @@ -151,8 +151,9 @@ if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); var start = end && toTagStart(iter); - if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return; + if (!end || !start || cmp(iter, pos) > 0) return; var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; + if (end == "selfClose") return {open: here, close: null, at: "open"}; if (start[1]) { // closing tag return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; From 996b37a281aaee8e7698892fba0739437b6df101 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 29 Jul 2014 11:34:12 +0200 Subject: [PATCH 28/59] Also update line heights from updateDisplaySimple Issue #2726 --- lib/codemirror.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index e363c5ed21..5bb1677afc 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -662,6 +662,7 @@ function updateDisplaySimple(cm, viewport) { var update = new DisplayUpdate(cm, viewport); if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); postUpdateDisplay(cm, update); var barMeasure = measureForScrollbars(cm); updateSelection(cm); From 41bd14d66caf5ae948234fd2f9b0c6dbc02413fa Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 29 Jul 2014 15:29:59 +0200 Subject: [PATCH 29/59] [real-world uses] Add IPython --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index 5d902a0f6b..e5ae339817 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -88,6 +88,7 @@
  • HaxPad (editor for Win RT)
  • Histone template engine playground
  • ICEcoder (web IDE)
  • +
  • IPython (interactive computing shell)
  • i-MOS (modeling and simulation platform)
  • Janvas (vector graphics editor)
  • Joomla plugin
  • From a7117799ba3a6efd89c15459e07ebf5e4284e1ca Mon Sep 17 00:00:00 2001 From: binny Date: Tue, 22 Jul 2014 05:18:38 +0530 Subject: [PATCH 30/59] [vim] replay change for blockwise visual --- keymap/vim.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++++------- test/vim_test.js | 13 +++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 346f74a37b..30a1396275 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1862,6 +1862,12 @@ var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; var text = cm.getSelection(); + var visualBlock = vim.visualBlock; + if (vim.lastSelection && !vim.visualMode) { + visualBlock = vim.lastSelection.visualBlock ? true : visualBlock; + } + var lastInsertModeChanges = vimGlobalState.macroModeState.lastInsertModeChanges; + lastInsertModeChanges.inVisualBlock = visualBlock; var replacement = new Array(selections.length).join('1').split('1'); // save the selectionEnd mark var selectionEnd = vim.marks['>'] ? vim.marks['>'].find() : cm.getCursor('head'); @@ -1870,7 +1876,7 @@ operatorArgs.linewise); if (operatorArgs.linewise) { // 'C' in visual block extends the block till eol for all lines - if (vim.visualBlock){ + if (visualBlock){ var startLine = curStart.line; while (startLine <= curEnd.line) { var endCh = lineLength(cm, startLine); @@ -1900,7 +1906,7 @@ curEnd = offsetCursor(curEnd, 0, - match[0].length); } } - if (vim.visualBlock) { + if (visualBlock) { cm.replaceSelections(replacement); } else { cm.setCursor(curStart); @@ -4527,7 +4533,32 @@ var macroModeState = vimGlobalState.macroModeState; var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.'); var isPlaying = macroModeState.isPlaying; + var lastChange = macroModeState.lastInsertModeChanges; + // In case of visual block, the insertModeChanges are not saved as a + // single word, so we convert them to a single word + // so as to update the ". register as expected in real vim. + var text = []; if (!isPlaying) { + var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1; + var changes = lastChange.changes; + var text = []; + var i = 0; + // In case of multiple selections in blockwise visual, + // the inserted text, for example: 'foo', is stored as + // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines). + // We push the contents of the changes array as per the following: + // 1. In case of InsertModeKey, just increment by 1. + // 2. In case of a character, jump by selLength (2 in the example). + while (i < changes.length) { + // This loop will convert 'ffoooo' to 'foo'. + text.push(changes[i]); + if (changes[i] instanceof InsertModeKey) { + i++; + } else { + i+= selLength; + } + } + lastChange.changes = text; cm.off('change', onChange); CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); } @@ -4544,7 +4575,7 @@ cm.setOption('disableInput', true); cm.toggleOverwrite(false); // exit replace mode if we were in it. // update the ". register before exiting insert mode - insertModeChangeRegister.setText(macroModeState.lastInsertModeChanges.changes.join('')); + insertModeChangeRegister.setText(lastChange.changes.join('')); CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); if (macroModeState.isRecording) { logInsertModeChange(macroModeState); @@ -4811,11 +4842,7 @@ // insert mode changes. Will conform to that behavior. repeat = !vim.lastEditActionCommand ? 1 : repeat; var changeObject = macroModeState.lastInsertModeChanges; - // This isn't strictly necessary, but since lastInsertModeChanges is - // supposed to be immutable during replay, this helps catch bugs. - macroModeState.lastInsertModeChanges = {}; repeatInsertModeChanges(cm, changeObject.changes, repeat); - macroModeState.lastInsertModeChanges = changeObject; } } vim.inputState = vim.lastEditInputState; @@ -4853,6 +4880,18 @@ } return true; } + var curStart = cm.getCursor(); + var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock; + if (inVisualBlock) { + // Set up block selection again for repeating the changes. + var vim = cm.state.vim; + var block = vim.lastSelection.visualBlock; + var curEnd = Pos(curStart.line + block.height-1, curStart.ch); + cm.setCursor(curStart); + selectBlock(cm, curEnd); + repeat = cm.listSelections().length; + cm.setCursor(curStart); + } for (var i = 0; i < repeat; i++) { for (var j = 0; j < changes.length; j++) { var change = changes[j]; @@ -4863,6 +4902,10 @@ cm.replaceRange(change, cur, cur); } } + if (inVisualBlock) { + curStart.line++; + cm.setCursor(curStart); + } } } diff --git a/test/vim_test.js b/test/vim_test.js index d10e364a93..6b9bbac17a 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -909,6 +909,19 @@ testVim('c_visual_block', function(cm, vim, helpers) { cm.replaceSelections(replacement); eq('1hworld\n5hworld\nahworld', cm.getValue()); }, {value: '1234\n5678\nabcdefg'}); +testVim('c_visual_block_replay', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'c'); + var replacement = new Array(cm.listSelections().length+1).join('fo ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('1fo4\n5fo8\nafodefg', cm.getValue()); + helpers.doInsertModeKeys('Esc'); + cm.setCursor(0, 0); + helpers.doKeys('.'); + eq('foo4\nfoo8\nfoodefg', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); + // Swapcase commands edit in place and do not modify registers. testVim('g~w_repeat', function(cm, vim, helpers) { // Assert that dw does delete newline if it should go to the next line, and From 59691404418843b5edd05a0478ade03557b2cd84 Mon Sep 17 00:00:00 2001 From: binny Date: Fri, 1 Aug 2014 03:11:30 +0530 Subject: [PATCH 31/59] [vim] remove visualBlock check in exitVisualMode --- keymap/vim.js | 7 ++----- test/vim_test.js | 5 +++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 30a1396275..4065f77383 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2797,13 +2797,10 @@ updateLastSelection(cm, vim); vim.visualMode = false; vim.visualLine = false; - if (!cursorEqual(selectionStart, selectionEnd) && !vim.visualBlock) { - // Clear the selection and set the cursor only if the selection has not - // already been cleared. Otherwise we risk moving the cursor somewhere - // it's not supposed to be. + vim.visualBlock = false; + if (!cursorEqual(selectionStart, selectionEnd)) { cm.setCursor(clipCursorToContent(cm, selectionEnd)); } - vim.visualBlock = false; CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); if (vim.fakeCursor) { vim.fakeCursor.clear(); diff --git a/test/vim_test.js b/test/vim_test.js index 6b9bbac17a..32c4fa6fc8 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1651,6 +1651,11 @@ testVim('visual', function(cm, vim, helpers) { helpers.doKeys('d'); eq('15', cm.getValue()); }, { value: '12345' }); +testVim('visual_exit', function(cm, vim, helpers) { + helpers.doKeys('', 'l', 'j', 'j', ''); + eq(cm.getCursor('anchor'), cm.getCursor('head')); + eq(vim.visualMode, false); +}, { value: 'hello\nworld\nfoo' }); testVim('visual_line', function(cm, vim, helpers) { helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd'); eq(' 4\n 5', cm.getValue()); From b4ecdded6357d0364fc9a51240675c9f040f039c Mon Sep 17 00:00:00 2001 From: gekkoe Date: Mon, 4 Aug 2014 20:26:23 -0700 Subject: [PATCH 32/59] [vim] Removed defunct comment. @mightyguava fixed this issue in https://github.com/marijnh/CodeMirror/commit/45fe0a109d02f682ee435a4093843d5124cf95b9 --- keymap/vim.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 4065f77383..a2d38719a1 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -4647,13 +4647,6 @@ // you wanted to use keys other than i j and k for two-character // escape sequences. Don't forget to add them in the await-second // section as well. - // - // Known Issue: Macro recording becomes polluted with the first - // character of your sequence each time you use it. - // Until this is fixed, use the actual ESC key during - // macro recording. Most likely the removal of the - // first character from the buffer in the function - // secondEscCharacterHandler is what needs fixing. "'i'": firstEscCharacterHandler('i'), "'j'": firstEscCharacterHandler('j'), "'k'": firstEscCharacterHandler('k'), From a7201ac119645198023ec2a44eeeec968a2f419d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 9 Aug 2014 12:49:06 +0200 Subject: [PATCH 33/59] Fix gutter width compensation in calculateScrollPos Issue #2745 --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 5bb1677afc..d7eb25e797 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3762,10 +3762,10 @@ } var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; - var screenw = display.scroller.clientWidth - scrollerCutOff; + var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth; var tooWide = x2 - x1 > screenw; if (tooWide) x2 = y1 + screen; - if (x1 < display.gutters.offsetWidth + 10) + if (x1 < 10) result.scrollLeft = 0; else if (x1 < screenleft) result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); From a183fa6bb190d6bacb287c3efe5da078b7b5bc74 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 9 Aug 2014 13:07:15 +0200 Subject: [PATCH 34/59] Less fragile way to deal with updating the scrollbar during editor width changes Issue #2745 --- lib/codemirror.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index d7eb25e797..882b17b925 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2042,9 +2042,6 @@ op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + scrollerCutOff - display.scroller.clientWidth); - var prevMinWidth = display.sizer.style.minWidth; - if (prevMinWidth && op.barMeasure.scrollWidth > op.barMeasure.clientWidth) - op.barMeasure.scrollWidth += op.adjustWidthTo - parseInt(prevMinWidth); } if (op.updatedDisplay || op.selectionChanged) @@ -2077,6 +2074,9 @@ function endOperation_finish(op) { var cm = op.cm, display = cm.display, doc = cm.doc; + if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1) + updateScrollbars(cm); + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); // Abort mouse wheel delta measurement, when scrolling explicitly From 9de315b8a480c0d85625ead4453b30bf9934b8f6 Mon Sep 17 00:00:00 2001 From: binny Date: Wed, 6 Aug 2014 01:12:38 +0530 Subject: [PATCH 35/59] [vim] blockwise paste --- keymap/vim.js | 109 ++++++++++++++++++++++++++++++++++++++++--------------- test/vim_test.js | 36 ++++++++++++++++++ 2 files changed, 116 insertions(+), 29 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index a2d38719a1..0344e8a70a 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -787,17 +787,19 @@ * pasted, should it insert itself into a new line, or should the text be * inserted at the cursor position.) */ - function Register(text, linewise) { + function Register(text, linewise, blockwise) { this.clear(); this.keyBuffer = [text || '']; this.insertModeChanges = []; this.searchQueries = []; this.linewise = !!linewise; + this.blockwise = !!blockwise; } Register.prototype = { - setText: function(text, linewise) { + setText: function(text, linewise, blockwise) { this.keyBuffer = [text || '']; this.linewise = !!linewise; + this.blockwise = !!blockwise; }, pushText: function(text, linewise) { // if this register has ever been set to linewise, use linewise. @@ -842,7 +844,7 @@ registers['/'] = new Register(); } RegisterController.prototype = { - pushText: function(registerName, operator, text, linewise) { + pushText: function(registerName, operator, text, linewise, blockwise) { if (linewise && text.charAt(0) == '\n') { text = text.slice(1) + '\n'; } @@ -859,7 +861,7 @@ switch (operator) { case 'yank': // The 0 register contains the text from the most recent yank. - this.registers['0'] = new Register(text, linewise); + this.registers['0'] = new Register(text, linewise, blockwise); break; case 'delete': case 'change': @@ -875,7 +877,7 @@ break; } // Make sure the unnamed register is set to what just happened - this.unnamedRegister.setText(text, linewise); + this.unnamedRegister.setText(text, linewise, blockwise); return; } @@ -884,7 +886,7 @@ if (append) { register.pushText(text, linewise); } else { - register.setText(text, linewise); + register.setText(text, linewise, blockwise); } // The unnamed register always has the same value as the last used // register. @@ -1924,17 +1926,19 @@ var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; // Save the '>' mark before cm.replaceRange clears it. var selectionEnd, selectionStart; + var blockwise = vim.visualBlock; if (vim.visualMode) { selectionEnd = vim.marks['>'].find(); selectionStart = vim.marks['<'].find(); } else if (vim.lastSelection) { selectionEnd = vim.lastSelection.curStartMark.find(); selectionStart = vim.lastSelection.curEndMark.find(); + blockwise = vim.lastSelection.visualBlock; } var text = cm.getSelection(); vimGlobalState.registerController.pushText( operatorArgs.registerName, 'delete', text, - operatorArgs.linewise); + operatorArgs.linewise, blockwise); var replacement = new Array(selections.length).join('1').split('1'); // If the ending line is past the last line, inclusive, instead of // including the trailing \n, include the \n before the starting line @@ -2007,11 +2011,11 @@ cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd); } }, - yank: function(cm, operatorArgs, _vim, _curStart, _curEnd, curOriginal) { + yank: function(cm, operatorArgs, vim, _curStart, _curEnd, curOriginal) { var text = cm.getSelection(); vimGlobalState.registerController.pushText( operatorArgs.registerName, 'yank', - text, operatorArgs.linewise); + text, operatorArgs.linewise, vim.visualBlock); cm.setCursor(curOriginal); } }; @@ -2374,6 +2378,7 @@ var text = Array(actionArgs.repeat + 1).join(text); } var linewise = register.linewise; + var blockwise = register.blockwise; if (linewise) { if(vim.visualMode) { text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; @@ -2386,6 +2391,7 @@ cur.ch = 0; } } else { + text = blockwise ? text.split('\n') : text; cur.ch += actionArgs.after ? 1 : 0; } var curPosFinal; @@ -2397,32 +2403,71 @@ var selectedArea = getSelectedAreaRange(cm, vim); var selectionStart = selectedArea[0]; var selectionEnd = selectedArea[1]; + var selectedText = cm.getSelection(); + var selections = cm.listSelections(); + var emptyStrings = new Array(selections.length).join('1').split('1'); // save the curEnd marker before it get cleared due to cm.replaceRange. - if (vim.lastSelection) lastSelectionCurEnd = vim.lastSelection.curEndMark.find(); + if (vim.lastSelection) { + lastSelectionCurEnd = vim.lastSelection.curEndMark.find(); + } // push the previously selected text to unnamed register - vimGlobalState.registerController.unnamedRegister.setText(cm.getRange(selectionStart, selectionEnd)); - cm.replaceRange(text, selectionStart, selectionEnd); + vimGlobalState.registerController.unnamedRegister.setText(selectedText); + if (blockwise) { + // first delete the selected text + cm.replaceSelections(emptyStrings); + // Set new selections as per the block length of the yanked text + selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + cm.replaceSelections(text); + curPosFinal = selectionStart; + } else if (vim.visualBlock) { + cm.replaceSelections(emptyStrings); + cm.setCursor(selectionStart); + cm.replaceRange(text, selectionStart, selectionStart); + curPosFinal = selectionStart; + } else { + cm.replaceRange(text, selectionStart, selectionEnd); + curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); + } // restore the the curEnd marker - if(lastSelectionCurEnd) vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd); - curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); - if(linewise)curPosFinal.ch=0; + if(lastSelectionCurEnd) { + vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd); + } + if (linewise) { + curPosFinal.ch=0; + } } else { - cm.replaceRange(text, cur); - // Now fine tune the cursor to where we want it. - if (linewise && actionArgs.after) { - curPosFinal = Pos( + if (blockwise) { + cm.setCursor(cur); + for (var i = 0; i < text.length; i++) { + var lastCh = lineLength(cm, cur.line+i); + if (lastCh < cur.ch) { + extendLineToColumn(cm, cur.line+i, cur.ch); + } + } + cm.setCursor(cur); + selectBlock(cm, Pos(cur.line + text.length-1, cur.ch)); + cm.replaceSelections(text); + curPosFinal = cur; + } else { + cm.replaceRange(text, cur); + // Now fine tune the cursor to where we want it. + if (linewise && actionArgs.after) { + curPosFinal = Pos( cur.line + 1, findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); - } else if (linewise && !actionArgs.after) { - curPosFinal = Pos( - cur.line, - findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); - } else if (!linewise && actionArgs.after) { - idx = cm.indexFromPos(cur); - curPosFinal = cm.posFromIndex(idx + text.length - 1); - } else { - idx = cm.indexFromPos(cur); - curPosFinal = cm.posFromIndex(idx + text.length); + } else if (linewise && !actionArgs.after) { + curPosFinal = Pos( + cur.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); + } else if (!linewise && actionArgs.after) { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length - 1); + } else { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length); + } } } cm.setCursor(curPosFinal); @@ -2632,6 +2677,12 @@ function escapeRegex(s) { return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); } + function extendLineToColumn(cm, lineNum, column) { + var endCh = lineLength(cm, lineNum); + var spaces = new Array(column-endCh+1).join(' '); + cm.setCursor(Pos(lineNum, endCh)); + cm.replaceRange(spaces, cm.getCursor()); + } // This functions selects a rectangular block // of text with selectionEnd as any of its corner // Height of block: diff --git a/test/vim_test.js b/test/vim_test.js index 32c4fa6fc8..ce666721d0 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1865,6 +1865,42 @@ testVim('S_normal', function(cm, vim, helpers) { helpers.assertCursorAt(1, 0); eq('aa\n\ncc', cm.getValue()); }, { value: 'aa\nbb\ncc'}); +testVim('blockwise_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '3', 'j', 'l', 'y'); + cm.setCursor(0, 2); + // paste one char after the current cursor position + helpers.doKeys('p'); + eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue()); + cm.setCursor(0, 0); + helpers.doKeys('v', '4', 'l', 'y'); + cm.setCursor(0, 0); + helpers.doKeys('', '3', 'j', 'p'); + eq('helheelhelo\norwold\noofo\narba', cm.getValue()); +}, { value: 'hello\nworld\nfoo\nbar'}); +testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) { + // extend short lines in case of different line lengths. + cm.setCursor(0, 0); + helpers.doKeys('', 'j', 'j', 'y'); + cm.setCursor(0, 3); + helpers.doKeys('p'); + eq('hellho\nfoo f\nbar b', cm.getValue()); +}, { value: 'hello\nfoo\nbar'}); +testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', 'x'); + cm.setCursor(0, 0); + helpers.doKeys('P'); + eq('cut\nand\npaste\nme', cm.getValue()); +}, { value: 'cut\nand\npaste\nme'}); +testVim('blockwise_paste_from_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('', '2', 'j', '"', 'a', 'y'); + cm.setCursor(0, 3); + helpers.doKeys('"', 'a', 'p'); + eq('foobfar\nhellho\nworlwd', cm.getValue()); +}, { value: 'foobar\nhello\nworld'}); + testVim('S_visual', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('v', 'j', 'S'); From 1a58ac568989ffdc24dffdff8440e922b7055f9e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 13:42:41 +0200 Subject: [PATCH 36/59] Bump supported Firefox to 4 Range.getBoundingClientRect is not supported in FF 3.x --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 19f0365a38..dce4e12a31 100644 --- a/index.html +++ b/index.html @@ -185,7 +185,7 @@ in standards mode (HTML5 <!doctype html> recommended) are supported:

    - + From 8432b6d2db93823abe7fba1b6c1fb49e46917549 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 13:46:53 +0200 Subject: [PATCH 37/59] [sass mode] Clean up coding style --- mode/sass/sass.js | 172 +++++++++++++++++++++++------------------------------- 1 file changed, 74 insertions(+), 98 deletions(-) diff --git a/mode/sass/sass.js b/mode/sass/sass.js index 2ff5003c0a..7b50e90d99 100644 --- a/mode/sass/sass.js +++ b/mode/sass/sass.js @@ -12,9 +12,9 @@ "use strict"; CodeMirror.defineMode("sass", function(config) { - var tokenRegexp = function(words){ + function tokenRegexp(words) { return new RegExp("^" + words.join("|")); - }; + } var keywords = ["true", "false", "null", "auto"]; var keywordsRegexp = new RegExp("^" + keywords.join("|")); @@ -24,246 +24,228 @@ CodeMirror.defineMode("sass", function(config) { var pseudoElementsRegexp = /^::?[\w\-]+/; - var urlTokens = function(stream, state){ + function urlTokens(stream, state) { var ch = stream.peek(); - if (ch === ")"){ + if (ch === ")") { stream.next(); state.tokenizer = tokenBase; return "operator"; - }else if (ch === "("){ + } else if (ch === "(") { stream.next(); stream.eatSpace(); return "operator"; - }else if (ch === "'" || ch === '"'){ + } else if (ch === "'" || ch === '"') { state.tokenizer = buildStringTokenizer(stream.next()); return "string"; - }else{ + } else { state.tokenizer = buildStringTokenizer(")", false); return "string"; } - }; - var multilineComment = function(stream, state) { - if (stream.skipTo("*/")){ + } + function multilineComment(stream, state) { + if (stream.skipTo("*/")) { stream.next(); stream.next(); state.tokenizer = tokenBase; - }else { + } else { stream.next(); } return "comment"; - }; + } - var buildStringTokenizer = function(quote, greedy){ - if(greedy == null){ greedy = true; } + function buildStringTokenizer(quote, greedy) { + if(greedy == null) { greedy = true; } - function stringTokenizer(stream, state){ + function stringTokenizer(stream, state) { var nextChar = stream.next(); var peekChar = stream.peek(); var previousChar = stream.string.charAt(stream.pos-2); var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\")); - /* - console.log("previousChar: " + previousChar); - console.log("nextChar: " + nextChar); - console.log("peekChar: " + peekChar); - console.log("ending: " + endingString); - */ - - if (endingString){ + if (endingString) { if (nextChar !== quote && greedy) { stream.next(); } state.tokenizer = tokenBase; return "string"; - }else if (nextChar === "#" && peekChar === "{"){ + } else if (nextChar === "#" && peekChar === "{") { state.tokenizer = buildInterpolationTokenizer(stringTokenizer); stream.next(); return "operator"; - }else { + } else { return "string"; } } return stringTokenizer; - }; + } - var buildInterpolationTokenizer = function(currentTokenizer){ - return function(stream, state){ - if (stream.peek() === "}"){ + function buildInterpolationTokenizer(currentTokenizer) { + return function(stream, state) { + if (stream.peek() === "}") { stream.next(); state.tokenizer = currentTokenizer; return "operator"; - }else{ + } else { return tokenBase(stream, state); } }; - }; + } - var indent = function(state){ - if (state.indentCount == 0){ + function indent(state) { + if (state.indentCount == 0) { state.indentCount++; var lastScopeOffset = state.scopes[0].offset; var currentOffset = lastScopeOffset + config.indentUnit; state.scopes.unshift({ offset:currentOffset }); } - }; + } - var dedent = function(state){ - if (state.scopes.length == 1) { return; } + function dedent(state) { + if (state.scopes.length == 1) return; state.scopes.shift(); - }; + } - var tokenBase = function(stream, state) { + function tokenBase(stream, state) { var ch = stream.peek(); // Single line Comment - if (stream.match('//')) { + if (stream.match("//")) { stream.skipToEnd(); return "comment"; } // Multiline Comment - if (stream.match('/*')){ + if (stream.match("/*")) { state.tokenizer = multilineComment; return state.tokenizer(stream, state); } // Interpolation - if (stream.match('#{')){ - state.tokenizer = buildInterpolationTokenizer(tokenBase); + if (stream.match("#{")) { + state.tokenizer = buildInterpolationTokenizer(tokenBase); return "operator"; } - if (ch === "."){ + if (ch === ".") { stream.next(); // Match class selectors - if (stream.match(/^[\w-]+/)){ + if (stream.match(/^[\w-]+/)) { indent(state); return "atom"; - }else if (stream.peek() === "#"){ + } else if (stream.peek() === "#") { indent(state); return "atom"; - }else{ + } else { return "operator"; } } - if (ch === "#"){ + if (ch === "#") { stream.next(); // Hex numbers - if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ + if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)) return "number"; - } // ID selectors - if (stream.match(/^[\w-]+/)){ + if (stream.match(/^[\w-]+/)) { indent(state); return "atom"; } - if (stream.peek() === "#"){ + if (stream.peek() === "#") { indent(state); return "atom"; } } // Numbers - if (stream.match(/^-?[0-9\.]+/)){ + if (stream.match(/^-?[0-9\.]+/)) return "number"; - } // Units - if (stream.match(/^(px|em|in)\b/)){ + if (stream.match(/^(px|em|in)\b/)) return "unit"; - } - if (stream.match(keywordsRegexp)){ + if (stream.match(keywordsRegexp)) return "keyword"; - } - if (stream.match(/^url/) && stream.peek() === "("){ + if (stream.match(/^url/) && stream.peek() === "(") { state.tokenizer = urlTokens; return "atom"; } // Variables - if (ch === "$"){ + if (ch === "$") { stream.next(); stream.eatWhile(/[\w-]/); - if (stream.peek() === ":"){ + if (stream.peek() === ":") { stream.next(); return "variable-2"; - }else{ + } else { return "variable-3"; } } - if (ch === "!"){ + if (ch === "!") { stream.next(); - - if (stream.match(/^[\w]+/)){ - return "keyword"; - } - - return "operator"; + return stream.match(/^[\w]+/) ? "keyword": "operator"; } - if (ch === "="){ + if (ch === "=") { stream.next(); // Match shortcut mixin definition - if (stream.match(/^[\w-]+/)){ + if (stream.match(/^[\w-]+/)) { indent(state); return "meta"; - }else { + } else { return "operator"; } } - if (ch === "+"){ + if (ch === "+") { stream.next(); // Match shortcut mixin definition - if (stream.match(/^[\w-]+/)){ + if (stream.match(/^[\w-]+/)) return "variable-3"; - }else { + else return "operator"; - } } // Indent Directives - if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)){ + if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) { indent(state); return "meta"; } // Other Directives - if (ch === "@"){ + if (ch === "@") { stream.next(); stream.eatWhile(/[\w-]/); return "meta"; } // Strings - if (ch === '"' || ch === "'"){ + if (ch === '"' || ch === "'") { stream.next(); state.tokenizer = buildStringTokenizer(ch); return "string"; } // Pseudo element selectors - if (ch == ':' && stream.match(pseudoElementsRegexp)){ + if (ch == ":" && stream.match(pseudoElementsRegexp)) return "keyword"; - } // atoms - if (stream.eatWhile(/[\w-&]/)){ + if (stream.eatWhile(/[\w-&]/)) { // matches a property definition if (stream.peek() === ":" && !stream.match(pseudoElementsRegexp, false)) return "property"; @@ -271,43 +253,37 @@ CodeMirror.defineMode("sass", function(config) { return "atom"; } - if (stream.match(opRegexp)){ + if (stream.match(opRegexp)) return "operator"; - } // If we haven't returned by now, we move 1 character // and return an error stream.next(); return null; - }; + } - var tokenLexer = function(stream, state) { - if (stream.sol()){ - state.indentCount = 0; - } + function tokenLexer(stream, state) { + if (stream.sol()) state.indentCount = 0; var style = state.tokenizer(stream, state); var current = stream.current(); - if (current === "@return"){ + if (current === "@return") dedent(state); - } - if (style === "atom"){ + if (style === "atom") indent(state); - } - if (style !== null){ + if (style !== null) { var startOfToken = stream.pos - current.length; var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); var newScopes = []; - for (var i = 0; i < state.scopes.length; i++){ + for (var i = 0; i < state.scopes.length; i++) { var scope = state.scopes[i]; - if (scope.offset <= withCurrentIndent){ + if (scope.offset <= withCurrentIndent) newScopes.push(scope); - } } state.scopes = newScopes; @@ -315,13 +291,13 @@ CodeMirror.defineMode("sass", function(config) { return style; - }; + } return { startState: function() { return { tokenizer: tokenBase, - scopes: [{offset: 0, type: 'sass'}], + scopes: [{offset: 0, type: "sass"}], definedVars: [], definedMixins: [] }; From f2a917e5cdef3ebffeaca1fd1afab0b9cda6d58b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 13:51:37 +0200 Subject: [PATCH 38/59] [sass mode] Terminate multiline comments on dedentation Closes #2752 --- mode/sass/sass.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/mode/sass/sass.js b/mode/sass/sass.js index 7b50e90d99..4326f183cf 100644 --- a/mode/sass/sass.js +++ b/mode/sass/sass.js @@ -44,16 +44,23 @@ CodeMirror.defineMode("sass", function(config) { return "string"; } } - function multilineComment(stream, state) { - if (stream.skipTo("*/")) { - stream.next(); - stream.next(); - state.tokenizer = tokenBase; - } else { - stream.next(); - } + function multilineComment(indentation) { + return function(stream, state) { + if (stream.sol() && stream.indentation() < indentation) { + state.tokenizer = tokenBase; + return tokenBase(stream, state); + } - return "comment"; + if (stream.skipTo("*/")) { + stream.next(); + stream.next(); + state.tokenizer = tokenBase; + } else { + stream.next(); + } + + return "comment"; + }; } function buildStringTokenizer(quote, greedy) { @@ -120,7 +127,7 @@ CodeMirror.defineMode("sass", function(config) { // Multiline Comment if (stream.match("/*")) { - state.tokenizer = multilineComment; + state.tokenizer = multilineComment(stream.indentation()); return state.tokenizer(stream, state); } From 0d45a4d15479ae12b8d2ce0658972828bf9887ae Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 13:54:53 +0200 Subject: [PATCH 39/59] [sass mode] Add support for indented single-line comments Issue #2752 --- mode/sass/sass.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/mode/sass/sass.js b/mode/sass/sass.js index 4326f183cf..68df323e18 100644 --- a/mode/sass/sass.js +++ b/mode/sass/sass.js @@ -44,14 +44,14 @@ CodeMirror.defineMode("sass", function(config) { return "string"; } } - function multilineComment(indentation) { + function comment(indentation, multiLine) { return function(stream, state) { - if (stream.sol() && stream.indentation() < indentation) { + if (stream.sol() && stream.indentation() <= indentation) { state.tokenizer = tokenBase; return tokenBase(stream, state); } - if (stream.skipTo("*/")) { + if (multiLine && stream.skipTo("*/")) { stream.next(); stream.next(); state.tokenizer = tokenBase; @@ -119,15 +119,13 @@ CodeMirror.defineMode("sass", function(config) { function tokenBase(stream, state) { var ch = stream.peek(); - // Single line Comment - if (stream.match("//")) { - stream.skipToEnd(); - return "comment"; - } - - // Multiline Comment + // Comment if (stream.match("/*")) { - state.tokenizer = multilineComment(stream.indentation()); + state.tokenizer = comment(stream.indentation(), true); + return state.tokenizer(stream, state); + } + if (stream.match("//")) { + state.tokenizer = comment(stream.indentation(), false); return state.tokenizer(stream, state); } From 2f2fcf92a37739f78d8248f7423798a32a0f6c52 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 14:11:24 +0200 Subject: [PATCH 40/59] [merge addon] Add a revertButtons option to turn off revert buttons Closes #2754 --- addon/merge/merge.js | 32 +++++++++++++++++++------------- doc/manual.html | 4 +++- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/addon/merge/merge.js b/addon/merge/merge.js index 3583936198..bde461fcea 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -71,7 +71,7 @@ function update(mode) { if (mode == "full") { if (dv.svg) clear(dv.svg); - clear(dv.copyButtons); + if (dv.copyButtons) clear(dv.copyButtons); clearMarks(dv.edit, edit.marked, dv.classes); clearMarks(dv.orig, orig.marked, dv.classes); edit.from = edit.to = orig.from = orig.to = 0; @@ -257,7 +257,7 @@ var w = dv.gap.offsetWidth; attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); } - clear(dv.copyButtons); + if (dv.copyButtons) clear(dv.copyButtons); var flip = dv.type == "left"; var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); @@ -279,11 +279,13 @@ "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", "class", dv.classes.connect); } - var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", - "CodeMirror-merge-copy")); - copy.title = "Revert chunk"; - copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; - copy.style.top = top + "px"; + if (dv.copyButtons) { + var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", + "CodeMirror-merge-copy")); + copy.title = "Revert chunk"; + copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; + copy.style.top = top + "px"; + } }); } @@ -298,6 +300,7 @@ var MergeView = CodeMirror.MergeView = function(node, options) { if (!(this instanceof MergeView)) return new MergeView(node, options); + this.options = options; var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; var hasLeft = origLeft != null, hasRight = origRight != null; var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); @@ -345,12 +348,15 @@ lock.title = "Toggle locked scrolling"; var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); - dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); - CodeMirror.on(dv.copyButtons, "click", function(e) { - var node = e.target || e.srcElement; - if (node.chunk) copyChunk(dv, node.chunk); - }); - var gapElts = [dv.copyButtons, lockWrap]; + var gapElts = [lockWrap]; + if (dv.mv.options.revertButtons !== false) { + dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); + CodeMirror.on(dv.copyButtons, "click", function(e) { + var node = e.target || e.srcElement; + if (node.chunk) copyChunk(dv, node.chunk); + }); + gapElts.unshift(dv.copyButtons); + } var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); if (svg && !svg.createSVGRect) svg = null; dv.svg = svg; diff --git a/doc/manual.html b/doc/manual.html index e2fe2b2964..673dfba4c7 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -2632,7 +2632,9 @@

    Static properties

    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). + original(s), and, unless a revertButtons option + of false is given, show buttons that allow the user + to revert changes (demo).
    tern/tern.js
    Provides integration with From 11d1a2355b3da68d2006b844c197eb2bcca34b4a Mon Sep 17 00:00:00 2001 From: Andreas Reischuck Date: Sun, 10 Aug 2014 00:31:27 +0200 Subject: [PATCH 41/59] [slim mode] Add --- mode/slim/index.html | 96 +++++++++ mode/slim/slim.js | 575 +++++++++++++++++++++++++++++++++++++++++++++++++++ mode/slim/test.js | 96 +++++++++ test/index.html | 2 + 4 files changed, 769 insertions(+) create mode 100644 mode/slim/index.html create mode 100644 mode/slim/slim.js create mode 100644 mode/slim/test.js diff --git a/mode/slim/index.html b/mode/slim/index.html new file mode 100644 index 0000000000..1a3b6ac867 --- /dev/null +++ b/mode/slim/index.html @@ -0,0 +1,96 @@ + + +CodeMirror: SLIM mode + + + + + + + + + + + + + + + + + + + + +
    +

    SLIM mode

    +
    + + +

    MIME types defined: application/x-slim.

    + +

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

    +
    diff --git a/mode/slim/slim.js b/mode/slim/slim.js new file mode 100644 index 0000000000..5e737131aa --- /dev/null +++ b/mode/slim/slim.js @@ -0,0 +1,575 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + + CodeMirror.defineMode("slim", function(config) { + var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); + var rubyMode = CodeMirror.getMode(config, "ruby"); + var modes = { html: htmlMode, ruby: rubyMode }; + var embedded = { + ruby: "ruby", + javascript: "javascript", + css: "text/css", + sass: "text/x-sass", + scss: "text/x-scss", + less: "text/x-less", + styl: "text/x-styl", // no highlighting so far + coffee: "coffeescript", + asciidoc: "text/x-asciidoc", + markdown: "text/x-markdown", + textile: "text/x-textile", // no highlighting so far + creole: "text/x-creole", // no highlighting so far + wiki: "text/x-wiki", // no highlighting so far + mediawiki: "text/x-mediawiki", // no highlighting so far + rdoc: "text/x-rdoc", // no highlighting so far + builder: "text/x-builder", // no highlighting so far + nokogiri: "text/x-nokogiri", // no highlighting so far + erb: "application/x-erb" + }; + var embeddedRegexp = function(map){ + var arr = []; + for(var key in map) arr.push(key); + return new RegExp("^("+arr.join('|')+"):"); + }(embedded); + + var styleMap = { + "commentLine": "comment", + "slimSwitch": "operator special", + "slimTag": "tag", + "slimId": "attribute def", + "slimClass": "attribute qualifier", + "slimAttribute": "attribute", + "slimSubmode": "keyword special", + "closeAttributeTag": null, + "slimDoctype": null, + "lineContinuation": null + }; + var closing = { + "{": "}", + "[": "]", + "(": ")" + }; + + var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; + var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040"; + var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)"); + var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)"); + var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*"); + var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/; + var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/; + + function backup(pos, tokenize, style) { + var restore = function(stream, state) { + state.tokenize = tokenize; + if (stream.pos < pos) { + stream.pos = pos; + return style; + } + return state.tokenize(stream, state); + }; + return function(stream, state) { + state.tokenize = restore; + return tokenize(stream, state); + }; + } + + function maybeBackup(stream, state, pat, offset, style) { + var cur = stream.current(); + var idx = cur.search(pat); + if (idx > -1) { + state.tokenize = backup(stream.pos, state.tokenize, style); + stream.backUp(cur.length - idx - offset); + } + return style; + } + + function continueLine(state, column) { + state.stack = { + parent: state.stack, + style: "continuation", + indented: column, + tokenize: state.line + }; + state.line = state.tokenize; + } + function finishContinue(state) { + if (state.line == state.tokenize) { + state.line = state.stack.tokenize; + state.stack = state.stack.parent; + } + } + + function lineContinuable(column, tokenize) { + return function(stream, state) { + finishContinue(state); + if (stream.match(/^\\$/)) { + continueLine(state, column); + return "lineContinuation"; + } + var style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) { + stream.backUp(1); + } + return style; + }; + } + function commaContinuable(column, tokenize) { + return function(stream, state) { + finishContinue(state); + var style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/,$/)) { + continueLine(state, column); + } + return style; + }; + } + + function rubyInQuote(endQuote, tokenize) { + // TODO: add multi line support + return function(stream, state) { + var ch = stream.peek(); + if (ch == endQuote && state.rubyState.tokenize.length == 1) { + // step out of ruby context as it seems to complete processing all the braces + stream.next(); + state.tokenize = tokenize; + return "closeAttributeTag"; + } else { + return ruby(stream, state); + } + }; + } + function startRubySplat(tokenize) { + var rubyState; + var runSplat = function(stream, state) { + if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) { + stream.backUp(1); + if (stream.eatSpace()) { + state.rubyState = rubyState; + state.tokenize = tokenize; + return tokenize(stream, state); + } + stream.next(); + } + return ruby(stream, state); + }; + return function(stream, state) { + rubyState = state.rubyState; + state.rubyState = rubyMode.startState(); + state.tokenize = runSplat; + return ruby(stream, state); + }; + } + + function ruby(stream, state) { + return rubyMode.token(stream, state.rubyState); + } + + function htmlLine(stream, state) { + if (stream.match(/^\\$/)) { + return "lineContinuation"; + } + return html(stream, state); + } + function html(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState)); + } + + function startHtmlLine(lastTokenize) { + return function(stream, state) { + var style = htmlLine(stream, state); + if (stream.eol()) state.tokenize = lastTokenize; + return style; + }; + } + + function startHtmlMode(stream, state, offset) { + state.stack = { + parent: state.stack, + style: "html", + indented: stream.column() + offset, // pipe + space + tokenize: state.line + }; + state.line = state.tokenize = html; + return null; + } + + function comment(stream, state) { + stream.skipToEnd(); + return state.stack.style; + } + + function commentMode(stream, state) { + state.stack = { + parent: state.stack, + style: "comment", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = comment; + return comment(stream, state); + } + + function attributeWrapper(stream, state) { + if (stream.eat(state.stack.endQuote)) { + state.line = state.stack.line; + state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + return null; + } + if (stream.match(wrappedAttributeNameRegexp)) { + state.tokenize = attributeWrapperAssign; + return "slimAttribute"; + } + stream.next(); + return null; + } + function attributeWrapperAssign(stream, state) { + if (stream.match(/^==?/)) { + state.tokenize = attributeWrapperValue; + return null; + } + return attributeWrapper(stream, state); + } + function attributeWrapperValue(stream, state) { + var ch = stream.peek(); + if (ch == '"' || ch == "\'") { + state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper); + stream.next(); + return state.tokenize(stream, state); + } + if (ch == '[') { + return startRubySplat(attributeWrapper)(stream, state); + } + if (stream.match(/^(true|false|nil)\b/)) { + state.tokenize = attributeWrapper; + return "keyword"; + } + return startRubySplat(attributeWrapper)(stream, state); + } + + function startAttributeWrapperMode(state, endQuote, tokenize) { + state.stack = { + parent: state.stack, + style: "wrapper", + indented: state.indented + 1, + tokenize: tokenize, + line: state.line, + endQuote: endQuote + }; + state.line = state.tokenize = attributeWrapper; + return null; + } + + function sub(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize); + subStream.pos = stream.pos - state.stack.indented; + subStream.start = stream.start - state.stack.indented; + subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented; + subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented; + var style = state.subMode.token(subStream, state.subState); + stream.pos = subStream.pos + state.stack.indented; + return style; + } + function firstSub(stream, state) { + state.stack.indented = stream.column(); + state.line = state.tokenize = sub; + return state.tokenize(stream, state); + } + + function createMode(mode) { + var query = embedded[mode]; + var spec = CodeMirror.mimeModes[query]; + if (spec) { + return CodeMirror.getMode(config, spec); + } + var factory = CodeMirror.modes[query]; + if (factory) { + return factory(config, {name: query}); + } + return CodeMirror.getMode(config, "null"); + } + + function getMode(mode) { + if (!modes.hasOwnProperty(mode)) { + return modes[mode] = createMode(mode); + } + return modes[mode]; + } + + function startSubMode(mode, state) { + var subMode = getMode(mode); + var subState = subMode.startState && subMode.startState(); + + state.subMode = subMode; + state.subState = subState; + + state.stack = { + parent: state.stack, + style: "sub", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = state.tokenize = firstSub; + return "slimSubmode"; + } + + function doctypeLine(stream, _state) { + stream.skipToEnd(); + return "slimDoctype"; + } + + function startLine(stream, state) { + var ch = stream.peek(); + if (ch == '<') { + return (state.tokenize = startHtmlLine(state.tokenize))(stream, state); + } + if (stream.match(/^[|']/)) { + return startHtmlMode(stream, state, 1); + } + if (stream.match(/^\/(!|\[\w+])?/)) { + return commentMode(stream, state); + } + if (stream.match(/^(-|==?[<>]?)/)) { + state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby)); + return "slimSwitch"; + } + if (stream.match(/^doctype\b/)) { + state.tokenize = doctypeLine; + return "keyword"; + } + + var m = stream.match(embeddedRegexp); + if (m) { + return startSubMode(m[1], state); + } + + return slimTag(stream, state); + } + + function slim(stream, state) { + if (state.startOfLine) { + return startLine(stream, state); + } + return slimTag(stream, state); + } + + function slimTag(stream, state) { + if (stream.eat('*')) { + state.tokenize = startRubySplat(slimTagExtras); + return null; + } + if (stream.match(nameRegexp)) { + state.tokenize = slimTagExtras; + return "slimTag"; + } + return slimClass(stream, state); + } + function slimTagExtras(stream, state) { + if (stream.match(/^(<>?|> state.indented && state.last != "slimSubmode") { + state.line = state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + state.subMode = null; + state.subState = null; + } + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + state.startOfLine = false; + if (style) state.last = style; + return styleMap.hasOwnProperty(style) ? styleMap[style] : style; + }, + + blankLine: function(state) { + if (state.subMode && state.subMode.blankLine) { + return state.subMode.blankLine(state.subState); + } + }, + + innerMode: function(state) { + if (state.subMode) return {state: state.subState, mode: state.subMode}; + return {state: state, mode: mode}; + } + + //indent: function(state) { + // return state.indented; + //} + }; + return mode; + }, "htmlmixed", "ruby"); + + CodeMirror.defineMIME("text/x-slim", "slim"); + CodeMirror.defineMIME("application/x-slim", "slim"); +}); diff --git a/mode/slim/test.js b/mode/slim/test.js new file mode 100644 index 0000000000..be4ddacb62 --- /dev/null +++ b/mode/slim/test.js @@ -0,0 +1,96 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh + +(function() { + var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "slim"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + // Requires at least one media query + MT("elementName", + "[tag h1] Hey There"); + + MT("oneElementPerLine", + "[tag h1] Hey There .h2"); + + MT("idShortcut", + "[attribute&def #test] Hey There"); + + MT("tagWithIdShortcuts", + "[tag h1][attribute&def #test] Hey There"); + + MT("classShortcut", + "[attribute&qualifier .hello] Hey There"); + + MT("tagWithIdAndClassShortcuts", + "[tag h1][attribute&def #test][attribute&qualifier .hello] Hey There"); + + MT("docType", + "[keyword doctype] xml"); + + MT("comment", + "[comment / Hello WORLD]"); + + MT("notComment", + "[tag h1] This is not a / comment "); + + MT("attributes", + "[tag a]([attribute title]=[string \"test\"]) [attribute href]=[string \"link\"]}"); + + MT("multiLineAttributes", + "[tag a]([attribute title]=[string \"test\"]", + " ) [attribute href]=[string \"link\"]}"); + + MT("htmlCode", + "[tag&bracket <][tag h1][tag&bracket >]Title[tag&bracket ]"); + + MT("rubyBlock", + "[operator&special =][variable-2 @item]"); + + MT("selectorRubyBlock", + "[tag a][attribute&qualifier .test][operator&special =] [variable-2 @item]"); + + MT("nestedRubyBlock", + "[tag a]", + " [operator&special =][variable puts] [string \"test\"]"); + + MT("multilinePlaintext", + "[tag p]", + " | Hello,", + " World"); + + MT("multilineRuby", + "[tag p]", + " [comment /# this is a comment]", + " [comment and this is a comment too]", + " | Date/Time", + " [operator&special -] [variable now] [operator =] [tag DateTime][operator .][property now]", + " [tag strong][operator&special =] [variable now]", + " [operator&special -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])", + " [operator&special =][string \"Happy\"]", + " [operator&special =][string \"Belated\"]", + " [operator&special =][string \"Birthday\"]"); + + MT("multilineComment", + "[comment /]", + " [comment Multiline]", + " [comment Comment]"); + + MT("hamlAfterRubyTag", + "[attribute&qualifier .block]", + " [tag strong][operator&special =] [variable now]", + " [attribute&qualifier .test]", + " [operator&special =][variable now]", + " [attribute&qualifier .right]"); + + MT("stretchedRuby", + "[operator&special =] [variable puts] [string \"Hello\"],", + " [string \"World\"]"); + + MT("interpolationInHashAttribute", + "[tag div]{[attribute id] = [string \"]#{[variable test]}[string _]#{[variable ting]}[string \"]} test"); + + MT("interpolationInHTMLAttribute", + "[tag div]([attribute title]=[string \"]#{[variable test]}[string _]#{[variable ting]()}[string \"]) Test"); +})(); diff --git a/test/index.html b/test/index.html index 9930301eb5..a10bd182d4 100644 --- a/test/index.html +++ b/test/index.html @@ -94,6 +94,8 @@ + + From b6e9eea8bb4daec6a0ff23f97104d251a0940414 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 14:17:48 +0200 Subject: [PATCH 42/59] [slim mode] Integrate Issue #2755 --- doc/compress.html | 1 + mode/index.html | 1 + mode/meta.js | 1 + mode/slim/slim.js | 8 ++++---- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/compress.html b/doc/compress.html index 859210c45c..8d25e4b4e9 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -152,6 +152,7 @@ + diff --git a/mode/index.html b/mode/index.html index 1aa0b8d821..1c106ae8e0 100644 --- a/mode/index.html +++ b/mode/index.html @@ -95,6 +95,7 @@
  • SCSS
  • Shell
  • Sieve
  • +
  • Slim
  • Smalltalk
  • Smarty
  • Smarty/HTML mixed
  • diff --git a/mode/meta.js b/mode/meta.js index 3627cd7470..e3c32b6f6c 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -86,6 +86,7 @@ CodeMirror.modeInfo = [ {name: "SCSS", mime: "text/x-scss", mode: "css"}, {name: "Shell", mime: "text/x-sh", mode: "shell"}, {name: "Sieve", mime: "application/sieve", mode: "sieve"}, + {name: "Slim", mime: "text/x-slim", mode: "slim"}, {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk"}, {name: "Smarty", mime: "text/x-smarty", mode: "smarty"}, {name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"}, diff --git a/mode/slim/slim.js b/mode/slim/slim.js index 5e737131aa..164464d066 100644 --- a/mode/slim/slim.js +++ b/mode/slim/slim.js @@ -104,10 +104,10 @@ state.line = state.tokenize; } function finishContinue(state) { - if (state.line == state.tokenize) { - state.line = state.stack.tokenize; - state.stack = state.stack.parent; - } + if (state.line == state.tokenize) { + state.line = state.stack.tokenize; + state.stack = state.stack.parent; + } } function lineContinuable(column, tokenize) { From bbc53ebda20b9a0a5b889d10a3ca4a44a43b3c9a Mon Sep 17 00:00:00 2001 From: Hakan Tunc Date: Mon, 28 Jul 2014 14:36:45 -0500 Subject: [PATCH 43/59] Add mimetype text/x-nesc --- mode/clike/clike.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mode/clike/clike.js b/mode/clike/clike.js index 2873e3629b..ee2c77a025 100644 --- a/mode/clike/clike.js +++ b/mode/clike/clike.js @@ -437,4 +437,15 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { modeProps: {fold: ["brace", "include"]} }); + def("text/x-nesc", { + name: "clike", + keywords: words(cKeywords + "as atomic async call command component components configuration event generic " + + "implementation includes interface module new norace nx_struct nx_union post provides " + + "signal task uses abstract extends"), + blockKeywords: words("case do else for if switch while struct"), + atoms: words("null"), + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + }); From a46ad916a6f40a0208c3f04b9fb35dcb7153f691 Mon Sep 17 00:00:00 2001 From: amuntean Date: Mon, 28 Jul 2014 13:31:06 +0200 Subject: [PATCH 44/59] [merge] new allowEditingOriginals option to edit all compared files --- addon/merge/merge.css | 6 ++++++ addon/merge/merge.js | 27 ++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/addon/merge/merge.css b/addon/merge/merge.css index 63237fc8e9..5d24b9bb7f 100644 --- a/addon/merge/merge.css +++ b/addon/merge/merge.css @@ -62,6 +62,12 @@ color: #44c; } +.CodeMirror-merge-copy-reverse { + position: absolute; + cursor: pointer; + color: #44c; +} + .CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; } .CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; } diff --git a/addon/merge/merge.js b/addon/merge/merge.js index bde461fcea..d9b277664b 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -37,7 +37,7 @@ constructor: DiffView, init: function(pane, orig, options) { this.edit = this.mv.edit; - this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options))); + this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options))); this.diff = getDiff(asString(orig), asString(options.value)); this.diffOutOfDate = false; @@ -282,16 +282,27 @@ if (dv.copyButtons) { var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", "CodeMirror-merge-copy")); - copy.title = "Revert chunk"; + var editOriginals = dv.mv.options.allowEditingOriginals; + copy.title = editOriginals ? "Push to left" : "Revert chunk"; copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; copy.style.top = top + "px"; + + if (editOriginals) { + var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit; + var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", + "CodeMirror-merge-copy-reverse")); + copyReverse.title = "Push to right"; + copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit}; + copyReverse.style.top = topReverse + "px"; + dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; + } } }); } - function copyChunk(dv, chunk) { + function copyChunk(dv, to, from, chunk) { if (dv.diffOutOfDate) return; - dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), + to.replaceRange(from.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0)); } @@ -326,6 +337,7 @@ (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; wrap.push(elt("div", null, null, "height: 0; clear: both;")); + var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); this.edit = CodeMirror(editPane, copyObj(options)); @@ -353,7 +365,12 @@ dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); CodeMirror.on(dv.copyButtons, "click", function(e) { var node = e.target || e.srcElement; - if (node.chunk) copyChunk(dv, node.chunk); + if (!node.chunk) return; + if (node.className == "CodeMirror-merge-copy-reverse") { + copyChunk(dv, dv.orig, dv.edit, node.chunk); + return; + } + copyChunk(dv, dv.edit, dv.orig, node.chunk); }); gapElts.unshift(dv.copyButtons); } From 162c6073e60fcc6635ba31e8ed7a0d35da68a5e0 Mon Sep 17 00:00:00 2001 From: Doug Wikle Date: Mon, 21 Jul 2014 08:29:29 -0400 Subject: [PATCH 45/59] [verilog mode] Addressed indentation issue Blocking indentation for import/export keywords --- mode/verilog/test.js | 144 ++++++++++++++++++++++++++++++++++++++++++++---- mode/verilog/verilog.js | 5 ++ 2 files changed, 138 insertions(+), 11 deletions(-) diff --git a/mode/verilog/test.js b/mode/verilog/test.js index e78860deb9..376d198685 100644 --- a/mode/verilog/test.js +++ b/mode/verilog/test.js @@ -5,7 +5,7 @@ var mode = CodeMirror.getMode({indentUnit: 4}, "verilog"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } - MT("Binary literals", + MT("binary_literals", "[number 1'b0]", "[number 1'b1]", "[number 1'bx]", @@ -30,14 +30,14 @@ "[number 'b0101]" ); - MT("Octal literals", + MT("octal_literals", "[number 3'o7]", "[number 3'O7]", "[number 3'so7]", "[number 3'SO7]" ); - MT("Decimal literals", + MT("decimal_literals", "[number 0]", "[number 1]", "[number 7]", @@ -52,7 +52,7 @@ "[number 32 'd 123]" ); - MT("Hex literals", + MT("hex_literals", "[number 4'h0]", "[number 4'ha]", "[number 4'hF]", @@ -69,7 +69,7 @@ "[number 32'hFFF?]" ); - MT("Real number literals", + MT("real_number_literals", "[number 1.2]", "[number 0.1]", "[number 2394.26331]", @@ -82,36 +82,158 @@ "[number 236.123_763_e-12]" ); - MT("Operators", + MT("operators", "[meta ^]" ); - MT("Keywords", + MT("keywords", "[keyword logic]", "[keyword logic] [variable foo]", "[keyword reg] [variable abc]" ); - MT("Variables", + MT("variables", "[variable _leading_underscore]", "[variable _if]", "[number 12] [variable foo]", "[variable foo] [number 14]" ); - MT("Tick defines", + MT("tick_defines", "[def `FOO]", "[def `foo]", "[def `FOO_bar]" ); - MT("System calls", + MT("system_calls", "[meta $display]", "[meta $vpi_printf]" ); - MT("Line comment", "[comment // Hello world]"); + MT("line_comment", "[comment // Hello world]"); + // Alignment tests + MT("align_port_map_style1", + /** + * mod mod(.a(a), + * .b(b) + * ); + */ + "[variable mod] [variable mod][bracket (].[variable a][bracket (][variable a][bracket )],", + " .[variable b][bracket (][variable b][bracket )]", + " [bracket )];", + "" + ); + + MT("align_port_map_style2", + /** + * mod mod( + * .a(a), + * .b(b) + * ); + */ + "[variable mod] [variable mod][bracket (]", + " .[variable a][bracket (][variable a][bracket )],", + " .[variable b][bracket (][variable b][bracket )]", + "[bracket )];", + "" + ); + + // Indentation tests + MT("indent_single_statement_if", + "[keyword if] [bracket (][variable foo][bracket )]", + " [keyword break];", + "" + ); + + MT("no_indent_after_single_line_if", + "[keyword if] [bracket (][variable foo][bracket )] [keyword break];", + "" + ); + + MT("indent_after_if_begin_same_line", + "[keyword if] [bracket (][variable foo][bracket )] [keyword begin]", + " [keyword break];", + " [keyword break];", + "[keyword end]", + "" + ); + + MT("indent_after_if_begin_next_line", + "[keyword if] [bracket (][variable foo][bracket )]", + " [keyword begin]", + " [keyword break];", + " [keyword break];", + " [keyword end]", + "" + ); + + MT("indent_single_statement_if_else", + "[keyword if] [bracket (][variable foo][bracket )]", + " [keyword break];", + "[keyword else]", + " [keyword break];", + "" + ); + + MT("indent_if_else_begin_same_line", + "[keyword if] [bracket (][variable foo][bracket )] [keyword begin]", + " [keyword break];", + " [keyword break];", + "[keyword end] [keyword else] [keyword begin]", + " [keyword break];", + " [keyword break];", + "[keyword end]", + "" + ); + + MT("indent_if_else_begin_next_line", + "[keyword if] [bracket (][variable foo][bracket )]", + " [keyword begin]", + " [keyword break];", + " [keyword break];", + " [keyword end]", + "[keyword else]", + " [keyword begin]", + " [keyword break];", + " [keyword break];", + " [keyword end]", + "" + ); + + MT("indent_if_nested_without_begin", + "[keyword if] [bracket (][variable foo][bracket )]", + " [keyword if] [bracket (][variable foo][bracket )]", + " [keyword if] [bracket (][variable foo][bracket )]", + " [keyword break];", + "" + ); + + MT("indent_case", + "[keyword case] [bracket (][variable state][bracket )]", + " [variable FOO]:", + " [keyword break];", + " [variable BAR]:", + " [keyword break];", + "[keyword endcase]", + "" + ); + + MT("unindent_after_end_with_preceding_text", + "[keyword begin]", + " [keyword break]; [keyword end]", + "" + ); + + MT("export_function_does_not_indent", + "[keyword export] [string \"DPI-C\"] [keyword function] [variable helloFromSV];", + "" + ); + + MT("export_task_does_not_indent", + "[keyword export] [string \"DPI-C\"] [keyword task] [variable helloFromSV];", + "" + ); })(); diff --git a/mode/verilog/verilog.js b/mode/verilog/verilog.js index 3414ec022e..46209b2492 100644 --- a/mode/verilog/verilog.js +++ b/mode/verilog/verilog.js @@ -95,6 +95,11 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { openClose["do" ] = "while"; openClose["fork" ] = "join;join_any;join_none"; + // This is a bit of a hack but will work to not indent after import/epxort statements + // as long as the function/task name is on the same line + openClose["import"] = "function;task"; + openClose["export"] = "function;task"; + for (var i in noIndentKeywords) { var keyword = noIndentKeywords[i]; if (openClose[keyword]) { From d0916042da46ae396e3cba45de623f2a24c137f1 Mon Sep 17 00:00:00 2001 From: TheHowl Date: Sun, 25 May 2014 14:49:59 +0200 Subject: [PATCH 46/59] [php mode] Add json, curl, mysqli extensions --- mode/php/php.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/php/php.js b/mode/php/php.js index 75b003ff75..f8821ed651 100644 --- a/mode/php/php.js +++ b/mode/php/php.js @@ -95,7 +95,7 @@ "die echo empty exit eval include include_once isset list require require_once return " + "print unset __halt_compiler self static parent yield insteadof finally"; var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__"; - var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once"; + var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count"; CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" ")); CodeMirror.registerHelper("wordChars", "php", /[\w$]/); From d46fd84445330c370d61c9cf38fc5e2c90236e08 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 14 Aug 2014 18:02:28 +0200 Subject: [PATCH 47/59] [sql-hint addon] Clean up, fix handling of whitespace Closes #2761 --- addon/hint/sql-hint.js | 110 ++++++++++++++++++++++++------------------------- mode/sql/index.html | 10 ++++- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/addon/hint/sql-hint.js b/addon/hint/sql-hint.js index fd58b8834e..cc756a2485 100644 --- a/addon/hint/sql-hint.js +++ b/addon/hint/sql-hint.js @@ -21,7 +21,7 @@ function getKeywords(editor) { var mode = editor.doc.modeOption; - if(mode === "sql") mode = "text/x-sql"; + if (mode === "sql") mode = "text/x-sql"; return CodeMirror.resolveMode(mode).keywords; } @@ -32,12 +32,12 @@ } function addMatches(result, search, wordlist, formatter) { - for(var word in wordlist) { - if(!wordlist.hasOwnProperty(word)) continue; - if(Array.isArray(wordlist)) { + for (var word in wordlist) { + if (!wordlist.hasOwnProperty(word)) continue; + if (Array.isArray(wordlist)) { word = wordlist[word]; } - if(match(search, word)) { + if (match(search, word)) { result.push(formatter(word)); } } @@ -49,33 +49,30 @@ var string = token.string.substr(1); var prevCur = Pos(cur.line, token.start); var table = editor.getTokenAt(prevCur).string; - if( !tables.hasOwnProperty( table ) ){ + if (!tables.hasOwnProperty(table)) table = findTableByAlias(table, editor); - } var columns = tables[table]; - if(!columns) { - return; - } - addMatches(result, string, columns, - function(w) {return "." + w;}); + if (!columns) return; + + addMatches(result, string, columns, function(w) {return "." + w;}); } function eachWord(lineText, f) { - if( !lineText ){return;} + if (!lineText) return; var excepted = /[,;]/g; - var words = lineText.split( " " ); - for( var i = 0; i < words.length; i++ ){ - f( words[i]?words[i].replace( excepted, '' ) : '' ); + var words = lineText.split(" "); + for (var i = 0; i < words.length; i++) { + f(words[i]?words[i].replace(excepted, '') : ''); } } - function convertCurToNumber( cur ){ + function convertCurToNumber(cur) { // max characters of a line is 999,999. - return cur.line + cur.ch / Math.pow( 10, 6 ); + return cur.line + cur.ch / Math.pow(10, 6); } - function convertNumberToCur( num ){ - return Pos(Math.floor( num ), +num.toString().split( '.' ).pop()); + function convertNumberToCur(num) { + return Pos(Math.floor(num), +num.toString().split('.').pop()); } function findTableByAlias(alias, editor) { @@ -86,26 +83,26 @@ var table = ""; var separator = []; var validRange = { - start: Pos( 0, 0 ), - end: Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length ) + start: Pos(0, 0), + end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length) }; //add separator - var indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV ); - while( indexOfSeparator != -1 ){ - separator.push( doc.posFromIndex(indexOfSeparator)); - indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV, indexOfSeparator+1); + var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV); + while(indexOfSeparator != -1) { + separator.push(doc.posFromIndex(indexOfSeparator)); + indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1); } - separator.unshift( Pos( 0, 0 ) ); - separator.push( Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) ); + separator.unshift(Pos(0, 0)); + separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length)); - //find valieRange + //find valid range var prevItem = 0; - var current = convertCurToNumber( editor.getCursor() ); - for( var i=0; i< separator.length; i++){ - var _v = convertCurToNumber( separator[i] ); - if( current > prevItem && current <= _v ){ - validRange = { start: convertNumberToCur( prevItem ), end: convertNumberToCur( _v ) }; + var current = convertCurToNumber(editor.getCursor()); + for (var i=0; i< separator.length; i++) { + var _v = convertCurToNumber(separator[i]); + if (current > prevItem && current <= _v) { + validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) }; break; } prevItem = _v; @@ -113,52 +110,51 @@ var query = doc.getRange(validRange.start, validRange.end, false); - for(var i=0; i < query.length; i++){ + for (var i = 0; i < query.length; i++) { var lineText = query[i]; - eachWord( lineText, function( word ){ + eachWord(lineText, function(word) { var wordUpperCase = word.toUpperCase(); - if( wordUpperCase === aliasUpperCase && tables.hasOwnProperty( previousWord ) ){ + if (wordUpperCase === aliasUpperCase && tables.hasOwnProperty(previousWord)) { table = previousWord; } - if( wordUpperCase !== CONS.ALIAS_KEYWORD ){ + if (wordUpperCase !== CONS.ALIAS_KEYWORD) { previousWord = word; } }); - if( table ){ break; } + if (table) break; } return table; } - function sqlHint(editor, options) { + CodeMirror.registerHelper("hint", "sql", function(editor, options) { tables = (options && options.tables) || {}; keywords = keywords || getKeywords(editor); var cur = editor.getCursor(); - var token = editor.getTokenAt(cur), end = token.end; var result = []; - var search = token.string.trim(); - + var token = editor.getTokenAt(cur), start, end, search; + if (token.string.match(/^\.?[\w@]+$/)) { + search = token.string; + start = token.start; + end = token.end; + } else { + start = end = cur.ch; + search = ""; + } if (search.charAt(0) == ".") { columnCompletion(result, editor); if (!result.length) { - while (token.start && search.charAt(0) == ".") { + while (start && search.charAt(0) == ".") { token = editor.getTokenAt(Pos(cur.line, token.start - 1)); + start = token.start; search = token.string + search; } - addMatches(result, search, tables, - function(w) {return w;}); + addMatches(result, search, tables, function(w) {return w;}); } } else { - addMatches(result, search, keywords, - function(w) {return w.toUpperCase();}); - addMatches(result, search, tables, - function(w) {return w;}); + addMatches(result, search, tables, function(w) {return w;}); + addMatches(result, search, keywords, function(w) {return w.toUpperCase();}); } - return { - list: result, - from: Pos(cur.line, token.start), - to: Pos(cur.line, end) - }; - } - CodeMirror.registerHelper("hint", "sql", sqlHint); + return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)}; + }); }); diff --git a/mode/sql/index.html b/mode/sql/index.html index 79a2e74e0f..7dd5f3075e 100644 --- a/mode/sql/index.html +++ b/mode/sql/index.html @@ -7,6 +7,9 @@ + + +
    Firefoxversion 3 and up
    Firefoxversion 4 and up
    Chromeany version
    Safariversion 5.2 and up
    Internet Explorerversion 8 and up