From f8cf3194bf2f810dee6b3cbbbb724be84b408bc3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 23 Jun 2014 18:10:48 +0200 Subject: [PATCH 01/62] Bump version number post-4.3 --- 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 80b379f14b..b103179156 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "CodeMirror", - "version":"4.3.0", + "version":"4.3.1", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/doc/manual.html b/doc/manual.html index 0f77ca8503..2deb8422a5 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -63,7 +63,7 @@

User manual and reference guide - version 4.3.0 + version 4.3.1

CodeMirror is a code-editor component that can be embedded in diff --git a/lib/codemirror.js b/lib/codemirror.js index bafc9fa142..390cb37866 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -7632,7 +7632,7 @@ // THE END - CodeMirror.version = "4.3.0"; + CodeMirror.version = "4.3.1"; return CodeMirror; }); diff --git a/package.json b/package.json index eea46775d5..a496af340e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.3.0", + "version":"4.3.1", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", From aa43a39f7f8f855f0d33be26d031904db3fe344e Mon Sep 17 00:00:00 2001 From: binny Date: Sun, 22 Jun 2014 04:33:55 +0530 Subject: [PATCH 02/62] [vim] visual Block with delete functionality --- keymap/vim.js | 154 +++++++++++++++++++++++++++++++++++++++++++++++-------- test/vim_test.js | 24 +++++++-- 2 files changed, 152 insertions(+), 26 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 3a099a0663..a72c5c0a3d 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -288,6 +288,8 @@ { keys: ['v'], type: 'action', action: 'toggleVisualMode' }, { keys: ['V'], type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, + { keys: [''], type: 'action', action: 'toggleVisualMode', + actionArgs: { blockwise: true }}, { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' }, { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true }, { keys: ['p'], type: 'action', action: 'paste', isEdit: true, @@ -607,6 +609,7 @@ visualMode: false, // If we are in visual line mode. No effect if visualMode is false. visualLine: false, + visualBlock: false, lastSelection: null, lastPastedText: null }; @@ -1343,19 +1346,28 @@ if (vim.visualMode) { // Check if the selection crossed over itself. Will need to shift // the start point if that happened. + // offset is set to -1 or 1 to shift the curEnd + // left or right + var offset = 0; if (cursorIsBefore(selectionStart, selectionEnd) && (cursorEqual(selectionStart, curEnd) || cursorIsBefore(curEnd, selectionStart))) { // The end of the selection has moved from after the start to // before the start. We will shift the start right by 1. selectionStart.ch += 1; - curEnd.ch -= 1; + offset = -1; } else if (cursorIsBefore(selectionEnd, selectionStart) && (cursorEqual(selectionStart, curEnd) || cursorIsBefore(selectionStart, curEnd))) { // The opposite happened. We will shift the start left by 1. selectionStart.ch -= 1; - curEnd.ch += 1; + offset = 1; + } + // in case of visual Block + // selectionStart and curEnd + // may not be on the same line + if (!vim.visualBlock) { + curEnd.ch += offset; } if (vim.lastHPos != Infinity) { vim.lastHPos = curEnd.ch; @@ -1375,8 +1387,12 @@ selectionEnd.ch = 0; selectionStart.ch = lineLength(cm, selectionStart.line); } + } else if (vim.visualBlock) { + selectBlock(cm, selectionEnd); + } + if (!vim.visualBlock) { + cm.setSelection(selectionStart, selectionEnd); } - cm.setSelection(selectionStart, selectionEnd); updateMark(cm, vim, '<', cursorIsBefore(selectionStart, selectionEnd) ? selectionStart : selectionEnd); @@ -1830,7 +1846,7 @@ cm.setCursor(curStart); }, // delete is a javascript keyword. - 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) { + 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { // If the ending line is past the last line, inclusive, instead of // including the trailing \n, include the \n before the starting line if (operatorArgs.linewise && @@ -1841,7 +1857,14 @@ vimGlobalState.registerController.pushText( operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), operatorArgs.linewise); - cm.replaceRange('', curStart, curEnd); + if (vim.visualBlock) { + var selections = cm.listSelections(); + curStart = selections[0].anchor; + var replacement = new Array(selections.length).join('1').split('1'); + cm.replaceSelections(replacement); + } else { + cm.replaceRange('', curStart, curEnd); + } if (operatorArgs.linewise) { cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); } else { @@ -2022,6 +2045,7 @@ cm.on('mousedown', exitVisualMode); vim.visualMode = true; vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; if (vim.visualLine) { curStart.ch = 0; curEnd = clipCursorToContent( @@ -2037,24 +2061,47 @@ } else { curStart = cm.getCursor('anchor'); curEnd = cm.getCursor('head'); - if (!vim.visualLine && actionArgs.linewise) { - // Shift-V pressed in characterwise visual mode. Switch to linewise - // visual mode instead of exiting visual mode. - vim.visualLine = true; - curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 : + if (vim.visualLine) { + vim.visualLine = false; + if (actionArgs.blockwise) { + // This means Ctrl-V pressed in linewise visual + vim.visualBlock = true; + CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'}); + } else if (!actionArgs.linewise) { + // v pressed in linewise, switch to characterwise visual mode + CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'}); + } else { + exitVisualMode(cm); + } + } else if (vim.visualBlock) { + vim.visualBlock = false; + if (actionArgs.linewise) { + // Shift-V pressed in blockwise visual mode + vim.visualLine = true; + CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'linewise'}); + } else if (!actionArgs.blockwise) { + // v pressed in blockwise mode, Switch to characterwise + CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'}); + } else { + exitVisualMode(cm); + } + } else if (actionArgs.linewise) { + // Shift-V pressed in characterwise visual mode. Switch to linewise + // visual mode instead of exiting visual mode. + vim.visualLine = true; + curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 : lineLength(cm, curStart.line); - curEnd.ch = cursorIsBefore(curStart, curEnd) ? + curEnd.ch = cursorIsBefore(curStart, curEnd) ? lineLength(cm, curEnd.line) : 0; - cm.setSelection(curStart, curEnd); - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"}); - } else if (vim.visualLine && !actionArgs.linewise) { - // v pressed in linewise visual mode. Switch to characterwise visual - // mode instead of exiting visual mode. - vim.visualLine = false; - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); - } else { - exitVisualMode(cm); - } + cm.setSelection(curStart, curEnd); + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"}); + } else if (actionArgs.blockwise) { + vim.visualBlock = true; + // write code for block selection; + CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'}); + } else { + exitVisualMode(cm); + } } updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart : curEnd); @@ -2407,6 +2454,70 @@ function escapeRegex(s) { return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); } + // This functions selects a rectangular block + // of text with selectionEnd as any of its corner + // Height of block: + // Difference in selectionEnd.line and first/last selection.line + // Width of the block: + // Distance between selectionEnd.ch and any(first considered here) selection.ch + function selectBlock(cm, selectionEnd) { + var selections = [], ranges = cm.listSelections(); + var firstRange = ranges[0].anchor, lastRange = ranges[ranges.length-1].anchor; + var start, end; + var primIndex = getIndex(ranges, cm.getCursor('head')); + // sets to true when selectionEnd already lies inside the existing selections + var contains = getIndex(ranges, selectionEnd) < 0 ? false : true; + selectionEnd = cm.clipPos(selectionEnd); + // difference in distance of selectionEnd from each end of the block. + var near = Math.abs(firstRange.line - selectionEnd.line) - Math.abs(lastRange.line - selectionEnd.line); + if (near > 0) { + end = selectionEnd.line; + start = firstRange.line; + if (lastRange.line == selectionEnd.line && contains) { + start = end; + } + } else if (near < 0) { + start = selectionEnd.line; + end = lastRange.line; + if (firstRange.line == selectionEnd.line && contains) { + end = start; + } + } else { + // Case where selectionEnd line is halfway between the 2 ends. + // We remove the primary selection in this case + if (primIndex == 0) { + start = selectionEnd.line; + end = lastRange.line; + } else { + start = firstRange.line; + end = selectionEnd.line; + } + } + if (start > end) { + var tmp = start; + start = end; + end = tmp; + } + while (start <= end) { + var anchor = {line: start, ch: (near > 0) ? firstRange.ch : lastRange.ch}; + var head = {line: start, ch: selectionEnd.ch}; + var range = {anchor: anchor, head: head}; + selections.push(range); + if (cursorEqual(head, selectionEnd)) { + primIndex = selections.indexOf(range); + } + start++; + } + cm.setSelections(selections, primIndex); + } + function getIndex(ranges, head) { + for (var i = 0; i < ranges.length; i++) { + if (cursorEqual(ranges[i].head, head)) { + return i; + } + } + return -1; + } function getSelectedAreaRange(cm, vim) { var selectionStart = cm.getCursor('anchor'); var selectionEnd = cm.getCursor('head'); @@ -2456,6 +2567,7 @@ updateLastSelection(cm, vim); vim.visualMode = false; vim.visualLine = false; + vim.visualBlock = false; if (!cursorEqual(selectionStart, selectionEnd)) { // Clear the selection and set the cursor only if the selection has not // already been cleared. Otherwise we risk moving the cursor somewhere diff --git a/test/vim_test.js b/test/vim_test.js index 932db70136..e437e8fe57 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1573,6 +1573,20 @@ testVim('visual_line', function(cm, vim, helpers) { helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd'); eq(' 4\n 5', cm.getValue()); }, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_block', function(cm, vim, helpers) { + // test the block selection with lines of different length + // i.e. extending the selection + // till the end of the longest line. + helpers.doKeys('', 'l', 'j', 'j', '6', 'l', 'd'); + helpers.doKeys('d', 'd', 'd', 'd'); + eq('', cm.getValue()); + // check for left side selection in case + // of moving up to a shorter line. + cm.replaceRange('hello world\n{\nthis is\nsparta!', cm.getCursor()); + cm.setCursor(3, 4); + helpers.doKeys('', 'l', 'k', 'k', 'd'); + eq('hello world\n{\nt is\nsta!', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); testVim('visual_marks', function(cm, vim, helpers) { helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v'); // Test visual mode marks @@ -1613,13 +1627,13 @@ testVim('reselect_visual', function(cm, vim, helpers) { eq('123456\n2345\nbar', cm.getValue()); }, { value: '123456\nfoo\nbar' }); testVim('reselect_visual_line', function(cm, vim, helpers) { - helpers.doKeys('l', 'V', 'l', 'j', 'j', 'V', 'g', 'v', 'd'); - eq(' foo\n and\n bar', cm.getValue()); - cm.setCursor(0, 0); + helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd'); + eq('\nfoo\nand\nbar', cm.getValue()); + cm.setCursor(1, 0); helpers.doKeys('V', 'y', 'j'); helpers.doKeys('V', 'p' , 'g', 'v', 'd'); - eq(' foo\n bar', cm.getValue()); -}, { value: ' hello\n this\n is \n foo\n and\n bar' }); + eq('\nfoo\nbar', cm.getValue()); +}, { value: 'hello\nthis\nis\nfoo\nand\nbar' }); testVim('s_normal', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('s'); From 2516fe1f4c311f1eee278d47d3cdc34b499c7b08 Mon Sep 17 00:00:00 2001 From: William Stein Date: Mon, 23 Jun 2014 10:08:53 -0700 Subject: [PATCH 03/62] Add SageMathCloud to real world examples --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index 6b44c5e67b..e495cfbba6 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -123,6 +123,7 @@

  • Quivive File Manager
  • Rascal (tiny computer)
  • RealTime.io (Internet-of-Things infrastructure)
  • +
  • SageMathCloud (interactive mathematical software environment)
  • ServePHP (PHP code testing in Chrome dev tools)
  • Shadertoy (shader sharing)
  • sketchPatch Livecodelab
  • From 009deb76dc7a99fd0a225e9ffc4c7a78410be774 Mon Sep 17 00:00:00 2001 From: mtaran-google Date: Wed, 11 Jun 2014 19:08:20 -0700 Subject: [PATCH 04/62] [comment addon] Avoid killing block comments outside selection Currently, if you perform the uncomment command with the cursor outside a single-line block comment, it will uncomment that block comment. This is unfortunate behavior for cases where the block comment is used to describe the parameter to a function like "foo(/* index */ 0)". It's also inconsistent: if you have a selection and a single-line block comment after the end of the selection, uncomment() will do nothing, however if you have a selection with a single-line block comment *before* the selection, it will line-comment out all the lines of the selection instead. This also isn't a complete fix, but significantly reduces the scope of the problem. For example if you have "/* foo */ bar /* baz */", uncomment() still does the wrong thing when the cursor is on foo or baz. --- addon/comment/comment.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/addon/comment/comment.js b/addon/comment/comment.js index 3ac476452d..cb78340231 100644 --- a/addon/comment/comment.js +++ b/addon/comment/comment.js @@ -153,6 +153,17 @@ !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1)))) return false; + // Avoid killing block comments completely outside the selection. + // Positions of the last startString before the start of the selection, and the first endString after it. + var lastStart = startLine.lastIndexOf(startString, from.ch); + var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); + if (lastStart != -1 && firstEnd != -1) return false; + // Positions of the first endString after the end of the selection, and the last startString before it. + firstEnd = endLine.indexOf(endString, to.ch); + var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); + lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart; + if (firstEnd != -1 && lastStart != -1) return false; + self.operation(function() { self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), Pos(end, close + endString.length)); From 1e78035dd3a7fedc762210a57c852073dbe3d19d Mon Sep 17 00:00:00 2001 From: mtaran-google Date: Mon, 23 Jun 2014 17:21:18 -0700 Subject: [PATCH 05/62] [comment tests] Test ctrl+/ on inline block comments --- test/comment_test.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/comment_test.js b/test/comment_test.js index d8ff2c866d..8bd3959ee9 100644 --- a/test/comment_test.js +++ b/test/comment_test.js @@ -9,6 +9,9 @@ namespace = "comment_"; } var simpleProg = "function foo() {\n return bar;\n}"; + var inlineBlock = "foo(/* bar */ true);"; + var inlineBlocks = "foo(/* bar */ true, /* baz */ false);"; + var multiLineInlineBlock = ["above();", "foo(/* bar */ true);", "below();"]; test("block", "javascript", function(cm) { cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"}); @@ -19,6 +22,17 @@ namespace = "comment_"; cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"}); }, simpleProg, simpleProg); + test("blockToggle2", "javascript", function(cm) { + cm.setCursor({line: 0, ch: 7 /* inside the block comment */}); + cm.execCommand("toggleComment"); + }, inlineBlock, "foo(bar true);"); + + // This test should work but currently fails. + // test("blockToggle3", "javascript", function(cm) { + // cm.setCursor({line: 0, ch: 7 /* inside the first block comment */}); + // cm.execCommand("toggleComment"); + // }, inlineBlocks, "foo(bar true, /* baz */ false);"); + test("line", "javascript", function(cm) { cm.lineComment(Pos(1, 1), Pos(1, 1)); }, simpleProg, "function foo() {\n// return bar;\n}"); @@ -36,6 +50,29 @@ namespace = "comment_"; cm.blockComment(Pos(0, 0), Pos(1)); }, "def blah()\n return hah\n", "# def blah()\n# return hah\n"); + test("ignoreExternalBlockComments", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, inlineBlocks, "// " + inlineBlocks); + + test("ignoreExternalBlockComments2", "javascript", function(cm) { + cm.setCursor({line: 0, ch: null /* eol */}); + cm.execCommand("toggleComment"); + }, inlineBlocks, "// " + inlineBlocks); + + test("ignoreExternalBlockCommentsMultiLineAbove", "javascript", function(cm) { + cm.setSelection({line: 0, ch: 0}, {line: 1, ch: 1}); + cm.execCommand("toggleComment"); + }, multiLineInlineBlock.join("\n"), ["// " + multiLineInlineBlock[0], + "// " + multiLineInlineBlock[1], + multiLineInlineBlock[2]].join("\n")); + + test("ignoreExternalBlockCommentsMultiLineBelow", "javascript", function(cm) { + cm.setSelection({line: 1, ch: 13 /* after end of block comment */}, {line: 2, ch: 1}); + cm.execCommand("toggleComment"); + }, multiLineInlineBlock.join("\n"), [multiLineInlineBlock[0], + "// " + multiLineInlineBlock[1], + "// " + multiLineInlineBlock[2]].join("\n")); + test("commentRange", "javascript", function(cm) { cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false}); }, simpleProg, "function foo() {\n /*return bar;*/\n}"); From 0abae990ec1e981ac1181a7637a6b333714278f5 Mon Sep 17 00:00:00 2001 From: mtaran-google Date: Mon, 23 Jun 2014 18:09:22 -0700 Subject: [PATCH 06/62] [sublime] Add findAllUnder command --- keymap/sublime.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/keymap/sublime.js b/keymap/sublime.js index 4d46938415..4ac03287e6 100644 --- a/keymap/sublime.js +++ b/keymap/sublime.js @@ -481,7 +481,7 @@ }); }; - function findAndGoTo(cm, forward) { + function getTarget(cm) { var from = cm.getCursor("from"), to = cm.getCursor("to"); if (CodeMirror.cmpPos(from, to) == 0) { var word = wordAt(cm, from); @@ -489,9 +489,13 @@ from = word.from; to = word.to; } + return {from: from, to: to, query: cm.getRange(from, to), word: word}; + } - var query = cm.getRange(from, to); - var cur = cm.getSearchCursor(query, forward ? to : from); + function findAndGoTo(cm, forward) { + var target = getTarget(cm); + var query = target.query; + var cur = cm.getSearchCursor(query, forward ? target.to : target.from); if (forward ? cur.findNext() : cur.findPrevious()) { cm.setSelection(cur.from(), cur.to()); @@ -500,12 +504,24 @@ : cm.clipPos(Pos(cm.lastLine()))); if (forward ? cur.findNext() : cur.findPrevious()) cm.setSelection(cur.from(), cur.to()); - else if (word) - cm.setSelection(from, to); + else if (target.word) + cm.setSelection(target.from, target.to); } }; cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); }; cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); }; + cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) { + var target = getTarget(cm); + var cur = cm.getSearchCursor(target.query); + var matches = []; + var primaryIndex = -1; + while (cur.findNext()) { + matches.push({anchor: cur.from(), head: cur.to()}); + if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch) + primaryIndex++; + } + cm.setSelections(matches, primaryIndex); + }; map["Shift-" + ctrl + "["] = "fold"; map["Shift-" + ctrl + "]"] = "unfold"; From da716e0bda0a00429f072309f55435b95f7496e4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 24 Jun 2014 21:14:47 +0200 Subject: [PATCH 07/62] [sublime] Fix null dereference bug introduced by #2655 --- keymap/sublime.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keymap/sublime.js b/keymap/sublime.js index 4ac03287e6..7a3a1e5f0e 100644 --- a/keymap/sublime.js +++ b/keymap/sublime.js @@ -494,6 +494,7 @@ function findAndGoTo(cm, forward) { var target = getTarget(cm); + if (!target) return; var query = target.query; var cur = cm.getSearchCursor(query, forward ? target.to : target.from); @@ -512,6 +513,7 @@ cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); }; cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) { var target = getTarget(cm); + if (!target) return; var cur = cm.getSearchCursor(target.query); var matches = []; var primaryIndex = -1; From e20d175f6294e3f5f6c18f7e9c2ce505aa040d17 Mon Sep 17 00:00:00 2001 From: binny Date: Wed, 25 Jun 2014 03:28:34 +0530 Subject: [PATCH 08/62] [vim] Update unit tests for switching between visual modes --- keymap/vim.js | 26 ++++++++++++++++++++++++-- test/vim_test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index a72c5c0a3d..fb79140bed 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2066,6 +2066,7 @@ if (actionArgs.blockwise) { // This means Ctrl-V pressed in linewise visual vim.visualBlock = true; + selectBlock(cm, curEnd); CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'}); } else if (!actionArgs.linewise) { // v pressed in linewise, switch to characterwise visual mode @@ -2078,9 +2079,20 @@ if (actionArgs.linewise) { // Shift-V pressed in blockwise visual mode vim.visualLine = true; + var selections = cm.listSelections(); + curStart = Pos(selections[0].anchor.line, 0); + curEnd = Pos(selections[selections.length-1].anchor.line, lineLength(cm, selections[selections.length-1].anchor.line)); + cm.setSelection(curStart, curEnd); CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'linewise'}); } else if (!actionArgs.blockwise) { // v pressed in blockwise mode, Switch to characterwise + var selections = cm.listSelections(); + if (curEnd != selections[0].head) { + curStart = selections[0].anchor; + } else { + curStart = selections[selections.length-1].anchor; + } + cm.setSelection(curStart, curEnd); CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'}); } else { exitVisualMode(cm); @@ -2097,7 +2109,7 @@ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"}); } else if (actionArgs.blockwise) { vim.visualBlock = true; - // write code for block selection; + selectBlock(cm, curEnd); CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'}); } else { exitVisualMode(cm); @@ -2464,7 +2476,8 @@ var selections = [], ranges = cm.listSelections(); var firstRange = ranges[0].anchor, lastRange = ranges[ranges.length-1].anchor; var start, end; - var primIndex = getIndex(ranges, cm.getCursor('head')); + var curEnd = cm.getCursor('head'); + var primIndex = getIndex(ranges, curEnd); // sets to true when selectionEnd already lies inside the existing selections var contains = getIndex(ranges, selectionEnd) < 0 ? false : true; selectionEnd = cm.clipPos(selectionEnd); @@ -2501,6 +2514,15 @@ while (start <= end) { var anchor = {line: start, ch: (near > 0) ? firstRange.ch : lastRange.ch}; var head = {line: start, ch: selectionEnd.ch}; + // Shift the anchor right or left + // as each selection crosses itself. + if ((anchor.ch < curEnd.ch) && ((head.ch == anchor.ch) || (anchor.ch - head.ch == 1))) { + anchor.ch++; + head.ch--; + } else if ((anchor.ch > curEnd.ch) && ((head.ch == anchor.ch) || (anchor.ch - head.ch == -1))) { + anchor.ch--; + head.ch++; + } var range = {anchor: anchor, head: head}; selections.push(range); if (cursorEqual(head, selectionEnd)) { diff --git a/test/vim_test.js b/test/vim_test.js index e437e8fe57..ff5c7c56c9 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1586,6 +1586,36 @@ testVim('visual_block', function(cm, vim, helpers) { cm.setCursor(3, 4); helpers.doKeys('', 'l', 'k', 'k', 'd'); eq('hello world\n{\nt is\nsta!', cm.getValue()); + cm.replaceRange('12345\n67891\nabcde', {line: 0, ch: 0}, {line: cm.lastLine(), ch: 6}); + cm.setCursor(1, 2); + helpers.doKeys('', '2', 'l', 'k'); + // circle around the anchor + // and check the selections + var selections = cm.getSelections(); + eq('345891', selections.join('')); + helpers.doKeys('4', 'h'); + selections = cm.getSelections(); + eq('123678', selections.join('')); + helpers.doKeys('j', 'j'); + selections = cm.getSelections(); + eq('678abc', selections.join('')); + helpers.doKeys('4', 'l'); + selections = cm.getSelections(); + eq('891cde', selections.join('')); + // switch between visual modes + cm.setCursor(1, 1); + // blockwise to characterwise visual + helpers.doKeys('', '', 'j', 'l', 'v'); + selections = cm.getSelections(); + eq('7891\nabc', selections.join('')); + // characterwise to blockwise + helpers.doKeys(''); + selections = cm.getSelections(); + eq('78bc', selections.join('')); + // blockwise to linewise visual + helpers.doKeys('V'); + selections = cm.getSelections(); + eq('67891\nabcde', selections.join('')); }, {value: '1234\n5678\nabcdefg'}); testVim('visual_marks', function(cm, vim, helpers) { helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v'); From 4cc9d2a1f6427585ed847be83ff4d345716f417f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Jun 2014 09:25:35 +0200 Subject: [PATCH 09/62] Add note about JSHint/Lint to CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8938f62046..3624a39e2b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,3 +70,7 @@ should be asked on the - Note that the linter (`bin/lint`) which is run after each commit complains about unused variables and functions. Prefix their names with an underscore to muffle it. + +- CodeMirror does *not* follow JSHint or JSLint prescribed style. + Patches that try to 'fix' code to pass one of these linters will be + unceremoniously discarded. From 2a8480e7eff68295059aba3e6a2e4c88e8b34f89 Mon Sep 17 00:00:00 2001 From: binny Date: Thu, 26 Jun 2014 02:04:27 +0530 Subject: [PATCH 10/62] [vim] visual block yank Also updated select block to return selectionStart. Operators from now on will stop using curStart and curEnd, and rely on the current selection instead. The selection will be generated in evalInput if needed. --- keymap/vim.js | 21 ++++++++++++++------- test/vim_test.js | 6 ++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index fb79140bed..2c0c52de85 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1388,7 +1388,9 @@ selectionStart.ch = lineLength(cm, selectionStart.line); } } else if (vim.visualBlock) { - selectBlock(cm, selectionEnd); + // Select a block and + // return the diagonally opposite end. + selectionStart = selectBlock(cm, selectionEnd); } if (!vim.visualBlock) { cm.setSelection(selectionStart, selectionEnd); @@ -1453,6 +1455,9 @@ operatorArgs.registerName = registerName; // Keep track of linewise as it affects how paste and change behave. operatorArgs.linewise = linewise; + if (!vim.visualBlock) { + cm.extendSelection(curStart, curEnd); + } operators[operator](cm, operatorArgs, vim, curStart, curEnd, curOriginal); if (vim.visualMode) { @@ -1904,10 +1909,11 @@ cm.setCursor(curOriginal); } }, - 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', - cm.getRange(curStart, curEnd), operatorArgs.linewise); + text, operatorArgs.linewise); cm.setCursor(curOriginal); } }; @@ -2038,6 +2044,7 @@ var repeat = actionArgs.repeat; var curStart = cm.getCursor(); var curEnd; + var selections = cm.listSelections(); // TODO: The repeat should actually select number of characters/lines // equal to the repeat times the size of the previous visual // operation. @@ -2079,14 +2086,12 @@ if (actionArgs.linewise) { // Shift-V pressed in blockwise visual mode vim.visualLine = true; - var selections = cm.listSelections(); curStart = Pos(selections[0].anchor.line, 0); curEnd = Pos(selections[selections.length-1].anchor.line, lineLength(cm, selections[selections.length-1].anchor.line)); cm.setSelection(curStart, curEnd); CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'linewise'}); } else if (!actionArgs.blockwise) { // v pressed in blockwise mode, Switch to characterwise - var selections = cm.listSelections(); if (curEnd != selections[0].head) { curStart = selections[0].anchor; } else { @@ -2475,7 +2480,7 @@ function selectBlock(cm, selectionEnd) { var selections = [], ranges = cm.listSelections(); var firstRange = ranges[0].anchor, lastRange = ranges[ranges.length-1].anchor; - var start, end; + var start, end, selectionStart; var curEnd = cm.getCursor('head'); var primIndex = getIndex(ranges, curEnd); // sets to true when selectionEnd already lies inside the existing selections @@ -2511,8 +2516,9 @@ start = end; end = tmp; } + selectionStart = (near > 0) ? firstRange : lastRange; while (start <= end) { - var anchor = {line: start, ch: (near > 0) ? firstRange.ch : lastRange.ch}; + var anchor = {line: start, ch: selectionStart.ch}; var head = {line: start, ch: selectionEnd.ch}; // Shift the anchor right or left // as each selection crosses itself. @@ -2531,6 +2537,7 @@ start++; } cm.setSelections(selections, primIndex); + return selectionStart; } function getIndex(ranges, head) { for (var i = 0; i < ranges.length; i++) { diff --git a/test/vim_test.js b/test/vim_test.js index ff5c7c56c9..250898942a 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -2056,6 +2056,12 @@ testVim('yank_register', function(cm, vim, helpers) { is(/b\s+bar/.test(text)); }); helpers.doKeys(':'); + cm.setCursor(0, 1); + helpers.doKeys('', 'l', 'j', '"', 'a', 'y'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+oo\nar/.test(text)); + }); + helpers.doKeys(':'); }, { value: 'foo\nbar'}); testVim('yank_append_line_to_line_register', function(cm, vim, helpers) { cm.setCursor(0, 0); From 44592dacc9992e7886486212f5919166204153c4 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Fri, 27 Jun 2014 09:33:13 -0700 Subject: [PATCH 11/62] [vim] Separate yank and yank visual block tests --- test/vim_test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/vim_test.js b/test/vim_test.js index 250898942a..391d381979 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -2056,6 +2056,8 @@ testVim('yank_register', function(cm, vim, helpers) { is(/b\s+bar/.test(text)); }); helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_visual_block', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('', 'l', 'j', '"', 'a', 'y'); cm.openNotification = helpers.fakeOpenNotification(function(text) { From 6654b706656e5355f80a3842640047ae9866c73a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Jun 2014 21:59:04 +0200 Subject: [PATCH 12/62] [smartymixed mode] Fix regexp escaping Issue #2659 --- mode/smartymixed/smartymixed.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/mode/smartymixed/smartymixed.js b/mode/smartymixed/smartymixed.js index 31cfd2bdae..3290d17d80 100644 --- a/mode/smartymixed/smartymixed.js +++ b/mode/smartymixed/smartymixed.js @@ -9,6 +9,9 @@ * @date 05.07.2013 */ +// Warning: Don't base other modes on this one. This here is a +// terrible way to write a mixed mode. + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../smarty/smarty")); @@ -20,11 +23,10 @@ "use strict"; CodeMirror.defineMode("smartymixed", function(config) { - var settings, regs, helpers, parsers, - htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"), - smartyMode = CodeMirror.getMode(config, "smarty"), + var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"); + var smartyMode = CodeMirror.getMode(config, "smarty"); - settings = { + var settings = { rightDelimiter: '}', leftDelimiter: '{' }; @@ -36,15 +38,18 @@ CodeMirror.defineMode("smartymixed", function(config) { settings.rightDelimiter = config.rightDelimiter; } - regs = { - smartyComment: new RegExp("^" + settings.leftDelimiter + "\\*"), - literalOpen: new RegExp(settings.leftDelimiter + "literal" + settings.rightDelimiter), - literalClose: new RegExp(settings.leftDelimiter + "\/literal" + settings.rightDelimiter), - hasLeftDelimeter: new RegExp(".*" + settings.leftDelimiter), - htmlHasLeftDelimeter: new RegExp("[^<>]*" + settings.leftDelimiter) + function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); } + + var reLeft = reEsc(settings.leftDelimiter), reRight = reEsc(settings.rightDelimiter); + var regs = { + smartyComment: new RegExp("^" + reRight + "\\*"), + literalOpen: new RegExp(reLeft + "literal" + reRight), + literalClose: new RegExp(reLeft + "\/literal" + reRight), + hasLeftDelimeter: new RegExp(".*" + reLeft), + htmlHasLeftDelimeter: new RegExp("[^<>]*" + reLeft) }; - helpers = { + var helpers = { chain: function(stream, state, parser) { state.tokenize = parser; return parser(stream, state); @@ -70,7 +75,7 @@ CodeMirror.defineMode("smartymixed", function(config) { } }; - parsers = { + var parsers = { html: function(stream, state) { if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && state.htmlMixedState.htmlState.tagName === null) { state.tokenize = parsers.smarty; From b4a619855c92c15cd1b0138bd95c8d87a5a02077 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Jun 2014 22:08:07 +0200 Subject: [PATCH 13/62] Move update of delayedCallbackDepth to start of endOperation To make it less likely that an error will corrupt its value and break all further signalLater events. Issue #2626 --- lib/codemirror.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 390cb37866..1aee422b42 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1935,6 +1935,11 @@ function endOperation(cm) { var op = cm.curOp, doc = cm.doc, display = cm.display; cm.curOp = null; + var delayed; + if (!--delayedCallbackDepth) { + delayed = delayedCallbacks; + delayedCallbacks = null; + } if (op.updateMaxLine) findMaxLine(cm); @@ -1984,11 +1989,6 @@ if (unhidden) for (var i = 0; i < unhidden.length; ++i) if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); - var delayed; - if (!--delayedCallbackDepth) { - delayed = delayedCallbacks; - delayedCallbacks = null; - } // Fire change events, and delayed event handlers if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs); From 73aaf3f0ef0496a03d49921049a3822a0c8614be Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Jun 2014 22:49:02 +0200 Subject: [PATCH 14/62] [show-hint addon] Fix offset computation when moving list above cursor Issue #2663 --- addon/hint/show-hint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index f43ca00c03..27b770bdef 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -228,9 +228,9 @@ (completion.options.container || document.body).appendChild(hints); var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; if (overlapY > 0) { - var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top); + var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); if (curTop - height > 0) { // Fits above cursor - hints.style.top = (top = curTop - height) + "px"; + hints.style.top = (top = pos.top - height) + "px"; below = false; } else if (height > winH) { hints.style.height = (winH - 5) + "px"; From bc9c42b4af78a345dc73edd425f3718dbb37f142 Mon Sep 17 00:00:00 2001 From: Leonya Khachaturov Date: Fri, 27 Jun 2014 18:05:19 +0200 Subject: [PATCH 15/62] [css mode] Fix nonStandardPropertyKeywords definition for consistency --- mode/css/css.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 2678c57d59..2695d08150 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -461,13 +461,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "glyph-orientation-vertical", "text-anchor", "writing-mode" ], propertyKeywords = keySet(propertyKeywords_); - var nonStandardPropertyKeywords = [ + var nonStandardPropertyKeywords_ = [ "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color", "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color", "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", "searchfield-results-decoration", "zoom" - ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords); + ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_); var colorKeywords_ = [ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", @@ -589,7 +589,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { ], fontProperties = keySet(fontProperties_); var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_) - .concat(nonStandardPropertyKeywords).concat(colorKeywords_).concat(valueKeywords_); + .concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_); CodeMirror.registerHelper("hintWords", "css", allWords); function tokenCComment(stream, state) { From 0320b3fb609f46c6703d70a64ebe63d68dd13780 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 28 Jun 2014 19:03:34 +0200 Subject: [PATCH 16/62] [real-world uses] Add CodeWorld --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index e495cfbba6..d730e411b2 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -37,6 +37,7 @@
  • Cargo Collective (creative publishing platform)
  • Chrome DevTools
  • ClickHelp (technical writing tool)
  • +
  • CodeWorld (Haskell playground)
  • Complete.ly playground
  • CrossUI (cross-platform UI builder)
  • Cruncher (notepad with calculation features)
  • From cfc0801a22e2ea657a37612c6054d3143b022224 Mon Sep 17 00:00:00 2001 From: binny Date: Sat, 28 Jun 2014 06:14:12 +0530 Subject: [PATCH 17/62] [vim] swap case in visual block --- keymap/vim.js | 37 +++++++++++++++++++++++++++---------- test/vim_test.js | 13 ++++++++++++- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 2c0c52de85..3cd6f05fdc 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1415,6 +1415,7 @@ curStart = selectionStart; curEnd = selectionEnd; motionArgs.inclusive = true; + operatorArgs.shouldMoveCursor = false; } // Swap start and end if motion was backward. if (curEnd && cursorIsBefore(curEnd, curStart)) { @@ -1896,17 +1897,33 @@ cm.setCursor(curStart); cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); }, - swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { - var toSwap = cm.getRange(curStart, curEnd); - var swapped = ''; - for (var i = 0; i < toSwap.length; i++) { - var character = toSwap.charAt(i); - swapped += isUpperCase(character) ? character.toLowerCase() : - character.toUpperCase(); - } - cm.replaceRange(swapped, curStart, curEnd); + swapcase: function(cm, operatorArgs, _vim, _curStart, _curEnd, _curOriginal) { + var selections = cm.getSelections(); + var ranges = cm.listSelections(); + var curStart = ranges[0].anchor; + var curEnd = ranges[0].head; + // extendSelection swaps curStart and + // curEnd, so make sure + // curStart < curEnd + if (cursorIsBefore(curEnd, curStart)) { + var cur = curStart; + curStart = curEnd; + curEnd = cur; + } + var swapped = []; + for (var j = 0; j < selections.length; j++) { + var toSwap = selections[j]; + var text = ''; + for (var i = 0; i < toSwap.length; i++) { + var character = toSwap.charAt(i); + text += isUpperCase(character) ? character.toLowerCase() : + character.toUpperCase(); + } + swapped.push(text); + } + cm.replaceSelections(swapped); if (!operatorArgs.shouldMoveCursor) { - cm.setCursor(curOriginal); + cm.setCursor(curStart); } }, yank: function(cm, operatorArgs, _vim, _curStart, _curEnd, curOriginal) { diff --git a/test/vim_test.js b/test/vim_test.js index 391d381979..e68c3e3c7a 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -918,8 +918,19 @@ testVim('g~g~', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('', register.toString()); is(!register.linewise); - eqPos(curStart, cm.getCursor()); + eqPos({line: curStart.line, ch:0}, cm.getCursor()); }, { value: ' word1\nword2\nword3\nword4\nword5\nword6' }); +testVim('visual_block_~', function(cm, vim, helpers) { + cm.setCursor(1, 1); + helpers.doKeys('', 'l', 'l', 'j', '~'); + helpers.assertCursorAt(1, 1); + eq('hello\nwoRLd\naBCDe', cm.getValue()); + cm.setCursor(2, 0); + helpers.doKeys('v', 'l', 'l', '~'); + helpers.assertCursorAt(2, 0); + eq('hello\nwoRLd\nAbcDe', cm.getValue()); +},{value: 'hello\nwOrld\nabcde' }); + testVim('>{motion}', function(cm, vim, helpers) { cm.setCursor(1, 3); var expectedLineCount = cm.lineCount(); From 6aadaaeb87d7b3a44ed31ca35b69fb189bb3b719 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sun, 29 Jun 2014 11:55:43 -0700 Subject: [PATCH 18/62] [vim] Simplify swapcase logic --- keymap/vim.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 3cd6f05fdc..e1a76de6bf 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1900,16 +1900,6 @@ swapcase: function(cm, operatorArgs, _vim, _curStart, _curEnd, _curOriginal) { var selections = cm.getSelections(); var ranges = cm.listSelections(); - var curStart = ranges[0].anchor; - var curEnd = ranges[0].head; - // extendSelection swaps curStart and - // curEnd, so make sure - // curStart < curEnd - if (cursorIsBefore(curEnd, curStart)) { - var cur = curStart; - curStart = curEnd; - curEnd = cur; - } var swapped = []; for (var j = 0; j < selections.length; j++) { var toSwap = selections[j]; @@ -1922,8 +1912,12 @@ swapped.push(text); } cm.replaceSelections(swapped); + var curStart = ranges[0].anchor; + var curEnd = ranges[0].head; if (!operatorArgs.shouldMoveCursor) { - cm.setCursor(curStart); + // extendSelection swaps curStart and curEnd, so make sure + // curStart < curEnd + cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd); } }, yank: function(cm, operatorArgs, _vim, _curStart, _curEnd, curOriginal) { From d08aa03ecc8a566cdc51ebf6da2a995bb8cbba5b Mon Sep 17 00:00:00 2001 From: binny Date: Thu, 3 Jul 2014 03:55:03 +0530 Subject: [PATCH 19/62] visual_o updated for blockwise visual --- keymap/vim.js | 33 +++++++++++++++++++-------------- test/vim_test.js | 13 +++++++++++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index e1a76de6bf..6ff5edce4c 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -225,7 +225,8 @@ { keys: ['|'], type: 'motion', motion: 'moveToColumn', motionArgs: { }}, - { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'}, + { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { }, context:'visual'}, + { keys: ['O'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'}, // Operators { keys: ['d'], type: 'operator', operator: 'delete' }, { keys: ['y'], type: 'operator', operator: 'yank' }, @@ -1363,10 +1364,10 @@ selectionStart.ch -= 1; offset = 1; } - // in case of visual Block - // selectionStart and curEnd - // may not be on the same line - if (!vim.visualBlock) { + // in case of visual Block selectionStart and curEnd + // may not be on the same line, + // Also, In case of v_o this should not happen. + if (!vim.visualBlock && !(motionResult instanceof Array)) { curEnd.ch += offset; } if (vim.lastHPos != Infinity) { @@ -1521,15 +1522,19 @@ } return null; }, - moveToOtherHighlightedEnd: function(cm) { - var curEnd = copyCursor(cm.getCursor('head')); - var curStart = copyCursor(cm.getCursor('anchor')); - if (cursorIsBefore(curStart, curEnd)) { - curEnd.ch += 1; - } else if (cursorIsBefore(curEnd, curStart)) { - curStart.ch -= 1; - } - return ([curEnd,curStart]); + moveToOtherHighlightedEnd: function(cm, motionArgs, vim) { + var ranges = cm.listSelections(); + var curEnd = cm.getCursor('head'); + var curStart = ranges[0].anchor; + var curIndex = cursorEqual(ranges[0].head, curEnd) ? ranges.length-1 : 0; + if (motionArgs.sameLine && vim.visualBlock) { + curStart = Pos(curEnd.line, ranges[curIndex].anchor.ch); + curEnd = Pos(ranges[curIndex].head.line, curEnd.ch); + } else { + curStart = ranges[curIndex].anchor; + } + cm.setCursor(curEnd); + return ([curEnd, curStart]); }, jumpToMark: function(cm, motionArgs, vim) { var best = cm.getCursor(); diff --git a/test/vim_test.js b/test/vim_test.js index e68c3e3c7a..b09d730d50 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1689,18 +1689,27 @@ testVim('s_visual', function(cm, vim, helpers) { helpers.assertCursorAt(0, 0); eq('ac', cm.getValue()); }, { value: 'abc'}); -testVim('o_visual', function(cm,vim,helpers) { +testVim('o_visual', function(cm, vim, helpers) { cm.setCursor(0,0); helpers.doKeys('v','l','l','l','o'); helpers.assertCursorAt(0,0); helpers.doKeys('v','v','j','j','j','o'); helpers.assertCursorAt(0,0); - helpers.doKeys('o'); + helpers.doKeys('O'); helpers.doKeys('l','l') helpers.assertCursorAt(3, 3); helpers.doKeys('d'); eq('p',cm.getValue()); }, { value: 'abcd\nefgh\nijkl\nmnop'}); +testVim('o_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('','3','j','l','l', 'o'); + helpers.assertCursorAt(0, 1); + helpers.doKeys('O'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('o'); + helpers.assertCursorAt(3, 1); +}, { value: 'abcd\nefgh\nijkl\nmnop'}); testVim('uppercase/lowercase_visual', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('v', 'l', 'l'); From c6eb304f390f3d32c81c0f8107ce2e1e3ae9a61b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 4 Jul 2014 11:29:53 +0200 Subject: [PATCH 20/62] [closebrackets addon] Add more refined heuristics for when to close quote characters Looks at token types to determine whether the quote would actually open a string Issue #2657 --- addon/edit/closebrackets.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index 83d4229f47..1a04f3664e 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -36,6 +36,22 @@ return str.length == 2 ? str : null; } + // Project the token type that will exists after the given char is + // typed, and use it to determine whether it would cause the start + // of a string token. + function enteringString(cm, pos, ch) { + var line = cm.getLine(pos.line); + var token = cm.getTokenAt(pos); + if (/\bstring2?\b/.test(token.type)) return false; + var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); + stream.pos = stream.start = token.start; + for (;;) { + var type1 = cm.getMode().token(stream, token.state); + if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); + stream.start = stream.pos; + } + } + function buildKeymap(pairs) { var map = { name : "autoCloseBrackets", @@ -61,8 +77,6 @@ var ranges = cm.listSelections(), type, next; for (var i = 0; i < ranges.length; i++) { var range = ranges[i], cur = range.head, curType; - if (left == "'" && cm.getTokenTypeAt(cur) == "comment") - return CodeMirror.Pass; var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); if (!range.empty()) curType = "surround"; @@ -75,9 +89,10 @@ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left && (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) curType = "addFour"; - else if (left == right && CodeMirror.isWordChar(next)) - return CodeMirror.Pass; - else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) + else if (left == '"' || left == "'") { + if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both"; + else return CodeMirror.Pass; + } else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) curType = "both"; else return CodeMirror.Pass; From afc1e5c790ffe79ee1afa96b68208c956370c096 Mon Sep 17 00:00:00 2001 From: Alexander Shvets Date: Thu, 3 Jul 2014 16:10:24 +0300 Subject: [PATCH 21/62] [searchcursor addon] Fix reverse case-insensitive searching for multiline strings --- addon/search/searchcursor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/search/searchcursor.js b/addon/search/searchcursor.js index 2783308858..55c108b5a3 100644 --- a/addon/search/searchcursor.js +++ b/addon/search/searchcursor.js @@ -107,7 +107,7 @@ var from = Pos(pos.line, cut); for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) if (target[i] != fold(doc.getLine(ln))) return; - if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return; + if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; return {from: from, to: Pos(ln, origTarget[last].length)}; } }; From 9826cafc3955412a5270169c7b381fe985c1aff5 Mon Sep 17 00:00:00 2001 From: nilp0inter Date: Thu, 3 Jul 2014 22:35:41 +0200 Subject: [PATCH 22/62] [mode/meta.js] Map application/javascript to javascript mode --- mode/meta.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mode/meta.js b/mode/meta.js index 281028d7ab..4e42cb32df 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -49,6 +49,7 @@ CodeMirror.modeInfo = [ {name: 'HTTP', mime: 'message/http', mode: 'http'}, {name: 'Jade', mime: 'text/x-jade', mode: 'jade'}, {name: 'JavaScript', mime: 'text/javascript', mode: 'javascript'}, + {name: 'JavaScript', mime: 'application/javascript', mode: 'javascript'}, {name: 'JSON', mime: 'application/x-json', mode: 'javascript'}, {name: 'JSON', mime: 'application/json', mode: 'javascript'}, {name: 'JSON-LD', mime: 'application/ld+json', mode: 'javascript'}, From fc3bb8f34b141f39073f038398c5bd0ded932e49 Mon Sep 17 00:00:00 2001 From: Leonid Khachaturov Date: Fri, 4 Jul 2014 10:23:15 +0200 Subject: [PATCH 23/62] Kotlin mode --- mode/kotlin/index.html | 88 ++++++++++++++++ mode/kotlin/kotlin.js | 280 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+) create mode 100644 mode/kotlin/index.html create mode 100644 mode/kotlin/kotlin.js diff --git a/mode/kotlin/index.html b/mode/kotlin/index.html new file mode 100644 index 0000000000..24fb46706f --- /dev/null +++ b/mode/kotlin/index.html @@ -0,0 +1,88 @@ + + +CodeMirror: Kotlin mode + + + + + + + + + +
    +

    Kotlin mode

    + + +
    + + +

    Mode for Kotlin (http://kotlin.jetbrains.org/)

    +

    Developed by Hadi Hariri (https://github.com/hhariri).

    +

    MIME type defined: text/x-kotlin.

    +
    diff --git a/mode/kotlin/kotlin.js b/mode/kotlin/kotlin.js new file mode 100644 index 0000000000..73c84f6c4f --- /dev/null +++ b/mode/kotlin/kotlin.js @@ -0,0 +1,280 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("kotlin", function (config, parserConfig) { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var multiLineStrings = parserConfig.multiLineStrings; + + var keywords = words( + "package continue return object while break class data trait throw super" + + " when type this else This try val var fun for is in if do as true false null get set"); + var softKeywords = words("import" + + " where by get set abstract enum open annotation override private public internal" + + " protected catch out vararg inline finally final ref"); + var blockKeywords = words("catch class do else finally for if where try while enum"); + var atoms = words("null true false this"); + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") { + return startString(ch, stream, state); + } + // Wildcard import w/o trailing semicolon (import smth.*) + if (ch == "." && stream.eat("*")) { + return "word"; + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + if (stream.eat(/eE/)) { + stream.eat(/\+\-/); + stream.eatWhile(/\d/); + } + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize.push(tokenComment); + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + if (expectExpression(state.lastToken)) { + return startString(ch, stream, state); + } + } + // Commented + if (ch == "-" && stream.eat(">")) { + curPunc = "->"; + return null; + } + if (/[\-+*&%=<>!?|\/~]/.test(ch)) { + stream.eatWhile(/[\-+*&%=<>|~]/); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + + var cur = stream.current(); + if (atoms.propertyIsEnumerable(cur)) { + return "atom"; + } + if (softKeywords.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "softKeyword"; + } + + if (keywords.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } + return "word"; + } + + tokenBase.isBase = true; + + function startString(quote, stream, state) { + var tripleQuoted = false; + if (quote != "/" && stream.eat(quote)) { + if (stream.eat(quote)) tripleQuoted = true; + else return "string"; + } + function t(stream, state) { + var escaped = false, next, end = !tripleQuoted; + + while ((next = stream.next()) != null) { + if (next == quote && !escaped) { + if (!tripleQuoted) { + break; + } + if (stream.match(quote + quote)) { + end = true; + break; + } + } + + if (quote == '"' && next == "$" && !escaped && stream.eat("{")) { + state.tokenize.push(tokenBaseUntilBrace()); + return "string"; + } + + if (next == "$" && !escaped && !stream.eat(" ")) { + state.tokenize.push(tokenBaseUntilSpace()); + return "string"; + } + escaped = !escaped && next == "\\"; + } + if (multiLineStrings) + state.tokenize.push(t); + if (end) state.tokenize.pop(); + return "string"; + } + + state.tokenize.push(t); + return t(stream, state); + } + + function tokenBaseUntilBrace() { + var depth = 1; + + function t(stream, state) { + if (stream.peek() == "}") { + depth--; + if (depth == 0) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length - 1](stream, state); + } + } else if (stream.peek() == "{") { + depth++; + } + return tokenBase(stream, state); + } + + t.isBase = true; + return t; + } + + function tokenBaseUntilSpace() { + function t(stream, state) { + if (stream.eat(/[\w]/)) { + var isWord = stream.eatWhile(/[\w]/); + if (isWord) { + state.tokenize.pop(); + return "word"; + } + } + state.tokenize.pop(); + return "string"; + } + + t.isBase = true; + return t; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize.pop(); + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function expectExpression(last) { + return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) || + last == "newstatement" || last == "keyword" || last == "proplabel"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function (basecolumn) { + return { + tokenize: [tokenBase], + context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false), + indented: 0, + startOfLine: true, + lastToken: null + }; + }, + + token: function (stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + // Automatic semicolon insertion + if (ctx.type == "statement" && !expectExpression(state.lastToken)) { + popContext(state); + ctx = state.context; + } + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = state.tokenize[state.tokenize.length - 1](stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + // Handle indentation for {x -> \n ... } + else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") { + popContext(state); + state.context.align = false; + } + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + state.lastToken = curPunc || style; + return style; + }, + + indent: function (state, textAfter) { + if (!state.tokenize[state.tokenize.length - 1].isBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), ctx = state.context; + if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") { + return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit); + } + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : config.indentUnit); + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-kotlin", "kotlin"); + +}); From 4928d37fb9650ce76e1dcba8ad593f1a579b1f70 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 4 Jul 2014 11:54:03 +0200 Subject: [PATCH 24/62] [kotlin mode] Integrate Issue #2678 --- doc/compress.html | 1 + mode/index.html | 1 + mode/kotlin/index.html | 3 +- mode/meta.js | 193 +++++++++++++++++++++++++------------------------ 4 files changed, 101 insertions(+), 97 deletions(-) diff --git a/doc/compress.html b/doc/compress.html index 5762aa442b..ede45e1c6e 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -123,6 +123,7 @@ + diff --git a/mode/index.html b/mode/index.html index 91fb92bff8..1aa0b8d821 100644 --- a/mode/index.html +++ b/mode/index.html @@ -65,6 +65,7 @@
  • JavaScript
  • Jinja2
  • Julia
  • +
  • Kotlin
  • LESS
  • LiveScript
  • Lua
  • diff --git a/mode/kotlin/index.html b/mode/kotlin/index.html index 24fb46706f..38700f3226 100644 --- a/mode/kotlin/index.html +++ b/mode/kotlin/index.html @@ -79,7 +79,8 @@

    Mode for Kotlin (http://kotlin.jetbrains.org/)

    diff --git a/mode/meta.js b/mode/meta.js index 4e42cb32df..3627cd7470 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -12,102 +12,103 @@ "use strict"; CodeMirror.modeInfo = [ - {name: 'APL', mime: 'text/apl', mode: 'apl'}, - {name: 'Asterisk', mime: 'text/x-asterisk', mode: 'asterisk'}, - {name: 'C', mime: 'text/x-csrc', mode: 'clike'}, - {name: 'C++', mime: 'text/x-c++src', mode: 'clike'}, - {name: 'Cobol', mime: 'text/x-cobol', mode: 'cobol'}, - {name: 'Java', mime: 'text/x-java', mode: 'clike'}, - {name: 'C#', mime: 'text/x-csharp', mode: 'clike'}, - {name: 'Scala', mime: 'text/x-scala', mode: 'clike'}, - {name: 'Clojure', mime: 'text/x-clojure', mode: 'clojure'}, - {name: 'CoffeeScript', mime: 'text/x-coffeescript', mode: 'coffeescript'}, - {name: 'Common Lisp', mime: 'text/x-common-lisp', mode: 'commonlisp'}, - {name: 'Cypher', mime: 'application/x-cypher-query', mode: 'cypher'}, - {name: 'CSS', mime: 'text/css', mode: 'css'}, - {name: 'D', mime: 'text/x-d', mode: 'd'}, - {name: 'diff', mime: 'text/x-diff', mode: 'diff'}, - {name: 'DTD', mime: 'application/xml-dtd', mode: 'dtd'}, - {name: 'Dylan', mime: 'text/x-dylan', mode: 'dylan'}, - {name: 'ECL', mime: 'text/x-ecl', mode: 'ecl'}, - {name: 'Eiffel', mime: 'text/x-eiffel', mode: 'eiffel'}, - {name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'}, - {name: 'Fortran', mime: 'text/x-fortran', mode: 'fortran'}, - {name: 'F#', mime: 'text/x-fsharp', mode: 'mllike'}, - {name: 'Gas', mime: 'text/x-gas', mode: 'gas'}, - {name: 'Gherkin', mime: 'text/x-feature', mode: 'gherkin'}, - {name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'}, - {name: 'Go', mime: 'text/x-go', mode: 'go'}, - {name: 'Groovy', mime: 'text/x-groovy', mode: 'groovy'}, - {name: 'HAML', mime: 'text/x-haml', mode: 'haml'}, - {name: 'Haskell', mime: 'text/x-haskell', mode: 'haskell'}, - {name: 'Haxe', mime: 'text/x-haxe', mode: 'haxe'}, - {name: 'ASP.NET', mime: 'application/x-aspx', mode: 'htmlembedded'}, - {name: 'Embedded Javascript', mime: 'application/x-ejs', mode: 'htmlembedded'}, - {name: 'JavaServer Pages', mime: 'application/x-jsp', mode: 'htmlembedded'}, - {name: 'HTML', mime: 'text/html', mode: 'htmlmixed'}, - {name: 'HTTP', mime: 'message/http', mode: 'http'}, - {name: 'Jade', mime: 'text/x-jade', mode: 'jade'}, - {name: 'JavaScript', mime: 'text/javascript', mode: 'javascript'}, - {name: 'JavaScript', mime: 'application/javascript', mode: 'javascript'}, - {name: 'JSON', mime: 'application/x-json', mode: 'javascript'}, - {name: 'JSON', mime: 'application/json', mode: 'javascript'}, - {name: 'JSON-LD', mime: 'application/ld+json', mode: 'javascript'}, - {name: 'TypeScript', mime: 'application/typescript', mode: 'javascript'}, - {name: 'Jinja2', mime: null, mode: 'jinja2'}, - {name: 'Julia', mime: 'text/x-julia', mode: 'julia'}, - {name: 'LESS', mime: 'text/x-less', mode: 'css'}, - {name: 'LiveScript', mime: 'text/x-livescript', mode: 'livescript'}, - {name: 'Lua', mime: 'text/x-lua', mode: 'lua'}, - {name: 'Markdown (GitHub-flavour)', mime: 'text/x-markdown', mode: 'markdown'}, - {name: 'mIRC', mime: 'text/mirc', mode: 'mirc'}, - {name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'}, - {name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'}, - {name: 'OCaml', mime: 'text/x-ocaml', mode: 'mllike'}, - {name: 'Octave', mime: 'text/x-octave', mode: 'octave'}, - {name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'}, - {name: 'PEG.js', mime: null, mode: 'pegjs'}, - {name: 'Perl', mime: 'text/x-perl', mode: 'perl'}, - {name: 'PHP', mime: 'text/x-php', mode: 'php'}, - {name: 'PHP(HTML)', mime: 'application/x-httpd-php', mode: 'php'}, - {name: 'Pig', mime: 'text/x-pig', mode: 'pig'}, - {name: 'Plain Text', mime: 'text/plain', mode: 'null'}, - {name: 'Properties files', mime: 'text/x-properties', mode: 'properties'}, - {name: 'Python', mime: 'text/x-python', mode: 'python'}, - {name: 'Puppet', mime: 'text/x-puppet', mode: 'puppet'}, - {name: 'Cython', mime: 'text/x-cython', mode: 'python'}, - {name: 'R', mime: 'text/x-rsrc', mode: 'r'}, - {name: 'reStructuredText', mime: 'text/x-rst', mode: 'rst'}, - {name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby'}, - {name: 'Rust', mime: 'text/x-rustsrc', mode: 'rust'}, - {name: 'Sass', mime: 'text/x-sass', mode: 'sass'}, - {name: 'Scheme', mime: 'text/x-scheme', mode: 'scheme'}, - {name: 'SCSS', mime: 'text/x-scss', mode: 'css'}, - {name: 'Shell', mime: 'text/x-sh', mode: 'shell'}, - {name: 'Sieve', mime: 'application/sieve', mode: 'sieve'}, - {name: 'Smalltalk', mime: 'text/x-stsrc', mode: 'smalltalk'}, - {name: 'Smarty', mime: 'text/x-smarty', mode: 'smarty'}, - {name: 'SmartyMixed', mime: 'text/x-smarty', mode: 'smartymixed'}, - {name: 'Solr', mime: 'text/x-solr', mode: 'solr'}, - {name: 'SPARQL', mime: 'application/x-sparql-query', mode: 'sparql'}, - {name: 'SQL', mime: 'text/x-sql', mode: 'sql'}, - {name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql'}, - {name: 'sTeX', mime: 'text/x-stex', mode: 'stex'}, - {name: 'LaTeX', mime: 'text/x-latex', mode: 'stex'}, - {name: 'SystemVerilog', mime: 'text/x-systemverilog', mode: 'verilog'}, - {name: 'Tcl', mime: 'text/x-tcl', mode: 'tcl'}, - {name: 'TiddlyWiki ', mime: 'text/x-tiddlywiki', mode: 'tiddlywiki'}, - {name: 'Tiki wiki', mime: 'text/tiki', mode: 'tiki'}, - {name: 'TOML', mime: 'text/x-toml', mode: 'toml'}, - {name: 'Turtle', mime: 'text/turtle', mode: 'turtle'}, - {name: 'VB.NET', mime: 'text/x-vb', mode: 'vb'}, - {name: 'VBScript', mime: 'text/vbscript', mode: 'vbscript'}, - {name: 'Velocity', mime: 'text/velocity', mode: 'velocity'}, - {name: 'Verilog', mime: 'text/x-verilog', mode: 'verilog'}, - {name: 'XML', mime: 'application/xml', mode: 'xml'}, - {name: 'XQuery', mime: 'application/xquery', mode: 'xquery'}, - {name: 'YAML', mime: 'text/x-yaml', mode: 'yaml'}, - {name: 'Z80', mime: 'text/x-z80', mode: 'z80'} + {name: "APL", mime: "text/apl", mode: "apl"}, + {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk"}, + {name: "C", mime: "text/x-csrc", mode: "clike"}, + {name: "C++", mime: "text/x-c++src", mode: "clike"}, + {name: "Cobol", mime: "text/x-cobol", mode: "cobol"}, + {name: "Java", mime: "text/x-java", mode: "clike"}, + {name: "C#", mime: "text/x-csharp", mode: "clike"}, + {name: "Scala", mime: "text/x-scala", mode: "clike"}, + {name: "Clojure", mime: "text/x-clojure", mode: "clojure"}, + {name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript"}, + {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp"}, + {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher"}, + {name: "CSS", mime: "text/css", mode: "css"}, + {name: "D", mime: "text/x-d", mode: "d"}, + {name: "diff", mime: "text/x-diff", mode: "diff"}, + {name: "DTD", mime: "application/xml-dtd", mode: "dtd"}, + {name: "Dylan", mime: "text/x-dylan", mode: "dylan"}, + {name: "ECL", mime: "text/x-ecl", mode: "ecl"}, + {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel"}, + {name: "Erlang", mime: "text/x-erlang", mode: "erlang"}, + {name: "Fortran", mime: "text/x-fortran", mode: "fortran"}, + {name: "F#", mime: "text/x-fsharp", mode: "mllike"}, + {name: "Gas", mime: "text/x-gas", mode: "gas"}, + {name: "Gherkin", mime: "text/x-feature", mode: "gherkin"}, + {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm"}, + {name: "Go", mime: "text/x-go", mode: "go"}, + {name: "Groovy", mime: "text/x-groovy", mode: "groovy"}, + {name: "HAML", mime: "text/x-haml", mode: "haml"}, + {name: "Haskell", mime: "text/x-haskell", mode: "haskell"}, + {name: "Haxe", mime: "text/x-haxe", mode: "haxe"}, + {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded"}, + {name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded"}, + {name: "JavaServer Pages", mime: "application/x-jsp", mode: "htmlembedded"}, + {name: "HTML", mime: "text/html", mode: "htmlmixed"}, + {name: "HTTP", mime: "message/http", mode: "http"}, + {name: "Jade", mime: "text/x-jade", mode: "jade"}, + {name: "JavaScript", mime: "text/javascript", mode: "javascript"}, + {name: "JavaScript", mime: "application/javascript", mode: "javascript"}, + {name: "JSON", mime: "application/x-json", mode: "javascript"}, + {name: "JSON", mime: "application/json", mode: "javascript"}, + {name: "JSON-LD", mime: "application/ld+json", mode: "javascript"}, + {name: "TypeScript", mime: "application/typescript", mode: "javascript"}, + {name: "Jinja2", mime: null, mode: "jinja2"}, + {name: "Julia", mime: "text/x-julia", mode: "julia"}, + {name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin"}, + {name: "LESS", mime: "text/x-less", mode: "css"}, + {name: "LiveScript", mime: "text/x-livescript", mode: "livescript"}, + {name: "Lua", mime: "text/x-lua", mode: "lua"}, + {name: "Markdown (GitHub-flavour)", mime: "text/x-markdown", mode: "markdown"}, + {name: "mIRC", mime: "text/mirc", mode: "mirc"}, + {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx"}, + {name: "NTriples", mime: "text/n-triples", mode: "ntriples"}, + {name: "OCaml", mime: "text/x-ocaml", mode: "mllike"}, + {name: "Octave", mime: "text/x-octave", mode: "octave"}, + {name: "Pascal", mime: "text/x-pascal", mode: "pascal"}, + {name: "PEG.js", mime: null, mode: "pegjs"}, + {name: "Perl", mime: "text/x-perl", mode: "perl"}, + {name: "PHP", mime: "text/x-php", mode: "php"}, + {name: "PHP(HTML)", mime: "application/x-httpd-php", mode: "php"}, + {name: "Pig", mime: "text/x-pig", mode: "pig"}, + {name: "Plain Text", mime: "text/plain", mode: "null"}, + {name: "Properties files", mime: "text/x-properties", mode: "properties"}, + {name: "Python", mime: "text/x-python", mode: "python"}, + {name: "Puppet", mime: "text/x-puppet", mode: "puppet"}, + {name: "Cython", mime: "text/x-cython", mode: "python"}, + {name: "R", mime: "text/x-rsrc", mode: "r"}, + {name: "reStructuredText", mime: "text/x-rst", mode: "rst"}, + {name: "Ruby", mime: "text/x-ruby", mode: "ruby"}, + {name: "Rust", mime: "text/x-rustsrc", mode: "rust"}, + {name: "Sass", mime: "text/x-sass", mode: "sass"}, + {name: "Scheme", mime: "text/x-scheme", mode: "scheme"}, + {name: "SCSS", mime: "text/x-scss", mode: "css"}, + {name: "Shell", mime: "text/x-sh", mode: "shell"}, + {name: "Sieve", mime: "application/sieve", mode: "sieve"}, + {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk"}, + {name: "Smarty", mime: "text/x-smarty", mode: "smarty"}, + {name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"}, + {name: "Solr", mime: "text/x-solr", mode: "solr"}, + {name: "SPARQL", mime: "application/x-sparql-query", mode: "sparql"}, + {name: "SQL", mime: "text/x-sql", mode: "sql"}, + {name: "MariaDB", mime: "text/x-mariadb", mode: "sql"}, + {name: "sTeX", mime: "text/x-stex", mode: "stex"}, + {name: "LaTeX", mime: "text/x-latex", mode: "stex"}, + {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog"}, + {name: "Tcl", mime: "text/x-tcl", mode: "tcl"}, + {name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"}, + {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"}, + {name: "TOML", mime: "text/x-toml", mode: "toml"}, + {name: "Turtle", mime: "text/turtle", mode: "turtle"}, + {name: "VB.NET", mime: "text/x-vb", mode: "vb"}, + {name: "VBScript", mime: "text/vbscript", mode: "vbscript"}, + {name: "Velocity", mime: "text/velocity", mode: "velocity"}, + {name: "Verilog", mime: "text/x-verilog", mode: "verilog"}, + {name: "XML", mime: "application/xml", mode: "xml"}, + {name: "XQuery", mime: "application/xquery", mode: "xquery"}, + {name: "YAML", mime: "text/x-yaml", mode: "yaml"}, + {name: "Z80", mime: "text/x-z80", mode: "z80"} ]; }); From b55cc7f5cd0b1128508dda51f525538dd1897f29 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 3 Jul 2014 15:05:42 +0200 Subject: [PATCH 25/62] [multi-editor operations] Set up operation grouping Now nested operations from different editors end at the same time --- lib/codemirror.js | 66 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 1aee422b42..85cfea9d7b 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1911,10 +1911,13 @@ // error-prone). Instead, display updates are batched and then all // combined and executed at once. + var operationGroup = null; + var nextOpId = 0; // Start a new operation. function startOperation(cm) { cm.curOp = { + cm: cm, viewChanged: false, // Flag that indicates that lines might need to be redrawn startHeight: cm.doc.height, // Used to detect need to update scrollbar forceUpdate: false, // Used to force a redraw @@ -1928,19 +1931,32 @@ scrollToPos: null, // Used to scroll to a specific position id: ++nextOpId // Unique ID }; - if (!delayedCallbackDepth++) delayedCallbacks = []; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } } // Finish an operation, updating the display and signalling delayed events function endOperation(cm) { - var op = cm.curOp, doc = cm.doc, display = cm.display; - cm.curOp = null; - var delayed; - if (!--delayedCallbackDepth) { - delayed = delayedCallbacks; - delayedCallbacks = null; - } + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + cm.curOp = operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + endOperationInner(group.ops[i]); + + for (var i = 0; i < group.delayedCallbacks.length; ++i) + group.delayedCallbacks[i](); + } + + function endOperationInner(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; if (op.updateMaxLine) findMaxLine(cm); // If it looks like an update might be needed, call updateDisplay @@ -1949,11 +1965,11 @@ op.scrollToPos.to.line >= display.viewTo) || display.maxLineChanged && cm.options.lineWrapping) { var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); - if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; + if (cm.display.scroller.offsetHeight) doc.scrollTop = cm.display.scroller.scrollTop; } // If no update was run, but the selection changed, redraw that. if (!updated && op.selectionChanged) updateSelection(cm); - if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm); + if (!updated && op.startHeight != doc.height) updateScrollbars(cm); // Abort mouse wheel delta measurement, when scrolling explicitly if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) @@ -1971,8 +1987,8 @@ } // If we need to scroll a specific position into view, do so. if (op.scrollToPos) { - var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), - clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); } @@ -1992,7 +2008,6 @@ // Fire change events, and delayed event handlers if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs); - if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); if (op.cursorActivityHandlers) for (var i = 0; i < op.cursorActivityHandlers.length; i++) op.cursorActivityHandlers[i](cm); @@ -6960,6 +6975,8 @@ for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); }; + var orphanDelayedCallbacks = null; + // Often, we want to signal events at a point where we are in the // middle of some work, but don't want the handler to start calling // other methods on the editor, which might be in an inconsistent @@ -6967,25 +6984,26 @@ // signalLater looks whether there are any handlers, and schedules // them to be executed when the last operation ends, or, if no // operation is active, when a timeout fires. - var delayedCallbacks, delayedCallbackDepth = 0; function signalLater(emitter, type /*, values...*/) { var arr = emitter._handlers && emitter._handlers[type]; if (!arr) return; - var args = Array.prototype.slice.call(arguments, 2); - if (!delayedCallbacks) { - ++delayedCallbackDepth; - delayedCallbacks = []; - setTimeout(fireDelayed, 0); + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); } function bnd(f) {return function(){f.apply(null, args);};}; for (var i = 0; i < arr.length; ++i) - delayedCallbacks.push(bnd(arr[i])); + list.push(bnd(arr[i])); } - function fireDelayed() { - --delayedCallbackDepth; - var delayed = delayedCallbacks; - delayedCallbacks = null; + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; for (var i = 0; i < delayed.length; ++i) delayed[i](); } From 0bdd5ea0b31aade11c0f2a385cdd720aab90eea4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 3 Jul 2014 15:32:10 +0200 Subject: [PATCH 26/62] [multi-editor operations] Fire delayed events before the operation ends --- lib/codemirror.js | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 85cfea9d7b..b9c63a226d 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1925,6 +1925,7 @@ typing: false, // Whether this reset should be careful to leave existing text (for compositing) changeObjs: null, // Accumulated changes, for firing change events cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already selectionChanged: false, // Whether the selection needs to be redrawn updateMaxLine: false, // Set when the widest line needs to be determined anew scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet @@ -1941,18 +1942,33 @@ } } + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); + } + // Finish an operation, updating the display and signalling delayed events function endOperation(cm) { var op = cm.curOp, group = op.ownsGroup; if (!group) return; - cm.curOp = operationGroup = null; - - for (var i = 0; i < group.ops.length; i++) - endOperationInner(group.ops[i]); - - for (var i = 0; i < group.delayedCallbacks.length; ++i) - group.delayedCallbacks[i](); + try { fireCallbacksForOps(group); } + finally { + cm.curOp = operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + endOperationInner(group.ops[i]); + } } function endOperationInner(op) { @@ -2008,9 +2024,6 @@ // Fire change events, and delayed event handlers if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs); - if (op.cursorActivityHandlers) - for (var i = 0; i < op.cursorActivityHandlers.length; i++) - op.cursorActivityHandlers[i](cm); } // Run the given function in an operation From 55e1ed94dc83539dcecc4abb56935c3b411da557 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 3 Jul 2014 15:54:08 +0200 Subject: [PATCH 27/62] Run operations cautiously again in highlightWorker --- lib/codemirror.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index b9c63a226d..089a3e9f3b 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1408,8 +1408,8 @@ if (doc.frontier >= cm.display.viewTo) return; var end = +new Date + cm.options.workTime; var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; - runInOp(cm, function() { doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { if (doc.frontier >= cm.display.viewFrom) { // Visible var oldStyles = line.styles; @@ -1421,7 +1421,7 @@ var ischange = !oldStyles || oldStyles.length != line.styles.length || oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; - if (ischange) regLineChange(cm, doc.frontier, "text"); + if (ischange) changedLines.push(doc.frontier); line.stateAfter = copyState(doc.mode, state); } else { processLine(cm, line.text, state); @@ -1433,6 +1433,9 @@ return true; } }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); }); } From 24920fb8a247a6079bc41d92af08f64cfe82b4b9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 3 Jul 2014 15:55:22 +0200 Subject: [PATCH 28/62] Don't wrap onKeyUp in an operation It doesn't need it --- lib/codemirror.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 089a3e9f3b..b2e194273f 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2452,7 +2452,7 @@ // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - on(d.input, "keyup", operation(cm, onKeyUp)); + on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); }); on(d.input, "input", function() { if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; fastPoll(cm); @@ -3145,8 +3145,8 @@ } function onKeyUp(e) { - if (signalDOMEvent(this, e)) return; if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); } function onKeyPress(e) { @@ -4164,7 +4164,7 @@ triggerOnKeyDown: methodOp(onKeyDown), triggerOnKeyPress: methodOp(onKeyPress), - triggerOnKeyUp: methodOp(onKeyUp), + triggerOnKeyUp: onKeyUp, execCommand: function(cmd) { if (commands.hasOwnProperty(cmd)) From e72560be12964597656fdf05d775893c46b81b14 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 3 Jul 2014 18:03:27 +0200 Subject: [PATCH 29/62] [multi-editor operations] Split actions at op end into steps, call in order --- lib/codemirror.js | 295 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 181 insertions(+), 114 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index b2e194273f..1bf02d2089 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -108,6 +108,7 @@ for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) optionHandlers[opt](cm, options[opt], Init); + maybeUpdateLineNumberWidth(cm); for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm); }); } @@ -467,18 +468,18 @@ } // Compute the lines that are visible in a given viewport (defaults - // the the current scroll position). viewPort may contain top, + // the the current scroll position). viewport may contain top, // height, and ensure (see op.scrollToPos) properties. - function visibleLines(display, doc, viewPort) { - var top = viewPort && viewPort.top != null ? Math.max(0, viewPort.top) : display.scroller.scrollTop; + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; top = Math.floor(top - paddingTop(display)); - var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight; + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); // Ensure is a {from: {line, ch}, to: {line, ch}} object, and // forces those lines into the viewport (if possible). - if (viewPort && viewPort.ensure) { - var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line; + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; if (ensureFrom < from) return {from: ensureFrom, to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)}; @@ -543,83 +544,46 @@ // DISPLAY DRAWING - // Updates the display, selection, and scrollbars, using the - // information in display.view to find out which nodes are no longer - // up-to-date. Tries to bail out early when no changes are needed, - // unless forced is true. - // Returns true if an actual update happened, false otherwise. - function updateDisplay(cm, viewPort, forced) { - var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated; - var visible = visibleLines(cm.display, cm.doc, viewPort); - for (var first = true;; first = false) { - var oldWidth = cm.display.scroller.clientWidth; - if (!updateDisplayInner(cm, visible, forced)) break; - updated = true; - - // If the max line changed since it was last measured, measure it, - // and ensure the document's width matches it. - if (cm.display.maxLineChanged && !cm.options.lineWrapping) - adjustContentWidth(cm); - - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - setDocumentHeight(cm, barMeasure); - updateScrollbars(cm, barMeasure); - if (webkit && cm.options.lineWrapping) - checkForWebkitWidthBug(cm, barMeasure); // (Issue #2420) - if (webkit && barMeasure.scrollWidth > barMeasure.clientWidth && - barMeasure.scrollWidth < barMeasure.clientWidth + 1 && - !hScrollbarTakesSpace(cm)) - updateScrollbars(cm); // (Issue #2562) - if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { - forced = true; - continue; - } - forced = false; - - // Clip forced viewport to actual scrollable area. - if (viewPort && viewPort.top != null) - viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)}; - // Updated line heights might result in the drawn area not - // actually covering the viewport. Keep looping until it does. - visible = visibleLines(cm.display, cm.doc, viewPort); - if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo) - break; - } + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; - cm.display.updateLineNumbers = null; - if (updated) { - signalLater(cm, "update", cm); - if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo) - signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); - } - return updated; + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo; + this.oldScrollerWidth = display.scroller.clientWidth; + this.force = force; + this.dims = getDimensions(cm); } // Does the actual updating of the line display. Bails out // (returning false) when there is nothing to be done and forced is // false. - function updateDisplayInner(cm, visible, forced) { + function updateDisplayIfNeeded(cm, update) { var display = cm.display, doc = cm.doc; - if (!display.wrapper.offsetWidth) { + if (update.editorIsHidden) { resetView(cm); - return; + return false; } // Bail out if the visible area is already rendered and nothing changed. - if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo && + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && countDirtyView(cm) == 0) - return; + return false; - if (maybeUpdateLineNumberWidth(cm)) + if (maybeUpdateLineNumberWidth(cm)) { resetView(cm); - var dims = getDimensions(cm); + update.dims = getDimensions(cm); + } // Compute a suitable new viewport (from & to) var end = doc.first + doc.size; - var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); - var to = Math.min(end, visible.to + cm.options.viewportMargin); + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); if (sawCollapsedSpans) { @@ -628,7 +592,7 @@ } var different = from != display.viewFrom || to != display.viewTo || - display.lastSizeC != display.wrapper.clientHeight; + display.lastSizeC != update.wrapperHeight; adjustView(cm, from, to); display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); @@ -636,13 +600,15 @@ cm.display.mover.style.top = display.viewOffset + "px"; var toUpdate = countDirtyView(cm); - if (!different && toUpdate == 0 && !forced) return; + if (!different && toUpdate == 0 && !update.force && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; // For big changes, we hide the enclosing element during the // update, since that speeds up the operations on most browsers. var focused = activeElt(); if (toUpdate > 4) display.lineDiv.style.display = "none"; - patchDisplay(cm, display.updateLineNumbers, dims); + patchDisplay(cm, display.updateLineNumbers, update.dims); if (toUpdate > 4) display.lineDiv.style.display = ""; // There might have been a widget with a focused element that got // hidden or updated, if so re-focus it. @@ -654,24 +620,50 @@ removeChildren(display.selectionDiv); if (different) { - display.lastSizeC = display.wrapper.clientHeight; + display.lastSizeC = update.wrapperHeight; startWorker(cm, 400); } - updateHeightsInViewport(cm); + display.updateLineNumbers = null; return true; } - function adjustContentWidth(cm) { - var display = cm.display; - var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left; - display.maxLineChanged = false; - var minWidth = Math.max(0, width + 3); - var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth); - display.sizer.style.minWidth = minWidth + "px"; - if (maxScrollLeft < cm.doc.scrollLeft) - setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); + function postUpdateDisplay(cm, update) { + var force = update.force, viewport = update.viewport; + for (var first = true;; first = false) { + updateHeightsInViewport(cm); + if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) { + force = true; + } else { + force = false; + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff - + cm.display.scroller.clientHeight, viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + var visible = visibleLines(cm.display, cm.doc, viewport); + if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + } + + signalLater(cm, "update", cm); + if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo) + signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } } function setDocumentHeight(cm, measure) { @@ -1257,10 +1249,10 @@ // SELECTION DRAWING // Redraw the selection and/or cursor - function updateSelection(cm) { - var display = cm.display, doc = cm.doc; - var curFragment = document.createDocumentFragment(); - var selFragment = document.createDocumentFragment(); + function drawSelection(cm) { + var display = cm.display, doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); for (var i = 0; i < doc.sel.ranges.length; i++) { var range = doc.sel.ranges[i]; @@ -1275,16 +1267,23 @@ if (cm.options.moveInputWithCursor) { var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); - var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)); - var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)); - display.inputDiv.style.top = top + "px"; - display.inputDiv.style.left = left + "px"; + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); } - removeChildrenAndAdd(display.cursorDiv, curFragment); - removeChildrenAndAdd(display.selectionDiv, selFragment); + return result; + } + + function updateSelection(cm, drawn) { + if (!drawn) drawn = drawSelection(cm); + removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + cm.display.inputDiv.style.top = drawn.teTop + "px"; + cm.display.inputDiv.style.left = drawn.teLeft + "px"; + } } // Draws a cursor for the given range @@ -1967,28 +1966,88 @@ if (!group) return; try { fireCallbacksForOps(group); } - finally { - cm.curOp = operationGroup = null; + finally { + operationGroup = null; for (var i = 0; i < group.ops.length; i++) - endOperationInner(group.ops[i]); + group.ops[i].cm.curOp = null; + endOperations(group); } } - function endOperationInner(op) { - var cm = op.cm, display = cm.display, doc = cm.doc; + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; if (op.updateMaxLine) findMaxLine(cm); - // If it looks like an update might be needed, call updateDisplay - if (op.viewChanged || op.forceUpdate || op.scrollTop != null || - op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || - op.scrollToPos.to.line >= display.viewTo) || - display.maxLineChanged && cm.options.lineWrapping) { - var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); - if (cm.display.scroller.offsetHeight) doc.scrollTop = cm.display.scroller.scrollTop; + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // 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 + 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 + + scrollerCutOff - display.scroller.clientWidth); } - // If no update was run, but the selection changed, redraw that. - if (!updated && op.selectionChanged) updateSelection(cm); - if (!updated && op.startHeight != doc.height) updateScrollbars(cm); + + op.barMeasure = measureForScrollbars(cm); + if (op.updatedDisplay || op.selectionChanged) + op.newSelectionNodes = drawSelection(cm); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + 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); + } + + if (op.newSelectionNodes) + updateSelection(cm, op.newSelectionNodes); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + resetInput(cm, op.typing); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; // Abort mouse wheel delta measurement, when scrolling explicitly if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) @@ -2011,11 +2070,6 @@ if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); } - if (op.selectionChanged) restartBlink(cm); - - if (cm.state.focused && op.updateInput) - resetInput(cm, op.typing); - // Fire events for markers that are hidden/unidden by editing or // undoing var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; @@ -2024,6 +2078,19 @@ if (unhidden) for (var i = 0; i < unhidden.length; ++i) if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Apply workaround for two webkit bugs + if (op.updatedDisplay && webkit) { + if (cm.options.lineWrapping) + checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420) + if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth && + op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 && + !hScrollbarTakesSpace(cm)) + updateScrollbars(cm); // (Issue #2562) + } + // Fire change events, and delayed event handlers if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs); @@ -2916,10 +2983,10 @@ function setScrollTop(cm, val) { if (Math.abs(cm.doc.scrollTop - val) < 2) return; cm.doc.scrollTop = val; - if (!gecko) updateDisplay(cm, {top: val}); + if (!gecko) updateDisplaySimple(cm, {top: val}); if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; - if (gecko) updateDisplay(cm); + if (gecko) updateDisplaySimple(cm); startWorker(cm, 100); } // Sync scroller and scrollbar, ensure the gutter elements are @@ -3002,7 +3069,7 @@ var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; if (pixels < 0) top = Math.max(0, top + pixels - 50); else bot = Math.min(cm.doc.height, bot + pixels + 50); - updateDisplay(cm, {top: top, bottom: bot}); + updateDisplaySimple(cm, {top: top, bottom: bot}); } if (wheelSamples < 20) { From c698ab758b4682c2aca55135c9948735dd75a05f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 4 Jul 2014 12:26:41 +0200 Subject: [PATCH 30/62] [multi-editor operations] Add a few tests --- test/test.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/test.js b/test/test.js index 4ef4ef547d..dcfffb7c07 100644 --- a/test/test.js +++ b/test/test.js @@ -1988,3 +1988,38 @@ testCM("resizeLineWidget", function(cm) { cm.setSize(40); is(widget.parentNode.offsetWidth < 42); }); + +testCM("combinedOperations", function(cm) { + var place = document.getElementById("testground"); + var other = CodeMirror(place, {value: "123"}); + try { + cm.operation(function() { + cm.addLineClass(0, "wrap", "foo"); + other.addLineClass(0, "wrap", "foo"); + }); + eq(byClassName(cm.getWrapperElement(), "foo").length, 1); + eq(byClassName(other.getWrapperElement(), "foo").length, 1); + cm.operation(function() { + cm.removeLineClass(0, "wrap", "foo"); + other.removeLineClass(0, "wrap", "foo"); + }); + eq(byClassName(cm.getWrapperElement(), "foo").length, 0); + eq(byClassName(other.getWrapperElement(), "foo").length, 0); + } finally { + place.removeChild(other.getWrapperElement()); + } +}, {value: "abc"}); + +testCM("eventOrder", function(cm) { + var seen = []; + cm.on("change", function() { + if (!seen.length) cm.replaceSelection("."); + seen.push("change"); + }); + cm.on("cursorActivity", function() { + cm.replaceSelection("!"); + seen.push("activity"); + }); + cm.replaceSelection("/"); + eq(seen.join(","), "change,change,activity,change"); +}); From 63591907b0dcd51c2f64dc967143e044ecac6923 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 7 Jul 2014 14:30:27 +0200 Subject: [PATCH 31/62] Work around IE10- client rect + zoom issue Issue #2665 --- lib/codemirror.js | 24 ++++++++++++++++++++++++ test/lint/lint.js | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 1bf02d2089..cc7caace19 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1667,6 +1667,8 @@ rect = nullRect; } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; var mid = (rtop + rbot) / 2; var heights = prepared.view.measure.heights; @@ -1678,9 +1680,22 @@ top: top, bottom: bot}; if (!rect.left && !rect.right) result.bogus = true; if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + return result; } + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + function clearLineMeasurementCacheFor(lineView) { if (lineView.measure) { lineView.measure.cache = {}; @@ -7432,6 +7447,15 @@ return typeof e.oncopy == "function"; })(); + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + // KEY NAMES var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", diff --git a/test/lint/lint.js b/test/lint/lint.js index ef4c13bd9a..c2c45262a6 100644 --- a/test/lint/lint.js +++ b/test/lint/lint.js @@ -19,7 +19,7 @@ var topAllowedGlobals = Object.create(null); ("Error RegExp Number String Array Function Object Math Date undefined " + "parseInt parseFloat Infinity NaN isNaN " + "window document navigator prompt alert confirm console " + - "FileReader Worker postMessage importScripts " + + "screen FileReader Worker postMessage importScripts " + "setInterval clearInterval setTimeout clearTimeout " + "CodeMirror " + "test exports require module define") From 57761d076413ad7e49f1e45d94b122dd8dfb2c55 Mon Sep 17 00:00:00 2001 From: Jaydeep Solanki Date: Sat, 5 Jul 2014 06:06:24 +0530 Subject: [PATCH 32/62] [ruby mode] Better heuristic for distinguishing division from regexps --- mode/ruby/ruby.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index b68ee2912a..d6c9d8796e 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -46,9 +46,23 @@ CodeMirror.defineMode("ruby", function(config) { var ch = stream.next(), m; if (ch == "`" || ch == "'" || ch == '"') { return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); - } else if (ch == "/" && !stream.eol() && stream.peek() != " ") { - if (stream.eat("=")) return "operator"; - return chain(readQuoted(ch, "string-2", true), stream, state); + } else if (ch == "/") { + var currentIndex = stream.current().length; + if (stream.skipTo("/")) { + var search_till = stream.current().length; + stream.backUp(stream.current().length - currentIndex); + var balance = 0; // balance brackets + while (stream.current().length < search_till) { + var chchr = stream.next(); + if (chchr == "(") balance += 1; + else if (chchr == ")") balance -= 1; + if (balance < 0) break; + } + stream.backUp(stream.current().length - currentIndex); + if (balance == 0) + return chain(readQuoted(ch, "string-2", true), stream, state); + } + return "operator"; } else if (ch == "%") { var style = "string", embed = true; if (stream.eat("s")) style = "atom"; From 347affcf2784dc8ed6e13abba517f7bd4968e715 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 7 Jul 2014 23:22:38 +0200 Subject: [PATCH 33/62] Handle altGr in early return from onKeyPress Issue #2671 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index cc7caace19..dcbacc258b 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3233,7 +3233,7 @@ function onKeyPress(e) { var cm = this; - if (signalDOMEvent(cm, e) || e.ctrlKey || mac && e.metaKey) return; + if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; var keyCode = e.keyCode, charCode = e.charCode; if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; From 93c846bd2b3190459aed37399a3b5583d578911a Mon Sep 17 00:00:00 2001 From: binny Date: Wed, 2 Jul 2014 00:09:42 +0530 Subject: [PATCH 34/62] [vim] reselectSelection for visualBlock --- keymap/vim.js | 78 ++++++++++++++++++++++++++++++++++++++------------------ test/vim_test.js | 24 ++++++++++++++--- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 6ff5edce4c..ab61e17afb 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1858,6 +1858,8 @@ }, // delete is a javascript keyword. 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { + // Save the '>' mark before cm.replaceRange clears it. + var selectionEnd = vim.visualMode ? vim.marks['>'].find() : null; // If the ending line is past the last line, inclusive, instead of // including the trailing \n, include the \n before the starting line if (operatorArgs.linewise && @@ -1876,6 +1878,10 @@ } else { cm.replaceRange('', curStart, curEnd); } + // restore the saved bookmark + if (selectionEnd) { + vim.marks['>'] = cm.setBookmark(selectionEnd); + } if (operatorArgs.linewise) { cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); } else { @@ -2085,7 +2091,6 @@ curStart = cm.getCursor('anchor'); curEnd = cm.getCursor('head'); if (vim.visualLine) { - vim.visualLine = false; if (actionArgs.blockwise) { // This means Ctrl-V pressed in linewise visual vim.visualBlock = true; @@ -2097,8 +2102,8 @@ } else { exitVisualMode(cm); } + vim.visualLine = false; } else if (vim.visualBlock) { - vim.visualBlock = false; if (actionArgs.linewise) { // Shift-V pressed in blockwise visual mode vim.visualLine = true; @@ -2118,6 +2123,7 @@ } else { exitVisualMode(cm); } + vim.visualBlock = false; } else if (actionArgs.linewise) { // Shift-V pressed in characterwise visual mode. Switch to linewise // visual mode instead of exiting visual mode. @@ -2142,27 +2148,40 @@ : curStart); }, reselectLastSelection: function(cm, _actionArgs, vim) { + var curStart = vim.marks['<'].find(); + var curEnd = vim.marks['>'].find(); var lastSelection = vim.lastSelection; if (lastSelection) { - var curStart = lastSelection.curStartMark.find(); - var curEnd = lastSelection.curEndMark.find(); - cm.setSelection(curStart, curEnd); + // Set the selections as per last selection + var selectionStart = lastSelection.curStartMark.find(); + var selectionEnd = lastSelection.curEndMark.find(); + var blockwise = lastSelection.visualBlock; + // update last selection + updateLastSelection(cm, vim, curStart, curEnd); + if (blockwise) { + cm.setCursor(selectionStart); + selectionStart = selectBlock(cm, selectionEnd); + } else { + cm.setSelection(selectionStart, selectionEnd); + selectionStart = cm.getCursor('anchor'); + selectionEnd = cm.getCursor('head'); + } if (vim.visualMode) { - updateLastSelection(cm, vim); - var selectionStart = cm.getCursor('anchor'); - var selectionEnd = cm.getCursor('head'); updateMark(cm, vim, '<', cursorIsBefore(selectionStart, selectionEnd) ? selectionStart - : selectionEnd); + : selectionEnd); updateMark(cm, vim, '>', cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd - : selectionStart); + : selectionStart); } + // Last selection is updated now + vim.visualMode = true; if (lastSelection.visualLine) { - vim.visualMode = true; vim.visualLine = true; - } - else { - vim.visualMode = true; + vim.visualBlock = false; + } else if (lastSelection.visualBlock) { vim.visualLine = false; + vim.visualBlock = true; + } else { + vim.visualBlock = vim.visualLine = false; } CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""}); } @@ -2552,6 +2571,10 @@ } start++; } + // Update selectionEnd and selectionStart + // after selection crossing + selectionEnd.ch = selections[0].head.ch; + selectionStart.ch = selections[0].anchor.ch; cm.setSelections(selections, primIndex); return selectionStart; } @@ -2586,22 +2609,27 @@ } return [selectionStart, selectionEnd]; } - function updateLastSelection(cm, vim) { - // We need the vim mark '<' to get the selection in case of yank and put - var selectionStart = vim.marks['<'].find() || cm.getCursor('anchor'); - var selectionEnd = vim.marks['>'].find() ||cm.getCursor('head'); - // To accommodate the effect lastPastedText in the last selection + function updateLastSelection(cm, vim, selectionStart, selectionEnd) { + if (!selectionStart || !selectionEnd) { + selectionStart = vim.marks['<'].find() || cm.getCursor('anchor'); + selectionEnd = vim.marks['>'].find() || cm.getCursor('head'); + } + // To accommodate the effect of lastPastedText in the last selection if (vim.lastPastedText) { - selectionEnd = cm.posFromIndex(cm.indexFromPos(selectionStart) + vim.lastPastedText.length-1); + selectionEnd = cm.posFromIndex(cm.indexFromPos(selectionStart) + vim.lastPastedText.length); vim.lastPastedText = null; } + var ranges = cm.listSelections(); + // This check ensures to set the cursor + // position where we left off in previous selection + var swap = getIndex(ranges, selectionStart) > -1; // can't use selection state here because yank has already reset its cursor // Also, Bookmarks make the visual selections robust to edit operations - vim.lastSelection = {'curStartMark': cm.setBookmark(selectionStart), 'curEndMark': cm.setBookmark(selectionEnd), 'visualMode': vim.visualMode, 'visualLine': vim.visualLine}; - if (cursorIsBefore(selectionEnd, selectionStart)) { - vim.lastSelection.curStartMark = cm.setBookmark(selectionEnd); - vim.lastSelection.curEndMark = cm.setBookmark(selectionStart); - } + vim.lastSelection = {'curStartMark': cm.setBookmark(swap ? selectionEnd : selectionStart), + 'curEndMark': cm.setBookmark(swap ? selectionStart : selectionEnd), + 'visualMode': vim.visualMode, + 'visualLine': vim.visualLine, + 'visualBlock': vim.visualBlock}; } function exitVisualMode(cm) { diff --git a/test/vim_test.js b/test/vim_test.js index b09d730d50..1c9e8c3c9a 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1655,7 +1655,8 @@ testVim('reselect_visual', function(cm, vim, helpers) { eq('123456\n2345\nbar', cm.getValue()); cm.setCursor(0, 0); helpers.doKeys('g', 'v'); - helpers.assertCursorAt(1, 3); + // here the fake cursor is at (1, 3) + helpers.assertCursorAt(2, 0); eqPos(makeCursor(1, 0), cm.getCursor('anchor')); helpers.doKeys('v'); cm.setCursor(2, 0); @@ -1669,17 +1670,32 @@ testVim('reselect_visual', function(cm, vim, helpers) { }, { value: '123456\nfoo\nbar' }); testVim('reselect_visual_line', function(cm, vim, helpers) { helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd'); - eq('\nfoo\nand\nbar', cm.getValue()); + eq('foo\nand\nbar', cm.getValue()); cm.setCursor(1, 0); helpers.doKeys('V', 'y', 'j'); helpers.doKeys('V', 'p' , 'g', 'v', 'd'); - eq('\nfoo\nbar', cm.getValue()); + eq('foo\nand', cm.getValue()); }, { value: 'hello\nthis\nis\nfoo\nand\nbar' }); +testVim('reselect_visual_block', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('', 'k', 'h', ''); + cm.setCursor(2, 1); + helpers.doKeys('v', 'l', 'g', 'v'); + helpers.assertCursorAt(0, 1); + // Ensure selection is done with visual block mode rather than one + // continuous range. + eq(cm.getSelections().join(''), '23oo') + helpers.doKeys('g', 'v'); + helpers.assertCursorAt(2, 3); + // Ensure selection of deleted range + cm.setCursor(1, 1); + helpers.doKeys('v', '', 'j', 'd', 'g', 'v'); + eq(cm.getSelections().join(''), 'or'); +}, { value: '123456\nfoo\nbar' }); testVim('s_normal', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('s'); helpers.doInsertModeKeys('Esc'); - helpers.assertCursorAt(0, 0); eq('ac', cm.getValue()); }, { value: 'abc'}); testVim('s_visual', function(cm, vim, helpers) { From 238b451301227eb8987a4204fc726e1f1207777f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 8 Jul 2014 23:44:59 +0200 Subject: [PATCH 35/62] Fix broken double-checking of display coverage Both a potential infinite loop, due to updateDisplayIfNeeded not using the updated set of visibile lines, and the fact that the current check wasn't really covering changing document size, due to it happening before the call to setDocumentHeight Issue #2683 --- lib/codemirror.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index dcbacc258b..19abaf50b7 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -632,7 +632,6 @@ function postUpdateDisplay(cm, update) { var force = update.force, viewport = update.viewport; for (var first = true;; first = false) { - updateHeightsInViewport(cm); if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) { force = true; } else { @@ -643,11 +642,16 @@ cm.display.scroller.clientHeight, viewport.top)}; // Updated line heights might result in the drawn area not // actually covering the viewport. Keep looping until it does. - var visible = visibleLines(cm.display, cm.doc, viewport); - if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo) + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) break; } if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); } signalLater(cm, "update", cm); @@ -2023,7 +2027,7 @@ function endOperation_R2(op) { var cm = op.cm, display = cm.display; - if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + if (op.updatedDisplay) updateHeightsInViewport(cm); // If the max line changed since it was last measured, measure it, // and ensure the document's width matches it. @@ -2064,6 +2068,8 @@ function endOperation_finish(op) { var cm = op.cm, display = cm.display, doc = cm.doc; + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + // Abort mouse wheel delta measurement, when scrolling explicitly if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) display.wheelStartX = display.wheelStartY = null; From 6c0cd2b56b50837a010bd27f322a57edfbe9fee9 Mon Sep 17 00:00:00 2001 From: Richard van der Meer Date: Tue, 8 Jul 2014 11:51:35 +0200 Subject: [PATCH 36/62] [vbscript mode] Fixed "Cannot read property 'substr' of null" Error occurs when entering multiple dots --- mode/vbscript/vbscript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/vbscript/vbscript.js b/mode/vbscript/vbscript.js index bff2594477..b66df2239a 100644 --- a/mode/vbscript/vbscript.js +++ b/mode/vbscript/vbscript.js @@ -291,7 +291,7 @@ CodeMirror.defineMode("vbscript", function(conf, parserConf) { style = state.tokenize(stream, state); current = stream.current(); - if (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword'){//|| knownWords.indexOf(current.substring(1)) > -1) { + if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) { if (style === 'builtin' || style === 'keyword') style='variable'; if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2'; From e92998af5dd97c771821f03c223b88442974bc6b Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Tue, 8 Jul 2014 20:35:07 -0700 Subject: [PATCH 37/62] [vim] Add features list to demo page --- demo/vim.html | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/demo/vim.html b/demo/vim.html index f92ff9e6d8..cc616759f0 100644 --- a/demo/vim.html +++ b/demo/vim.html @@ -45,17 +45,32 @@ return (--n >= 0) ? (unsigned char) *bufp++ : EOF; } -
    - -
    +
    Key buffer:

    The vim keybindings are enabled by including keymap/vim.js and setting the vimMode option to true. This will also automatically change the keyMap option to "vim".

    +

    Features

    + +
      +
    • All common motions and operators, including text objects
    • +
    • Operator motion orthogonality
    • +
    • Visual mode - characterwise, linewise, partial support for blockwise
    • +
    • Full macro support (q, @)
    • +
    • Incremental highlighted search (/, ?, #, *, g#, g*)
    • +
    • Search/replace with confirm (:substitute, :%s)
    • +
    • Search history
    • +
    • Jump lists (Ctrl-o, Ctrl-i)
    • +
    • Key/command mapping with API (:map, :nmap, :vmap)
    • +
    • Sort (:sort)
    • +
    • Marks (`, ')
    • +
    • :global
    • +
    • Insert mode behaves identical to base CodeMirror
    • +
    • Cross-buffer yank/paste
    • +
    +

    Note that while the vim mode tries to emulate the most useful features of vim as faithfully as possible, it does not strive to become a complete vim implementation

    @@ -69,13 +84,6 @@ matchBrackets: true, showCursorWhenSelecting: true }); - var editor2 = CodeMirror.fromTextArea(document.getElementById("code2"), { - lineNumbers: true, - mode: "text/x-csrc", - vimMode: true, - matchBrackets: true, - showCursorWhenSelecting: true - }); var commandDisplay = document.getElementById('command-display'); var keys = ''; CodeMirror.on(editor, 'vim-keypress', function(key) { From 770c0970cf2e3541ad21ee6fa0c7d78b21b368e0 Mon Sep 17 00:00:00 2001 From: binny Date: Wed, 9 Jul 2014 19:15:58 +0530 Subject: [PATCH 38/62] [vim] change for blockwise visual --- keymap/vim.js | 51 ++++++++++++++++++++++++++++++++++++++------------- test/vim_test.js | 14 ++++++++++++++ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index ab61e17afb..c1b0df4683 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1831,17 +1831,39 @@ }; var operators = { - change: function(cm, operatorArgs, _vim, curStart, curEnd) { + change: function(cm, operatorArgs, vim) { + var selections = cm.listSelections(); + var start = selections[0], end = selections[selections.length-1]; + 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 replacement = new Array(selections.length).join('1').split('1'); vimGlobalState.registerController.pushText( - operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd), + operatorArgs.registerName, 'change', text, operatorArgs.linewise); if (operatorArgs.linewise) { - // Push the next line back down, if there is a next line. - var replacement = curEnd.line > cm.lastLine() ? '' : '\n'; - cm.replaceRange(replacement, curStart, curEnd); - cm.indentLine(curStart.line, 'smart'); - // null ch so setCursor moves to end of line. - curStart.ch = null; + // 'C' in visual block extends the block till eol for all lines + if (vim.visualBlock){ + var startLine = curStart.line; + while (startLine <= curEnd.line) { + var endCh = lineLength(cm, startLine); + var head = Pos(startLine, endCh); + var anchor = Pos(startLine, curStart.ch); + startLine++; + cm.replaceRange('', anchor, head); + } + } else { + // Push the next line back down, if there is a next line. + replacement = '\n'; + if (curEnd.line == curStart.line && curEnd.line == cm.lastLine()) { + replacement = ''; + } + cm.replaceRange(replacement, curStart, curEnd); + cm.indentLine(curStart.line, 'smart'); + // null ch so setCursor moves to end of line. + curStart.ch = null; + cm.setCursor(curStart); + } } else { // Exclude trailing whitespace if the range is not all whitespace. var text = cm.getRange(curStart, curEnd); @@ -1851,15 +1873,20 @@ curEnd = offsetCursor(curEnd, 0, - match[0].length); } } - cm.replaceRange('', curStart, curEnd); + if (vim.visualBlock) { + cm.replaceSelections(replacement); + } else { + cm.setCursor(curStart); + cm.replaceRange('', curStart, curEnd); + } } actions.enterInsertMode(cm, {}, cm.state.vim); - cm.setCursor(curStart); }, // delete is a javascript keyword. 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { // Save the '>' mark before cm.replaceRange clears it. var selectionEnd = vim.visualMode ? vim.marks['>'].find() : null; + var text = cm.getSelection(); // If the ending line is past the last line, inclusive, instead of // including the trailing \n, include the \n before the starting line if (operatorArgs.linewise && @@ -1868,7 +1895,7 @@ curStart.ch = lineLength(cm, curStart.line); } vimGlobalState.registerController.pushText( - operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), + operatorArgs.registerName, 'delete', text, operatorArgs.linewise); if (vim.visualBlock) { var selections = cm.listSelections(); @@ -1926,8 +1953,6 @@ var curStart = ranges[0].anchor; var curEnd = ranges[0].head; if (!operatorArgs.shouldMoveCursor) { - // extendSelection swaps curStart and curEnd, so make sure - // curStart < curEnd cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd); } }, diff --git a/test/vim_test.js b/test/vim_test.js index 1c9e8c3c9a..76a2699780 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -895,6 +895,20 @@ testVim('cc_append', function(cm, vim, helpers) { helpers.doKeys('c', 'c'); eq(expectedLineCount, cm.lineCount()); }); +testVim('c_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 'c'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('1hello\n5hello\nahellofg', cm.getValue()); + cm.setCursor(2, 3); + helpers.doKeys('', '2', 'k', 'h', 'C'); + replacement = new Array(cm.listSelections().length+1).join('world ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('1hworld\n5hworld\nahworld', 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 5285494ab15ba444bc95f956c389502796b0bce1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 Jul 2014 08:51:22 +0200 Subject: [PATCH 39/62] Ignore auto indentation beyond column 80 Issue #2688 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 19abaf50b7..49fd45a084 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3812,7 +3812,7 @@ how = "not"; } else if (how == "smart") { indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass) { + if (indentation == Pass || indentation > 80) { if (!aggressive) return; how = "prev"; } From ce7536377698f682eaf3959408c70df665f690cf Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 Jul 2014 08:56:12 +0200 Subject: [PATCH 40/62] Bump indentation-ignoring threshold to 150 You might actually want to align things on long lines Issue #2688 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 49fd45a084..ad77be2cb9 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3812,7 +3812,7 @@ how = "not"; } else if (how == "smart") { indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass || indentation > 80) { + if (indentation == Pass || indentation > 150) { if (!aggressive) return; how = "prev"; } From ae978a7e9372f7bae489510c68740741dfe67e05 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 Jul 2014 11:20:31 +0200 Subject: [PATCH 41/62] Bind Cmd-Home to goDocStart on Mac Closes #2687 --- doc/manual.html | 2 +- lib/codemirror.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 2deb8422a5..490143e31b 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -815,7 +815,7 @@

    Module loaders

    Redo the last change to the selection, or the last text change if no selection changes remain.
    -
    goDocStartCtrl-Up (PC), Cmd-Up (Mac)
    +
    goDocStartCtrl-Up (PC), Cmd-Up (Mac), Cmd-Home (Mac)
    Move the cursor to the start of the document.
    goDocEndCtrl-Down (PC), Cmd-End (Mac), Cmd-Down (Mac)
    diff --git a/lib/codemirror.js b/lib/codemirror.js index ad77be2cb9..bf5d6baa2e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4824,7 +4824,7 @@ }; keyMap.macDefault = { "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", From 0b3d49f4cfc458d481a21b38d7be44fb48c1b46f Mon Sep 17 00:00:00 2001 From: Tim Alby Date: Thu, 10 Jul 2014 17:31:43 +0100 Subject: [PATCH 42/62] Fix key-binding behaviour on Mac in wrap mode Bind Cmd-Left to goLineLeft and Cmd-right to goLineRight instead of goLineStart and goLineEnd Create and bind delVisualLeft and delVisualRight to Cmd-Backspace and Cmd-Delete --- doc/manual.html | 16 +++++++++++----- lib/codemirror.js | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 490143e31b..ed0d076a9f 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -797,9 +797,15 @@

    Module loaders

    deleteLineCtrl-D (PC), Cmd-D (Mac)
    Deletes the whole line under the cursor, including newline at the end.
    -
    delLineLeftCmd-Backspace (Mac)
    +
    delLineLeft
    Delete the part of the line before the cursor.
    +
    delWrappedLineLeftCmd-Backspace (Mac)
    +
    Delete the part of the line from the left side of the visual line the cursor is on to the cursor.
    + +
    delWrappedLineRightCmd-Delete (Mac)
    +
    Delete the part of the line from the cursor to the right side of the visual line the cursor is on.
    +
    undoCtrl-Z (PC), Cmd-Z (Mac)
    Undo the last change.
    @@ -821,7 +827,7 @@

    Module loaders

    goDocEndCtrl-Down (PC), Cmd-End (Mac), Cmd-Down (Mac)
    Move the cursor to the end of the document.
    -
    goLineStartAlt-Left (PC), Cmd-Left (Mac), Ctrl-A (Mac)
    +
    goLineStartAlt-Left (PC), Ctrl-A (Mac)
    Move the cursor to the start of the line.
    goLineStartSmartHome
    @@ -829,14 +835,14 @@

    Module loaders

    already there, to the actual start of the line (including whitespace). -
    goLineEndAlt-Right (PC), Cmd-Right (Mac), Ctrl-E (Mac)
    +
    goLineEndAlt-Right (PC), Ctrl-E (Mac)
    Move the cursor to the end of the line.
    -
    goLineLeft
    +
    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.
    -
    goLineRight
    +
    goLineRightCmd-Right (Mac)
    Move the cursor to the right side of the visual line it is on.
    goLineUpUp, Ctrl-P (Mac)
    diff --git a/lib/codemirror.js b/lib/codemirror.js index bf5d6baa2e..3fef17d95e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4687,6 +4687,20 @@ return {from: Pos(range.from().line, 0), to: range.from()}; }); }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, undo: function(cm) {cm.undo();}, redo: function(cm) {cm.redo();}, undoSelection: function(cm) {cm.undoSelection();}, @@ -4825,10 +4839,10 @@ keyMap.macDefault = { "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", fallthrough: ["basic", "emacsy"] }; From 0392fd3bb51fe8b2ac0f385ff12e18299900630d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 11 Jul 2014 11:55:55 +0200 Subject: [PATCH 43/62] Catch and suppress input of certain code point on Mac Ctrl-arrow key presses were, for some reason, inserting such characters into our textarea. Issue #2689 --- lib/codemirror.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 3fef17d95e..f52f7865c6 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2388,8 +2388,11 @@ var text = input.value; // If nothing changed, bail. if (text == prevInput && !cm.somethingSelected()) return false; - // Work around nonsensical selection resetting in IE9/10 - if (ie && ie_version >= 9 && cm.display.inputHasSelection === text) { + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && cm.display.inputHasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { resetInput(cm); return false; } From f02df0b41300a1069ab5e0e3ebae3431b6b507b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Sun, 6 Jul 2014 04:30:44 +0200 Subject: [PATCH 44/62] [lint addon] Do not constrain severity names Issue #2681 --- addon/lint/lint.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addon/lint/lint.js b/addon/lint/lint.js index 604e2e6058..a87e70c09e 100644 --- a/addon/lint/lint.js +++ b/addon/lint/lint.js @@ -11,7 +11,6 @@ })(function(CodeMirror) { "use strict"; var GUTTER_ID = "CodeMirror-lint-markers"; - var SEVERITIES = /^(?:error|warning)$/; function showTooltip(e, content) { var tt = document.createElement("div"); @@ -110,7 +109,7 @@ function annotationTooltip(ann) { var severity = ann.severity; - if (!SEVERITIES.test(severity)) severity = "error"; + if (!severity) severity = "error"; var tip = document.createElement("div"); tip.className = "CodeMirror-lint-message-" + severity; tip.appendChild(document.createTextNode(ann.message)); @@ -141,7 +140,7 @@ for (var i = 0; i < anns.length; ++i) { var ann = anns[i]; var severity = ann.severity; - if (!SEVERITIES.test(severity)) severity = "error"; + if (!severity) severity = "error"; maxSeverity = getMaxSeverity(maxSeverity, severity); if (options.formatAnnotation) ann = options.formatAnnotation(ann); From 13acf661b6976badca64e69861f054111757084b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 14 Jul 2014 11:07:30 +0200 Subject: [PATCH 45/62] Don't treat %= as the start of a string Closes #2692 --- mode/ruby/ruby.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index d6c9d8796e..e7de7b57f1 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -69,7 +69,7 @@ CodeMirror.defineMode("ruby", function(config) { else if (stream.eat(/[WQ]/)) style = "string"; else if (stream.eat(/[r]/)) style = "string-2"; else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } - var delim = stream.eat(/[^\w\s]/); + var delim = stream.eat(/[^\w\s=]/); if (!delim) return "operator"; if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; return chain(readQuoted(delim, style, embed, true), stream, state); From f4ae5b42c929edc78c2df0888ae1b0179098652a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 15 Jul 2014 11:13:36 +0200 Subject: [PATCH 46/62] Track last copied text, in order to find selection boundaries on paste Issue #2697 --- lib/codemirror.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index f52f7865c6..fcaaab28bd 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2366,6 +2366,11 @@ cm.display.poll.set(20, p); } + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + // Read input from the textarea, and update the document to match. // When something is selected, it is present in the textarea, and // selected (unless it is huge, in which case a placeholder is @@ -2409,7 +2414,13 @@ var inserted = text.slice(same), textLines = splitLines(inserted); // When pasing N lines into N selections, insert one line per selection - var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length; + var multiPaste = null; + if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = lastCopied.length == doc.sel.ranges.length && map(lastCopied, splitLines); + else if (textLines.length == doc.sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } // Normal behavior is to insert the new text into every selection for (var i = doc.sel.ranges.length - 1; i >= 0; i--) { @@ -2422,7 +2433,7 @@ else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines, + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i] : textLines, origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; makeChange(cm.doc, changeEvent); signalLater(cm, "inputRead", cm, changeEvent); @@ -2589,27 +2600,29 @@ function prepareCopyCut(e) { if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); if (d.inaccurateSelection) { d.prevInput = ""; d.inaccurateSelection = false; - d.input.value = cm.getSelection(); + d.input.value = lastCopied.join("\n"); selectInput(d.input); } } else { - var text = "", ranges = []; + var text = [], ranges = []; for (var i = 0; i < cm.doc.sel.ranges.length; i++) { var line = cm.doc.sel.ranges[i].head.line; var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; ranges.push(lineRange); - text += cm.getRange(lineRange.anchor, lineRange.head); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); } if (e.type == "cut") { cm.setSelections(ranges, null, sel_dontScroll); } else { d.prevInput = ""; - d.input.value = text; + d.input.value = text.join("\n"); selectInput(d.input); } + lastCopied = text; } if (e.type == "cut") cm.state.cutIncoming = true; } From bc87689c02950857049470355f20e8a29eb19639 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 16 Jul 2014 10:04:41 +0200 Subject: [PATCH 47/62] Remove unused extra argument to computeSelAfterChange --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index fcaaab28bd..0aad3fda68 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3549,7 +3549,7 @@ antiChanges.push(historyChangeFromChange(doc, change)); - var after = i ? computeSelAfterChange(doc, change, null) : lst(source); + var after = i ? computeSelAfterChange(doc, change) : lst(source); makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); if (!i && doc.cm) doc.cm.scrollIntoView(change); var rebased = []; @@ -3608,7 +3608,7 @@ change.removed = getBetween(doc, change.from, change.to); - if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); + if (!selAfter) selAfter = computeSelAfterChange(doc, change); if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); else updateDoc(doc, change, spans); setSelectionNoUndo(doc, selAfter, sel_dontScroll); From 59138ec896618d730046fda7002d57eef02bd6a8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 16 Jul 2014 12:55:59 +0200 Subject: [PATCH 48/62] [javascript mode] Indent properly in case of function arg in wrapped arg list --- mode/javascript/javascript.js | 2 ++ mode/javascript/test.js | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index 315674be74..fdb066eb1f 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -298,6 +298,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var result = function() { var state = cx.state, indent = state.indented; if (state.lexical.type == "stat") indent = state.lexical.indented; + else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) + indent = outer.indented; state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); }; result.lex = true; diff --git a/mode/javascript/test.js b/mode/javascript/test.js index a9cc993d3d..77cc695fef 100644 --- a/mode/javascript/test.js +++ b/mode/javascript/test.js @@ -128,6 +128,12 @@ " [keyword else]", " [number 3];"); + MT("indent_funarg", + "[variable foo]([number 10000],", + " [keyword function]([def a]) {", + " [keyword debugger];", + "};"); + MT("indent_below_if", "[keyword for] (;;)", " [keyword if] ([variable foo])", From 2fa988d3b46d47a732bdbf6d5ac1f52f49b6a6e0 Mon Sep 17 00:00:00 2001 From: Sander AKA Redsandro Date: Thu, 17 Jul 2014 16:44:50 +0200 Subject: [PATCH 49/62] Disable replace for readOnly content Disable the `replace()` when the selected `CodeMirror` is `readOnly`. --- addon/search/search.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/search/search.js b/addon/search/search.js index 3ce7cc95d8..b177dce6ed 100644 --- a/addon/search/search.js +++ b/addon/search/search.js @@ -110,6 +110,7 @@ var replacementQueryDialog = 'With: '; var doReplaceConfirm = "Replace? "; function replace(cm, all) { + if (cm.getOption("readOnly")) return; dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { if (!query) return; query = parseQuery(query); From ddd36cb0b085358e797cd64ff08ed819005b9f60 Mon Sep 17 00:00:00 2001 From: binny Date: Sat, 5 Jul 2014 07:02:00 +0530 Subject: [PATCH 50/62] [vim] visual block replace --- keymap/vim.js | 33 ++++++++++++++++++++++----------- test/vim_test.js | 13 +++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index c1b0df4683..b239929f07 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2370,10 +2370,11 @@ var curStart = cm.getCursor(); var replaceTo; var curEnd; - if (vim.visualMode){ - curStart=cm.getCursor('start'); - curEnd=cm.getCursor('end'); - }else{ + var selections = cm.listSelections(); + if (vim.visualMode) { + curStart = cm.getCursor('start'); + curEnd = cm.getCursor('end'); + } else { var line = cm.getLine(curStart.line); replaceTo = curStart.ch + actionArgs.repeat; if (replaceTo > line.length) { @@ -2381,19 +2382,29 @@ } curEnd = Pos(curStart.line, replaceTo); } - if (replaceWith=='\n'){ + if (replaceWith=='\n') { if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); // special case, where vim help says to replace by just one line-break (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); - }else { - var replaceWithStr=cm.getRange(curStart, curEnd); + } else { + var replaceWithStr = cm.getRange(curStart, curEnd); //replace all characters in range by selected, but keep linebreaks - replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith); - cm.replaceRange(replaceWithStr, curStart, curEnd); - if (vim.visualMode){ + replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); + if (vim.visualBlock) { + // Tabs are split in visua block before replacing + var spaces = new Array(cm.options.tabSize+1).join(' '); + replaceWithStr = cm.getSelection(); + replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n'); + cm.replaceSelections(replaceWithStr); + } else { + cm.replaceRange(replaceWithStr, curStart, curEnd); + } + if (vim.visualMode) { + curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? + selections[0].anchor : selections[0].head; cm.setCursor(curStart); exitVisualMode(cm); - }else{ + } else { cm.setCursor(offsetCursor(curEnd, 0, -1)); } } diff --git a/test/vim_test.js b/test/vim_test.js index 76a2699780..342737de52 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1332,6 +1332,19 @@ testVim('r', function(cm, vim, helpers) { helpers.doKeys('v', 'j', 'h', 'r', ''); eq('wuuu \n her', cm.getValue(),'Replacing selection by space-characters failed'); }, { value: 'wordet\nanother' }); +testVim('r_visual_block', function(cm, vim, helpers) { + cm.setCursor(2, 3); + helpers.doKeys('', 'k', 'k', 'h', 'h', 'r', 'l'); + eq('1lll\n5lll\nalllefg', cm.getValue()); + helpers.doKeys('', 'l', 'j', 'r', ''); + eq('1 l\n5 l\nalllefg', cm.getValue()); + cm.setCursor(2, 0); + helpers.doKeys('o'); + helpers.doInsertModeKeys('Esc'); + cm.replaceRange('\t\t', cm.getCursor()); + helpers.doKeys('', 'h', 'h', 'r', 'r'); + eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); testVim('R', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('R'); From bce69920dfdce2b5eeb910bf7a7cff147a3bd5ca Mon Sep 17 00:00:00 2001 From: binny Date: Tue, 15 Jul 2014 05:32:25 +0530 Subject: [PATCH 51/62] [vim] changeCase for blockwise visual --- keymap/vim.js | 89 +++++++++++++++++++++++++++++++++++++++++--------------- test/vim_test.js | 16 ++++++++-- 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index b239929f07..e7bb885bf2 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2348,6 +2348,9 @@ } } cm.setCursor(curPosFinal); + if (vim.visualMode) { + exitVisualMode(cm); + } }, undo: function(cm, actionArgs) { cm.operation(function() { @@ -2449,17 +2452,26 @@ repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); }, changeCase: function(cm, actionArgs, vim) { - var selectedAreaRange = getSelectedAreaRange(cm, vim); - var selectionStart = selectedAreaRange[0]; - var selectionEnd = selectedAreaRange[1]; + var selectionStart = getSelectedAreaRange(cm, vim)[0]; + var text = cm.getSelection(); + var lastSelectionCurEnd; + var blockSelection; + if (vim.lastSelection) { // save the curEnd marker to avoid its removal due to cm.replaceRange - var lastSelectionCurEnd = vim.lastSelection.curEndMark.find(); + lastSelectionCurEnd = vim.lastSelection.curEndMark.find(); + blockSelection = vim.lastSelection.visualBlock; + } var toLower = actionArgs.toLower; - var text = cm.getRange(selectionStart, selectionEnd); - cm.replaceRange(toLower ? text.toLowerCase() : text.toUpperCase(), selectionStart, selectionEnd); + text = toLower ? text.toLowerCase() : text.toUpperCase(); + cm.replaceSelections(vim.visualBlock || blockSelection ? text.split('\n') : [text]); // restore the last selection curEnd marker - vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd); + if (lastSelectionCurEnd) { + vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd); + } cm.setCursor(selectionStart); + if (vim.visualMode) { + exitVisualMode(cm); + } } }; @@ -2623,27 +2635,56 @@ return -1; } function getSelectedAreaRange(cm, vim) { - var selectionStart = cm.getCursor('anchor'); - var selectionEnd = cm.getCursor('head'); var lastSelection = vim.lastSelection; - if (!vim.visualMode) { - var lastSelectionCurStart = vim.lastSelection.curStartMark.find(); - var lastSelectionCurEnd = vim.lastSelection.curEndMark.find(); - var line = lastSelectionCurEnd.line - lastSelectionCurStart.line; - var ch = line ? lastSelectionCurEnd.ch : lastSelectionCurEnd.ch - lastSelectionCurStart.ch; - selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; - if (lastSelection.visualLine) { - return [{line: selectionStart.line, ch: 0}, {line: selectionEnd.line, ch: lineLength(cm, selectionEnd.line)}]; + var getCurrentSelectedAreaRange = function() { + var selections = cm.listSelections(); + var start = selections[0]; + var end = selections[selections.length-1]; + var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; + return [selectionStart, selectionEnd]; + }; + var getLastSelectedAreaRange = function() { + var start = lastSelection.curStartMark.find(); + var end = lastSelection.curEndMark.find(); + var selectionStart = cm.getCursor(); + var selectionEnd = cm.getCursor(); + if (lastSelection.visualBlock) { + var anchor = Pos(Math.min(start.line, end.line), Math.min(start.ch, end.ch)); + var head = Pos(Math.max(start.line, end.line), Math.max(start.ch, end.ch)); + var width = head.ch - anchor.ch; + var height = head.line - anchor.line; + selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width); + var endCh = cm.clipPos(selectionEnd).ch; + // We do not want selection crossing while selecting here. + // So, we cut down the selection. + while (endCh != selectionEnd.ch) { + if (endCh-1 == selectionStart.ch) { + break; + } + selectionEnd.line--; + endCh = cm.clipPos(selectionEnd).ch; + } + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + } else { + var line = end.line - start.line; + var ch = end.ch - start.ch; + selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; + if (lastSelection.visualLine) { + selectionStart = Pos(selectionStart.line, 0); + selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line)); + } + cm.setSelection(selectionStart, selectionEnd); } + return [selectionStart, selectionEnd]; + }; + if (!vim.visualMode) { + // In case of replaying the action. + return getLastSelectedAreaRange(); } else { - if (cursorIsBefore(selectionEnd, selectionStart)) { - var tmp = selectionStart; - selectionStart = selectionEnd; - selectionEnd = tmp; - } - exitVisualMode(cm); + return getCurrentSelectedAreaRange(); } - return [selectionStart, selectionEnd]; } function updateLastSelection(cm, vim, selectionStart, selectionEnd) { if (!selectionStart || !selectionEnd) { diff --git a/test/vim_test.js b/test/vim_test.js index 342737de52..2fb1843169 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -1683,7 +1683,7 @@ testVim('reselect_visual', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('g', 'v'); // here the fake cursor is at (1, 3) - helpers.assertCursorAt(2, 0); + helpers.assertCursorAt(1, 4); eqPos(makeCursor(1, 0), cm.getCursor('anchor')); helpers.doKeys('v'); cm.setCursor(2, 0); @@ -1753,7 +1753,7 @@ testVim('o_visual_block', function(cm, vim, helpers) { helpers.doKeys('o'); helpers.assertCursorAt(3, 1); }, { value: 'abcd\nefgh\nijkl\nmnop'}); -testVim('uppercase/lowercase_visual', function(cm, vim, helpers) { +testVim('changeCase_visual', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('v', 'l', 'l'); helpers.doKeys('U'); @@ -1772,6 +1772,18 @@ testVim('uppercase/lowercase_visual', function(cm, vim, helpers) { helpers.doKeys('V', 'U', 'j', '.'); eq('ABCDEF\nGHIJKL\nMnopq\nSHORT LINE\nLONG LINE OF TEXT', cm.getValue()); }, { value: 'abcdef\nghijkl\nmnopq\nshort line\nlong line of text'}); +testVim('changeCase_visual_block', function(cm, vim, helpers) { + cm.setCursor(2, 1); + helpers.doKeys('', 'k', 'k', 'h', 'U'); + eq('ABcdef\nGHijkl\nMNopq\nfoo', cm.getValue()); + cm.setCursor(0, 2); + helpers.doKeys('.'); + eq('ABCDef\nGHIJkl\nMNOPq\nfoo', cm.getValue()); + // check when last line is shorter. + cm.setCursor(2, 2); + helpers.doKeys('.'); + eq('ABCDef\nGHIJkl\nMNOPq\nfoO', cm.getValue()); +}, { value: 'abcdef\nghijkl\nmnopq\nfoo'}); testVim('visual_paste', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('v', 'l', 'l', 'y', 'j', 'v', 'l', 'p'); From 9312c712a80d665e569e29ea8448e6bb52c03b69 Mon Sep 17 00:00:00 2001 From: binny Date: Fri, 18 Jul 2014 05:30:15 +0530 Subject: [PATCH 52/62] [vim] using dot to replay actions and operators --- keymap/vim.js | 111 +++++++++++++++++++++++++++++++++++++------------------ test/vim_test.js | 17 ++++++++- 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index e7bb885bf2..16f51b3bf1 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1411,6 +1411,7 @@ if (operator) { var inverted = false; vim.lastMotion = null; + var lastSelection = vim.lastSelection; operatorArgs.repeat = repeat; // Indent in visual mode needs this. if (vim.visualMode) { curStart = selectionStart; @@ -1437,6 +1438,24 @@ curEnd.line = curStart.line + operatorArgs.selOffset.line; if (operatorArgs.selOffset.line) {curEnd.ch = operatorArgs.selOffset.ch; } else { curEnd.ch = curStart.ch + operatorArgs.selOffset.ch; } + // In case of blockwise visual + if (lastSelection && lastSelection.visualBlock) { + var block = lastSelection.visualBlock; + var width = block.width; + var height = block.height; + curEnd = Pos(curStart.line + height, curStart.ch + width); + // selectBlock creates a 'proper' rectangular block. + // We do not want that in all cases, so we manually set selections. + var selections = []; + for (var i = curStart.line; i < curEnd.line; i++) { + var anchor = Pos(i, curStart.ch); + var head = Pos(i, curEnd.ch); + var range = {anchor: anchor, head: head}; + selections.push(range); + } + cm.setSelections(selections); + var blockSelected = true; + } } else if (vim.visualMode) { var selOffset = Pos(); selOffset.line = curEnd.line - curStart.line; @@ -1457,8 +1476,8 @@ operatorArgs.registerName = registerName; // Keep track of linewise as it affects how paste and change behave. operatorArgs.linewise = linewise; - if (!vim.visualBlock) { - cm.extendSelection(curStart, curEnd); + if (!vim.visualBlock && !blockSelected) { + cm.setSelection(curStart, curEnd); } operators[operator](cm, operatorArgs, vim, curStart, curEnd, curOriginal); @@ -1838,6 +1857,8 @@ var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; var text = cm.getSelection(); var replacement = new Array(selections.length).join('1').split('1'); + // save the selectionEnd mark + var selectionEnd = vim.marks['>'] ? vim.marks['>'].find() : cm.getCursor('head'); vimGlobalState.registerController.pushText( operatorArgs.registerName, 'change', text, operatorArgs.linewise); @@ -1880,34 +1901,52 @@ cm.replaceRange('', curStart, curEnd); } } + vim.marks['>'] = cm.setBookmark(selectionEnd); actions.enterInsertMode(cm, {}, cm.state.vim); }, // delete is a javascript keyword. - 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { + 'delete': function(cm, operatorArgs, vim) { + var selections = cm.listSelections(); + var start = selections[0], end = selections[selections.length-1]; + var curStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; // Save the '>' mark before cm.replaceRange clears it. - var selectionEnd = vim.visualMode ? vim.marks['>'].find() : null; + var selectionEnd, selectionStart; + 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(); + } var text = cm.getSelection(); + vimGlobalState.registerController.pushText( + operatorArgs.registerName, 'delete', text, + operatorArgs.linewise); + 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 if (operatorArgs.linewise && - curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) { + curEnd.line == cm.lastLine() && curStart.line == curEnd.line) { + var tmp = copyCursor(curEnd); curStart.line--; curStart.ch = lineLength(cm, curStart.line); - } - vimGlobalState.registerController.pushText( - operatorArgs.registerName, 'delete', text, - operatorArgs.linewise); - if (vim.visualBlock) { - var selections = cm.listSelections(); - curStart = selections[0].anchor; - var replacement = new Array(selections.length).join('1').split('1'); - cm.replaceSelections(replacement); - } else { + curEnd = tmp; cm.replaceRange('', curStart, curEnd); + } else { + cm.replaceSelections(replacement); } // restore the saved bookmark if (selectionEnd) { - vim.marks['>'] = cm.setBookmark(selectionEnd); + var curStartMark = cm.setBookmark(selectionStart); + var curEndMark = cm.setBookmark(selectionEnd); + if (vim.visualMode) { + vim.marks['<'] = curStartMark; + vim.marks['>'] = curEndMark; + } else { + vim.lastSelection.curStartMark = curStartMark; + vim.lastSelection.curEndMark = curEndMark; + } } if (operatorArgs.linewise) { cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); @@ -2645,29 +2684,26 @@ return [selectionStart, selectionEnd]; }; var getLastSelectedAreaRange = function() { - var start = lastSelection.curStartMark.find(); - var end = lastSelection.curEndMark.find(); var selectionStart = cm.getCursor(); var selectionEnd = cm.getCursor(); - if (lastSelection.visualBlock) { - var anchor = Pos(Math.min(start.line, end.line), Math.min(start.ch, end.ch)); - var head = Pos(Math.max(start.line, end.line), Math.max(start.ch, end.ch)); - var width = head.ch - anchor.ch; - var height = head.line - anchor.line; + var block = lastSelection.visualBlock; + if (block) { + var width = block.width; + var height = block.height; selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width); - var endCh = cm.clipPos(selectionEnd).ch; - // We do not want selection crossing while selecting here. - // So, we cut down the selection. - while (endCh != selectionEnd.ch) { - if (endCh-1 == selectionStart.ch) { - break; - } - selectionEnd.line--; - endCh = cm.clipPos(selectionEnd).ch; + var selections = []; + // selectBlock creates a 'proper' rectangular block. + // We do not want that in all cases, so we manually set selections. + for (var i = selectionStart.line; i < selectionEnd.line; i++) { + var anchor = Pos(i, selectionStart.ch); + var head = Pos(i, selectionEnd.ch); + var range = {anchor: anchor, head: head}; + selections.push(range); } - cm.setCursor(selectionStart); - selectBlock(cm, selectionEnd); + cm.setSelections(selections); } else { + var start = lastSelection.curStartMark.find(); + var end = lastSelection.curEndMark.find(); var line = end.line - start.line; var ch = end.ch - start.ch; selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; @@ -2700,13 +2736,18 @@ // This check ensures to set the cursor // position where we left off in previous selection var swap = getIndex(ranges, selectionStart) > -1; + if (vim.visualBlock) { + var height = Math.abs(selectionStart.line - selectionEnd.line)+1; + var width = Math.abs(selectionStart.ch - selectionEnd.ch); + var block = {height: height, width: width}; + } // can't use selection state here because yank has already reset its cursor // Also, Bookmarks make the visual selections robust to edit operations vim.lastSelection = {'curStartMark': cm.setBookmark(swap ? selectionEnd : selectionStart), 'curEndMark': cm.setBookmark(swap ? selectionStart : selectionEnd), 'visualMode': vim.visualMode, 'visualLine': vim.visualLine, - 'visualBlock': vim.visualBlock}; + 'visualBlock': block}; } function exitVisualMode(cm) { diff --git a/test/vim_test.js b/test/vim_test.js index 2fb1843169..7c24634a89 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -944,7 +944,22 @@ testVim('visual_block_~', function(cm, vim, helpers) { helpers.assertCursorAt(2, 0); eq('hello\nwoRLd\nAbcDe', cm.getValue()); },{value: 'hello\nwOrld\nabcde' }); - +testVim('._swapCase_visualBlock', function(cm, vim, helpers) { + helpers.doKeys('', 'j', 'j', 'l', '~'); + cm.setCursor(0, 3); + helpers.doKeys('.'); + eq('HelLO\nWorLd\nAbcdE', cm.getValue()); +},{value: 'hEllo\nwOrlD\naBcDe' }); +testVim('._delete_visualBlock', function(cm, vim, helpers) { + helpers.doKeys('', 'j', 'x'); + eq('ive\ne\nsome\nsugar', cm.getValue()); + helpers.doKeys('.'); + eq('ve\n\nsome\nsugar', cm.getValue()); + helpers.doKeys('j', 'j', '.'); + eq('ve\n\nome\nugar', cm.getValue()); + helpers.doKeys('u', '', '.'); + eq('ve\n\nme\ngar', cm.getValue()); +},{value: 'give\nme\nsome\nsugar' }); testVim('>{motion}', function(cm, vim, helpers) { cm.setCursor(1, 3); var expectedLineCount = cm.lineCount(); From 252a9d6cb8afd782762bf10c75760cbbd75a5c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Sat, 12 Jul 2014 20:20:08 -0400 Subject: [PATCH 53/62] [clike mode] Enable multilineString for scala It is a good approximation --- mode/clike/clike.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mode/clike/clike.js b/mode/clike/clike.js index 3e253624b4..2873e3629b 100644 --- a/mode/clike/clike.js +++ b/mode/clike/clike.js @@ -369,6 +369,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { ), + multiLineStrings: true, blockKeywords: words("catch class do else finally for forSome if match switch try while"), atoms: words("true false null"), hooks: { From 58d2a8a8b28e6f45ca98729952870de92184cf05 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 20 Jul 2014 09:34:51 +0200 Subject: [PATCH 54/62] [yaml mode] Be less restrictive about keys in front of colons Issue #2695 --- mode/yaml/yaml.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/yaml/yaml.js b/mode/yaml/yaml.js index 4ebe5c81cf..15a5916df1 100644 --- a/mode/yaml/yaml.js +++ b/mode/yaml/yaml.js @@ -80,7 +80,7 @@ CodeMirror.defineMode("yaml", function() { } /* pairs (associative arrays) -> key */ - if (!state.pair && stream.match(/^\s*\S+(?=\s*:($|\s))/i)) { + if (!state.pair && stream.match(/^\s*[^\-:{}"\[\]][^:{}"\[\]]*(?=\s*:($|\s))/i)) { state.pair = true; state.keyCol = stream.indentation(); return "atom"; From dff9738301bedfc57e0a3f3a2168edb05fd5dcf9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 20 Jul 2014 09:41:56 +0200 Subject: [PATCH 55/62] [puppet mode] Make regexp for regexps non-greedy Issue #2696 --- mode/puppet/puppet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/puppet/puppet.js b/mode/puppet/puppet.js index 66698bb6ad..b407ded883 100644 --- a/mode/puppet/puppet.js +++ b/mode/puppet/puppet.js @@ -176,7 +176,7 @@ CodeMirror.defineMode("puppet", function () { // Match characters that we are going to assume // are trying to be regex if (ch == '/') { - stream.match(/.*\//); + stream.match(/.*?\//); return 'variable-3'; } // Match all the numbers From 4e7e863c0e34f9fc450351d408a3f585d3843549 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 20 Jul 2014 10:26:22 +0200 Subject: [PATCH 56/62] Remove a few unneccesary property accesses --- lib/codemirror.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 0aad3fda68..8b3feac507 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3815,7 +3815,7 @@ if (how == "smart") { // Fall back to "prev" when the mode doesn't have an indentation // method. - if (!cm.doc.mode.indent) how = "prev"; + if (!doc.mode.indent) how = "prev"; else state = getStateBefore(cm, n); } @@ -3827,7 +3827,7 @@ indentation = 0; how = "not"; } else if (how == "smart") { - indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); if (indentation == Pass || indentation > 150) { if (!aggressive) return; how = "prev"; @@ -3851,7 +3851,7 @@ if (pos < indentation) indentString += spaceStr(indentation - pos); if (indentString != curSpaceString) { - replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); } else { // Ensure that, if the cursor was in the whitespace at the start // of the line, it is moved to the end of that space. From 1ddba34bb6e467282eeda6a79bd16c9650668169 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 20 Jul 2014 10:56:24 +0200 Subject: [PATCH 57/62] Force stable y scroll when focusing textarea in onContextMenu Closes #2712 --- lib/codemirror.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index 8b3feac507..09ea0a3134 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3319,7 +3319,9 @@ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) focusInput(cm); + if (webkit) window.scrollTo(null, oldScrollY); resetInput(cm); // Adds "Select all" to context menu in FF if (!cm.somethingSelected()) display.input.value = display.prevInput = " "; From 1d35536ee0f23be031beba12309a38db9f49b4d5 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sun, 20 Jul 2014 14:01:11 -0700 Subject: [PATCH 58/62] [vim] Do not open prompt if no cm.openDialog --- keymap/vim.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 16f51b3bf1..a47b005d02 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -561,7 +561,9 @@ MacroModeState.prototype = { exitMacroRecordMode: function() { var macroModeState = vimGlobalState.macroModeState; - macroModeState.onRecordingDone(); // close dialog + if (macroModeState.onRecordingDone) { + macroModeState.onRecordingDone(); // close dialog + } macroModeState.onRecordingDone = undefined; macroModeState.isRecording = false; }, @@ -571,8 +573,10 @@ if (register) { register.clear(); this.latestRegister = registerName; - this.onRecordingDone = cm.openDialog( - '(recording)['+registerName+']', null, {bottom:true}); + if (cm.openDialog) { + this.onRecordingDone = cm.openDialog( + '(recording)['+registerName+']', null, {bottom:true}); + } this.isRecording = true; } } From 1d4b525f8127e79aaee811ff2b6972a3fa76a7bd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 21 Jul 2014 08:28:07 +0200 Subject: [PATCH 59/62] [yaml mode] Tweak key regexp Issue #2695 --- mode/yaml/yaml.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/yaml/yaml.js b/mode/yaml/yaml.js index 15a5916df1..332aef6a23 100644 --- a/mode/yaml/yaml.js +++ b/mode/yaml/yaml.js @@ -80,7 +80,7 @@ CodeMirror.defineMode("yaml", function() { } /* pairs (associative arrays) -> key */ - if (!state.pair && stream.match(/^\s*[^\-:{}"\[\]][^:{}"\[\]]*(?=\s*:($|\s))/i)) { + if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) { state.pair = true; state.keyCol = stream.indentation(); return "atom"; From e02b946bfdf68ac4c3933b99e119a8fa9898efb6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 21 Jul 2014 08:33:42 +0200 Subject: [PATCH 60/62] Also split pasted content by selection when selection length is a multiple of clipboard length Issue #2697 --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 09ea0a3134..31c6d71be5 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2417,7 +2417,7 @@ var multiPaste = null; if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) { if (lastCopied && lastCopied.join("\n") == inserted) - multiPaste = lastCopied.length == doc.sel.ranges.length && map(lastCopied, splitLines); + multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); else if (textLines.length == doc.sel.ranges.length) multiPaste = map(textLines, function(l) { return [l]; }); } @@ -2433,7 +2433,7 @@ else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i] : textLines, + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; makeChange(cm.doc, changeEvent); signalLater(cm, "inputRead", cm, changeEvent); From ee088bc36b3fca2771835cc42febcabeb19e791c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Mon, 14 Jul 2014 08:32:17 +0200 Subject: [PATCH 61/62] [bower.json] Normalized a package name The package name on http://bower.io/search/ is lowercase. --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index b103179156..407b86d649 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,5 @@ { - "name": "CodeMirror", + "name": "codemirror", "version":"4.3.1", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ From 485a7da897e378025268e686f4eb79377973219e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 21 Jul 2014 08:51:28 +0200 Subject: [PATCH 62/62] Mark release 4.4 --- AUTHORS | 9 +++++++++ bower.json | 2 +- doc/compress.html | 1 + doc/manual.html | 2 +- doc/releases.html | 14 ++++++++++++++ index.html | 2 +- lib/codemirror.js | 2 +- package.json | 2 +- 8 files changed, 29 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index f5f569721e..0c2f67c31a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ Alberto Pose Albert Xing Alexander Pavlov Alexander Schepanovski +Alexander Shvets Alexander Solovyov Alexandre Bique alexey-k @@ -168,6 +169,7 @@ Jason Grout Jason Johnston Jason San Jose Jason Siefken +Jaydeep Solanki Jean Boussier jeffkenton Jeff Pickhardt @@ -205,6 +207,7 @@ kubelsmieci Lanny Laszlo Vidacs leaf corcoran +Leonid Khachaturov Leonya Khachaturov Liam Newman LM @@ -269,6 +272,7 @@ Niels van Groningen Nikita Beloglazov Nikita Vasilyev Nikolay Kostov +nilp0inter nlwillia pablo Page @@ -291,7 +295,9 @@ Radek Piórkowski Rahul Randy Edmunds Rasmus Erik Voel Jensen +Richard van der Meer Richard Z.H. Wang +Roberto Abdelkader Martínez Pérez robertop23 Robert Plummer Ruslan Osmanov @@ -299,6 +305,7 @@ Ryan Prior sabaca Samuel Ainsworth sandeepshetty +Sander AKA Redsandro santec Sascha Peilicke satchmorun @@ -330,6 +337,7 @@ Thaddee Tyl think Thomas Dvornik Thomas Schmid +Tim Alby Tim Baumann Timothy Farrell Timothy Hatcher @@ -349,6 +357,7 @@ Volker Mische wenli Wesley Wiser William Jamieson +William Stein Wojtek Ptak Xavier Mendez YNH Webdev diff --git a/bower.json b/bower.json index 407b86d649..8c57fcdd4b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.3.1", + "version":"4.4.0", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/doc/compress.html b/doc/compress.html index ede45e1c6e..859210c45c 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -36,6 +36,7 @@

    Version: