diff --git a/kitchen-sink.html b/kitchen-sink.html
index b2ab3577..65fe2a2a 100644
--- a/kitchen-sink.html
+++ b/kitchen-sink.html
@@ -12,8 +12,8 @@
-->
-
+
+
@@ -96,7 +96,6 @@
diff --git a/lib/ace/editor.js b/lib/ace/editor.js
index 13aa2f60..59b136b1 100644
--- a/lib/ace/editor.js
+++ b/lib/ace/editor.js
@@ -2476,7 +2476,6 @@ var Editor = function(renderer, session) {
**/
this.undo = function() {
this.$blockScrolling++;
- this.session.$syncInformUndoManager();
this.session.getUndoManager().undo();
this.$blockScrolling--;
this.renderer.scrollCursorIntoView(null, 0.5);
@@ -2488,7 +2487,6 @@ var Editor = function(renderer, session) {
**/
this.redo = function() {
this.$blockScrolling++;
- this.session.$syncInformUndoManager();
this.session.getUndoManager().redo();
this.$blockScrolling--;
this.renderer.scrollCursorIntoView(null, 0.5);
diff --git a/lib/ace/keyboard/vim.js b/lib/ace/keyboard/vim.js
index 0e860db1..cbf3dda3 100644
--- a/lib/ace/keyboard/vim.js
+++ b/lib/ace/keyboard/vim.js
@@ -62,7 +62,7 @@
define(function(require, exports, module) {
'use strict';
- /* function log() {
+ function log() {
var d = "";
function format(p) {
if (typeof p != "object")
@@ -83,7 +83,7 @@ define(function(require, exports, module) {
d+= f+" "
}
console.log(d)
- } */
+ }
var Range = require("../range").Range;
var EventEmitter = require("../lib/event_emitter").EventEmitter;
var dom = require("../lib/dom");
@@ -297,8 +297,10 @@ define(function(require, exports, module) {
ranges.push(ranges.splice(primIndex, 1)[0]);
}
sel.toSingleRange(ranges[0].clone());
+ var session = this.ace.session;
for (var i = 0; i < ranges.length; i++) {
- sel.addRange(ranges[i]);
+ var range = session.$clipRangeToDocument(ranges[i]); // todo why ace doesn't do this?
+ sel.addRange(range);
}
};
this.setSelection = function(a, h, options) {
@@ -951,10 +953,14 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Operator-Motion dual commands
{ keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
{ keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
- { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
- { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
- { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
- { keys: '~', type: 'operatorMotion', operator: 'changeCase', operatorArgs: { shouldMoveCursor: true }, motion: 'moveByCharacters', motionArgs: { forward: true }},
+ { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
+ { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
+ { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
+ { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
{ keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
// Actions
{ keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
@@ -965,7 +971,8 @@ dom.importCssString(".normal-mode .ace_cursor{\
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
{ keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
- { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank' }},
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
{ keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
{ keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
{ keys: 'v', type: 'action', action: 'toggleVisualMode' },
@@ -1069,7 +1076,6 @@ dom.importCssString(".normal-mode .ace_cursor{\
CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
cm.on('keypress', handleKeyPress);
cm.on('keydown', handleKeyDown);
- CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
}
function leaveVimMode(cm) {
@@ -1350,7 +1356,9 @@ dom.importCssString(".normal-mode .ace_cursor{\
visualLine: false,
visualBlock: false,
lastSelection: null,
- lastPastedText: null
+ lastPastedText: null,
+ sel: {
+ }
};
}
return cm.state.vim;
@@ -1406,6 +1414,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Add user defined key bindings.
exCommandDispatcher.map(lhs, rhs, ctx);
},
+ unmap: function(lhs, ctx) {
+ // remove user defined key bindings.
+ exCommandDispatcher.unmap(lhs, ctx);
+ },
setOption: setOption,
getOption: getOption,
defineOption: defineOption,
@@ -1529,6 +1541,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
} catch (e) {
// clear VIM state in case it's in a bad state.
cm.state.vim = undefined;
+ maybeInitVimState(cm);
throw e;
}
});
@@ -2032,13 +2045,13 @@ dom.importCssString(".normal-mode .ace_cursor{\
var operator = inputState.operator;
var operatorArgs = inputState.operatorArgs || {};
var registerName = inputState.registerName;
- var selectionEnd = copyCursor(cm.getCursor('head'));
- var selectionStart = copyCursor(cm.getCursor('anchor'));
- // The difference between cur and selection cursors are that cur is
- // being operated on and ignores that there is a selection.
- var curStart = copyCursor(selectionEnd);
- var curOriginal = copyCursor(curStart);
- var curEnd;
+ var sel = vim.sel;
+ // TODO: Make sure cm and vim selections are identical outside visual mode.
+ var origHead = copyCursor(vim.visualMode ? sel.head: cm.getCursor('head'));
+ var origAnchor = copyCursor(vim.visualMode ? sel.anchor : cm.getCursor('anchor'));
+ var oldHead = copyCursor(origHead);
+ var oldAnchor = copyCursor(origAnchor);
+ var newHead, newAnchor;
var repeat;
if (operator) {
this.recordLastEdit(vim, inputState);
@@ -2065,7 +2078,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
motionArgs.repeat = repeat;
clearInputState(cm);
if (motion) {
- var motionResult = motions[motion](cm, motionArgs, vim);
+ var motionResult = motions[motion](cm, origHead, motionArgs, vim);
vim.lastMotion = motions[motion];
if (!motionResult) {
return;
@@ -2078,159 +2091,139 @@ dom.importCssString(".normal-mode .ace_cursor{\
recordJumpPosition(cm, cachedCursor, motionResult);
delete jumpList.cachedCursor;
} else {
- recordJumpPosition(cm, curOriginal, motionResult);
+ recordJumpPosition(cm, origHead, motionResult);
}
}
if (motionResult instanceof Array) {
- curStart = motionResult[0];
- curEnd = motionResult[1];
+ newAnchor = motionResult[0];
+ newHead = motionResult[1];
} else {
- curEnd = motionResult;
+ newHead = motionResult;
}
// TODO: Handle null returns from motion commands better.
- if (!curEnd) {
- curEnd = Pos(curStart.line, curStart.ch);
+ if (!newHead) {
+ newHead = copyCursor(origHead);
}
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;
- 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;
- offset = 1;
- }
- // 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) {
- vim.lastHPos = curEnd.ch;
- }
- selectionEnd = curEnd;
- selectionStart = (motionResult instanceof Array) ? curStart : selectionStart;
- if (vim.visualLine) {
- if (cursorIsBefore(selectionStart, selectionEnd)) {
- selectionStart.ch = 0;
-
- var lastLine = cm.lastLine();
- if (selectionEnd.line > lastLine) {
- selectionEnd.line = lastLine;
- }
- selectionEnd.ch = lineLength(cm, selectionEnd.line);
- } else {
- selectionEnd.ch = 0;
- selectionStart.ch = lineLength(cm, selectionStart.line);
- }
- } else if (vim.visualBlock) {
- // Select a block and
- // return the diagonally opposite end.
- selectionStart = selectBlock(cm, selectionEnd);
- }
- if (!vim.visualBlock) {
- cm.setSelection(selectionStart, selectionEnd);
+ newHead = clipCursorToContent(cm, newHead, true);
+ if (newAnchor) {
+ newAnchor = clipCursorToContent(cm, newAnchor, true);
}
+ newAnchor = newAnchor || oldAnchor;
+ sel.anchor = newAnchor;
+ sel.head = newHead;
+ updateCmSelection(cm);
updateMark(cm, vim, '<',
- cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
- : selectionEnd);
+ cursorIsBefore(newAnchor, newHead) ? newAnchor
+ : newHead);
updateMark(cm, vim, '>',
- cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
- : selectionStart);
+ cursorIsBefore(newAnchor, newHead) ? newHead
+ : newAnchor);
} else if (!operator) {
- curEnd = clipCursorToContent(cm, curEnd);
- cm.setCursor(curEnd.line, curEnd.ch);
+ newHead = clipCursorToContent(cm, newHead);
+ cm.setCursor(newHead.line, newHead.ch);
}
}
-
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;
- curEnd = selectionEnd;
- motionArgs.inclusive = true;
- operatorArgs.shouldMoveCursor = false;
- }
- // Swap start and end if motion was backward.
- if (curEnd && cursorIsBefore(curEnd, curStart)) {
- var tmp = curStart;
- curStart = curEnd;
- curEnd = tmp;
- inverted = true;
- } else if (!curEnd) {
- curEnd = copyCursor(curStart);
- }
- if (motionArgs.inclusive && !vim.visualMode) {
- // Move the selection end one to the right to include the last
- // character.
- curEnd.ch++;
- }
- if (operatorArgs.selOffset) {
+ if (operatorArgs.lastSel) {
// Replaying a visual mode operation
- 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;
+ newAnchor = oldAnchor;
+ var lastSel = operatorArgs.lastSel;
+ var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line);
+ var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch);
+ if (lastSel.visualLine) {
+ // Linewise Visual mode: The same number of lines.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
+ } else if (lastSel.visualBlock) {
+ // Blockwise Visual mode: The same number of lines and columns.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset);
+ } else if (lastSel.head.line == lastSel.anchor.line) {
+ // Normal Visual mode within one line: The same number of characters.
+ newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset);
+ } else {
+ // Normal Visual mode with several lines: The same number of lines, in the
+ // last line the same number of characters as in the last line the last time.
+ newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch);
}
+ vim.visualMode = true;
+ vim.visualLine = lastSel.visualLine;
+ vim.visualBlock = lastSel.visualBlock;
+ sel = vim.sel = {
+ anchor: newAnchor,
+ head: newHead
+ };
+ updateCmSelection(cm);
} else if (vim.visualMode) {
- var selOffset = Pos();
- selOffset.line = curEnd.line - curStart.line;
- if (selOffset.line) { selOffset.ch = curEnd.ch; }
- else { selOffset.ch = curEnd.ch - curStart.ch; }
- operatorArgs.selOffset = selOffset;
+ operatorArgs.lastSel = {
+ anchor: copyCursor(sel.anchor),
+ head: copyCursor(sel.head),
+ visualBlock: vim.visualBlock,
+ visualLine: vim.visualLine
+ };
}
- var linewise = motionArgs.linewise ||
- (vim.visualMode && vim.visualLine) ||
- operatorArgs.linewise;
- if (linewise) {
- // Expand selection to entire line.
- expandSelectionToLine(cm, curStart, curEnd);
- } else if (motionArgs.forward) {
- // Clip to trailing newlines only if the motion goes forward.
- clipToLine(cm, curStart, curEnd);
+ var curStart, curEnd, linewise, mode;
+ var cmSel;
+ if (vim.visualMode) {
+ // Init visual op
+ curStart = cursorMin(sel.head, sel.anchor);
+ curEnd = cursorMax(sel.head, sel.anchor);
+ linewise = vim.visualLine || operatorArgs.linewise;
+ mode = vim.visualBlock ? 'block' :
+ linewise ? 'line' :
+ 'char';
+ cmSel = makeCmSelection(cm, {
+ anchor: curStart,
+ head: curEnd
+ }, mode);
+ if (linewise) {
+ var ranges = cmSel.ranges;
+ if (mode == 'block') {
+ // Linewise operators in visual block mode extend to end of line
+ for (var i = 0; i < ranges.length; i++) {
+ ranges[i].head.ch = lineLength(cm, ranges[i].head.line);
+ }
+ } else if (mode == 'line') {
+ ranges[0].head = Pos(ranges[0].head.line + 1, 0);
+ }
+ }
+ } else {
+ // Init motion op
+ curStart = copyCursor(newAnchor || oldAnchor);
+ curEnd = copyCursor(newHead || oldHead);
+ if (cursorIsBefore(curEnd, curStart)) {
+ var tmp = curStart;
+ curStart = curEnd;
+ curEnd = tmp;
+ }
+ linewise = motionArgs.linewise || operatorArgs.linewise;
+ if (linewise) {
+ // Expand selection to entire line.
+ expandSelectionToLine(cm, curStart, curEnd);
+ } else if (motionArgs.forward) {
+ // Clip to trailing newlines only if the motion goes forward.
+ clipToLine(cm, curStart, curEnd);
+ }
+ mode = 'char';
+ var exclusive = !motionArgs.inclusive || linewise;
+ cmSel = makeCmSelection(cm, {
+ anchor: curStart,
+ head: curEnd
+ }, mode, exclusive);
}
+ cm.setSelections(cmSel.ranges, cmSel.primary);
+ vim.lastMotion = null;
+ operatorArgs.repeat = repeat; // For indent in visual mode.
operatorArgs.registerName = registerName;
// Keep track of linewise as it affects how paste and change behave.
operatorArgs.linewise = linewise;
- if (!vim.visualBlock && !blockSelected) {
- cm.setSelection(curStart, curEnd);
- }
- operators[operator](cm, operatorArgs, vim, curStart,
- curEnd, curOriginal);
+ var operatorMoveTo = operators[operator](
+ cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
if (vim.visualMode) {
exitVisualMode(cm);
}
+ if (operatorMoveTo) {
+ cm.setCursor(operatorMoveTo);
+ }
}
},
recordLastEdit: function(vim, inputState, actionCommand) {
@@ -2249,7 +2242,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
*/
// All of the functions below return Cursor objects.
var motions = {
- moveToTopLine: function(cm, motionArgs) {
+ moveToTopLine: function(cm, _head, motionArgs) {
var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
},
@@ -2258,17 +2251,17 @@ dom.importCssString(".normal-mode .ace_cursor{\
var line = Math.floor((range.top + range.bottom) * 0.5);
return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
},
- moveToBottomLine: function(cm, motionArgs) {
+ moveToBottomLine: function(cm, _head, motionArgs) {
var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line)));
},
- expandToLine: function(cm, motionArgs) {
+ expandToLine: function(_cm, head, motionArgs) {
// Expands forward to end of line, and then to next line if repeat is
// >1. Does not handle backward motion!
- var cur = cm.getCursor();
+ var cur = head;
return Pos(cur.line + motionArgs.repeat - 1, Infinity);
},
- findNext: function(cm, motionArgs) {
+ findNext: function(cm, _head, motionArgs) {
var state = getSearchState(cm);
var query = state.getQuery();
if (!query) {
@@ -2280,7 +2273,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
highlightSearchMatches(cm, query);
return findNext(cm, prev/** prev */, query, motionArgs.repeat);
},
- goToMark: function(cm, motionArgs, vim) {
+ goToMark: function(cm, _head, motionArgs, vim) {
var mark = vim.marks[motionArgs.selectedCharacter];
if (mark) {
var pos = mark.find();
@@ -2288,22 +2281,19 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
return null;
},
- 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);
+ moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) {
+ if (vim.visualBlock && motionArgs.sameLine) {
+ var sel = vim.sel;
+ return [
+ clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)),
+ clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch))
+ ];
} else {
- curStart = ranges[curIndex].anchor;
+ return ([vim.sel.head, vim.sel.anchor]);
}
- cm.setCursor(curEnd);
- return ([curEnd, curStart]);
},
- jumpToMark: function(cm, motionArgs, vim) {
- var best = cm.getCursor();
+ jumpToMark: function(cm, head, motionArgs, vim) {
+ var best = head;
for (var i = 0; i < motionArgs.repeat; i++) {
var cursor = best;
for (var key in vim.marks) {
@@ -2340,14 +2330,14 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
return best;
},
- moveByCharacters: function(cm, motionArgs) {
- var cur = cm.getCursor();
+ moveByCharacters: function(_cm, head, motionArgs) {
+ var cur = head;
var repeat = motionArgs.repeat;
var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
return Pos(cur.line, ch);
},
- moveByLines: function(cm, motionArgs, vim) {
- var cur = cm.getCursor();
+ moveByLines: function(cm, head, motionArgs, vim) {
+ var cur = head;
var endCh = cur.ch;
// Depending what our last motion was, we may want to do different
// things. If our last motion was moving vertically, we want to
@@ -2382,8 +2372,8 @@ dom.importCssString(".normal-mode .ace_cursor{\
vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left;
return Pos(line, endCh);
},
- moveByDisplayLines: function(cm, motionArgs, vim) {
- var cur = cm.getCursor();
+ moveByDisplayLines: function(cm, head, motionArgs, vim) {
+ var cur = head;
switch (vim.lastMotion) {
case this.moveByDisplayLines:
case this.moveByScroll:
@@ -2410,16 +2400,16 @@ dom.importCssString(".normal-mode .ace_cursor{\
vim.lastHPos = res.ch;
return res;
},
- moveByPage: function(cm, motionArgs) {
+ moveByPage: function(cm, head, motionArgs) {
// CodeMirror only exposes functions that move the cursor page down, so
// doing this bad hack to move the cursor and move it back. evalInput
// will move the cursor to where it should be in the end.
- var curStart = cm.getCursor();
+ var curStart = head;
var repeat = motionArgs.repeat;
return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page');
},
- moveByParagraph: function(cm, motionArgs) {
- var line = cm.getCursor().line;
+ moveByParagraph: function(cm, head, motionArgs) {
+ var line = head.line;
var repeat = motionArgs.repeat;
var inc = motionArgs.forward ? 1 : -1;
for (var i = 0; i < repeat; i++) {
@@ -2434,16 +2424,16 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
return Pos(line, 0);
},
- moveByScroll: function(cm, motionArgs, vim) {
+ moveByScroll: function(cm, head, motionArgs, vim) {
var scrollbox = cm.getScrollInfo();
var curEnd = null;
var repeat = motionArgs.repeat;
if (!repeat) {
repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
}
- var orig = cm.charCoords(cm.getCursor(), 'local');
+ var orig = cm.charCoords(head, 'local');
motionArgs.repeat = repeat;
- var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
+ var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim);
if (!curEnd) {
return null;
}
@@ -2451,11 +2441,11 @@ dom.importCssString(".normal-mode .ace_cursor{\
cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
return curEnd;
},
- moveByWords: function(cm, motionArgs) {
- return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
+ moveByWords: function(cm, head, motionArgs) {
+ return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward,
!!motionArgs.wordEnd, !!motionArgs.bigWord);
},
- moveTillCharacter: function(cm, motionArgs) {
+ moveTillCharacter: function(cm, _head, motionArgs) {
var repeat = motionArgs.repeat;
var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
motionArgs.selectedCharacter);
@@ -2465,26 +2455,26 @@ dom.importCssString(".normal-mode .ace_cursor{\
curEnd.ch += increment;
return curEnd;
},
- moveToCharacter: function(cm, motionArgs) {
+ moveToCharacter: function(cm, head, motionArgs) {
var repeat = motionArgs.repeat;
recordLastCharacterSearch(0, motionArgs);
return moveToCharacter(cm, repeat, motionArgs.forward,
- motionArgs.selectedCharacter) || cm.getCursor();
+ motionArgs.selectedCharacter) || head;
},
- moveToSymbol: function(cm, motionArgs) {
+ moveToSymbol: function(cm, head, motionArgs) {
var repeat = motionArgs.repeat;
return findSymbol(cm, repeat, motionArgs.forward,
- motionArgs.selectedCharacter) || cm.getCursor();
+ motionArgs.selectedCharacter) || head;
},
- moveToColumn: function(cm, motionArgs, vim) {
+ moveToColumn: function(cm, head, motionArgs, vim) {
var repeat = motionArgs.repeat;
// repeat is equivalent to which column we want to move to!
vim.lastHPos = repeat - 1;
- vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
+ vim.lastHSPos = cm.charCoords(head,'div').left;
return moveToColumn(cm, repeat);
},
- moveToEol: function(cm, motionArgs, vim) {
- var cur = cm.getCursor();
+ moveToEol: function(cm, head, motionArgs, vim) {
+ var cur = head;
vim.lastHPos = Infinity;
var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
var end=cm.clipPos(retval);
@@ -2492,15 +2482,15 @@ dom.importCssString(".normal-mode .ace_cursor{\
vim.lastHSPos = cm.charCoords(end,'div').left;
return retval;
},
- moveToFirstNonWhiteSpaceCharacter: function(cm) {
+ moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
// Go to the start of the line where the text begins, or the end for
// whitespace-only lines
- var cursor = cm.getCursor();
+ var cursor = head;
return Pos(cursor.line,
findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
},
- moveToMatchedSymbol: function(cm) {
- var cursor = cm.getCursor();
+ moveToMatchedSymbol: function(cm, head) {
+ var cursor = head;
var line = cursor.line;
var ch = cursor.ch;
var lineText = cm.getLine(line);
@@ -2521,11 +2511,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
return cursor;
}
},
- moveToStartOfLine: function(cm) {
- var cursor = cm.getCursor();
- return Pos(cursor.line, 0);
+ moveToStartOfLine: function(_cm, head) {
+ return Pos(head.line, 0);
},
- moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
+ moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) {
var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
if (motionArgs.repeatIsExplicit) {
lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
@@ -2533,7 +2522,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
return Pos(lineNum,
findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)));
},
- textObjectManipulation: function(cm, motionArgs) {
+ textObjectManipulation: function(cm, head, motionArgs) {
// TODO: lots of possible exceptions that can be thrown here. Try da(
// outside of a () block.
@@ -2562,9 +2551,9 @@ dom.importCssString(".normal-mode .ace_cursor{\
var tmp;
if (mirroredPairs[character]) {
- tmp = selectCompanionObject(cm, character, inclusive);
+ tmp = selectCompanionObject(cm, head, character, inclusive);
} else if (selfPaired[character]) {
- tmp = findBeginningAndEnd(cm, character, inclusive);
+ tmp = findBeginningAndEnd(cm, head, character, inclusive);
} else if (character === 'W') {
tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
true /** bigWord */);
@@ -2586,7 +2575,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
},
- repeatLastCharacterSearch: function(cm, motionArgs) {
+ repeatLastCharacterSearch: function(cm, head, motionArgs) {
var lastSearch = vimGlobalState.lastChararacterSearch;
var repeat = motionArgs.repeat;
var forward = motionArgs.forward === lastSearch.forward;
@@ -2596,141 +2585,107 @@ dom.importCssString(".normal-mode .ace_cursor{\
var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
if (!curEnd) {
cm.moveH(increment, 'char');
- return cm.getCursor();
+ return head;
}
curEnd.ch += increment;
return curEnd;
}
};
+ function fillArray(val, times) {
+ var arr = [];
+ for (var i = 0; i < times; i++) {
+ arr.push(val);
+ }
+ return arr;
+ }
+ /**
+ * An operator acts on a text selection. It receives the list of selections
+ * as input. The corresponding CodeMirror selection is guaranteed to
+ * match the input selection.
+ */
var operators = {
- 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 visualBlock = vim.visualBlock;
- if (vim.lastSelection && !vim.visualMode) {
- visualBlock = vim.lastSelection.visualBlock ? true : visualBlock;
- }
- var lastInsertModeChanges = vimGlobalState.macroModeState.lastInsertModeChanges;
- lastInsertModeChanges.inVisualBlock = visualBlock;
- var replacement = new Array(selections.length).join('1').split('1');
- // save the selectionEnd mark
- var selectionEnd = vim.marks[">"] && vim.marks[">"].find();
- if (!selectionEnd) {
- selectionEnd = cm.getCursor("head");
- }
- vimGlobalState.registerController.pushText(
- operatorArgs.registerName, 'change', text,
- operatorArgs.linewise);
- if (operatorArgs.linewise) {
- // 'C' in visual block extends the block till eol for all lines
- if (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);
+ change: function(cm, args, ranges) {
+ var finalHead, text;
+ var vim = cm.state.vim;
+ vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock;
+ if (!vim.visualMode) {
+ var anchor = ranges[0].anchor,
+ head = ranges[0].head;
+ text = cm.getRange(anchor, head);
if (!isWhiteSpaceString(text)) {
+ // Exclude trailing whitespace if the range is not all whitespace.
var match = (/\s+$/).exec(text);
if (match) {
- curEnd = offsetCursor(curEnd, 0, - match[0].length);
+ head = offsetCursor(head, 0, - match[0].length);
+ text = text.slice(0, - match[0].length);
}
}
- if (visualBlock) {
- cm.replaceSelections(replacement);
- } else {
- cm.setCursor(curStart);
- cm.replaceRange('', curStart, curEnd);
+ var wasLastLine = head.line - 1 == cm.lastLine();
+ cm.replaceRange('', anchor, head);
+ if (args.linewise && !wasLastLine) {
+ // Push the next line back down, if there is a next line.
+ CodeMirror.commands.newlineAndIndent(cm);
+ // null ch so setCursor moves to end of line.
+ anchor.ch = null;
}
+ finalHead = anchor;
+ } else {
+ text = cm.getSelection();
+ var replacement = fillArray('', ranges.length);
+ cm.replaceSelections(replacement);
+ finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
}
- vim.marks['>'] = cm.setBookmark(selectionEnd);
- actions.enterInsertMode(cm, {}, cm.state.vim);
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'change', text,
+ args.linewise, ranges.length > 1);
+ actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim);
},
// delete is a javascript keyword.
- '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, selectionStart;
- var blockwise = vim.visualBlock;
- if (vim.visualMode) {
- selectionEnd = vim.marks['>'].find();
- selectionStart = vim.marks['<'].find();
- } else if (vim.lastSelection) {
- selectionEnd = vim.lastSelection.curStartMark.find();
- selectionStart = vim.lastSelection.curEndMark.find();
- blockwise = vim.lastSelection.visualBlock;
- }
- var text = cm.getSelection();
- vimGlobalState.registerController.pushText(
- operatorArgs.registerName, 'delete', text,
- operatorArgs.linewise, blockwise);
- var replacement = new Array(selections.length).join('1').split('1');
- // If the ending line is past the last line, inclusive, instead of
- // including the trailing \n, include the \n before the starting line
- if (operatorArgs.linewise &&
- curEnd.line == cm.lastLine() && curStart.line == curEnd.line) {
- if (curEnd.line == 0) {
- curStart.ch = 0;
+ 'delete': function(cm, args, ranges) {
+ var finalHead, text;
+ var vim = cm.state.vim;
+ if (!vim.visualBlock) {
+ var anchor = ranges[0].anchor,
+ head = ranges[0].head;
+ if (args.linewise &&
+ head.line != cm.firstLine() &&
+ anchor.line == cm.lastLine() &&
+ anchor.line == head.line - 1) {
+ // Special case for dd on last line (and first line).
+ if (anchor.line == cm.firstLine()) {
+ anchor.ch = 0;
+ } else {
+ anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1));
+ }
}
- else {
- var tmp = copyCursor(curEnd);
- curStart.line--;
- curStart.ch = lineLength(cm, curStart.line);
- curEnd = tmp;
+ text = cm.getRange(anchor, head);
+ cm.replaceRange('', anchor, head);
+ finalHead = anchor;
+ if (args.linewise) {
+ finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor);
}
- cm.replaceRange('', curStart, curEnd);
} else {
+ text = cm.getSelection();
+ var replacement = fillArray('', ranges.length);
cm.replaceSelections(replacement);
+ finalHead = ranges[0].anchor;
}
- // restore the saved bookmark
- if (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));
- } else {
- cm.setCursor(curStart);
- }
+ vimGlobalState.registerController.pushText(
+ args.registerName, 'delete', text,
+ args.linewise, vim.visualBlock);
+ return finalHead;
},
- indent: function(cm, operatorArgs, vim, curStart, curEnd) {
- var startLine = curStart.line;
- var endLine = curEnd.line;
+ indent: function(cm, args, ranges) {
+ var vim = cm.state.vim;
+ var startLine = ranges[0].anchor.line;
+ var endLine = vim.visualBlock ?
+ ranges[ranges.length - 1].anchor.line :
+ ranges[0].head.line;
// In visual mode, n> shifts the selection right n times, instead of
// shifting n lines right once.
- var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
- if (operatorArgs.linewise) {
+ var repeat = (vim.visualMode) ? args.repeat : 1;
+ if (args.linewise) {
// The only way to delete a newline is to delete until the start of
// the next line, so in linewise mode evalInput will include the next
// line. We don't want this in indent, so we go back a line.
@@ -2738,17 +2693,15 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
for (var i = startLine; i <= endLine; i++) {
for (var j = 0; j < repeat; j++) {
- cm.indentLine(i, operatorArgs.indentRight);
+ cm.indentLine(i, args.indentRight);
}
}
- cm.setCursor(curStart);
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
},
- changeCase: function(cm, operatorArgs, _vim, _curStart, _curEnd, _curOriginal) {
+ changeCase: function(cm, args, ranges, oldAnchor, newHead) {
var selections = cm.getSelections();
- var ranges = cm.listSelections();
var swapped = [];
- var toLower = operatorArgs.toLower;
+ var toLower = args.toLower;
for (var j = 0; j < selections.length; j++) {
var toSwap = selections[j];
var text = '';
@@ -2766,18 +2719,26 @@ dom.importCssString(".normal-mode .ace_cursor{\
swapped.push(text);
}
cm.replaceSelections(swapped);
- var curStart = ranges[0].anchor;
- var curEnd = ranges[0].head;
- if (!operatorArgs.shouldMoveCursor) {
- cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd);
+ if (args.shouldMoveCursor){
+ return newHead;
+ } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) {
+ return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor);
+ } else if (args.linewise){
+ return oldAnchor;
+ } else {
+ return cursorMin(ranges[0].anchor, ranges[0].head);
}
},
- yank: function(cm, operatorArgs, vim, _curStart, _curEnd, curOriginal) {
+ yank: function(cm, args, ranges, oldAnchor) {
+ var vim = cm.state.vim;
var text = cm.getSelection();
+ var endPos = vim.visualMode
+ ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor)
+ : oldAnchor;
vimGlobalState.registerController.pushText(
- operatorArgs.registerName, 'yank',
- text, operatorArgs.linewise, vim.visualBlock);
- cm.setCursor(curOriginal);
+ args.registerName, 'yank',
+ text, args.linewise, vim.visualBlock);
+ return endPos;
}
};
@@ -2869,41 +2830,40 @@ dom.importCssString(".normal-mode .ace_cursor{\
vim.insertMode = true;
vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
var insertAt = (actionArgs) ? actionArgs.insertAt : null;
- if (vim.visualMode) {
- var selections = getSelectedAreaRange(cm, vim);
- var selectionStart = selections[0];
- var selectionEnd = selections[1];
- }
+ var sel = vim.sel;
+ var head = actionArgs.head || cm.getCursor('head');
+ var height = cm.listSelections().length;
if (insertAt == 'eol') {
- var cursor = cm.getCursor();
- cursor = Pos(cursor.line, lineLength(cm, cursor.line));
- cm.setCursor(cursor);
+ head = Pos(head.line, lineLength(cm, head.line));
} else if (insertAt == 'charAfter') {
- cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
+ head = offsetCursor(head, 0, 1);
} else if (insertAt == 'firstNonBlank') {
- if (vim.visualMode && !vim.visualBlock) {
- if (selectionEnd.line < selectionStart.line) {
- cm.setCursor(selectionEnd);
+ head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
+ } else if (insertAt == 'startOfSelectedArea') {
+ if (!vim.visualBlock) {
+ if (sel.head.line < sel.anchor.line) {
+ head = sel.head;
} else {
- selectionStart = Pos(selectionStart.line, 0);
- cm.setCursor(selectionStart);
+ head = Pos(sel.anchor.line, 0);
}
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
- } else if (vim.visualBlock) {
- selectionEnd = Pos(selectionEnd.line, selectionStart.ch);
- cm.setCursor(selectionStart);
- selectBlock(cm, selectionEnd);
} else {
- cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
+ head = Pos(
+ Math.min(sel.head.line, sel.anchor.line),
+ Math.min(sel.head.ch, sel.anchor.ch));
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
}
} else if (insertAt == 'endOfSelectedArea') {
- if (vim.visualBlock) {
- selectionStart = Pos(selectionStart.line, selectionEnd.ch);
- cm.setCursor(selectionStart);
- selectBlock(cm, selectionEnd);
- } else if (selectionEnd && selectionEnd.line < selectionStart.line) {
- selectionEnd = Pos(selectionStart.line, 0);
- cm.setCursor(selectionEnd);
+ if (!vim.visualBlock) {
+ if (sel.head.line >= sel.anchor.line) {
+ head = offsetCursor(sel.head, 0, 1);
+ } else {
+ head = Pos(sel.anchor.line, 0);
+ }
+ } else {
+ head = Pos(
+ Math.min(sel.head.line, sel.anchor.line),
+ Math.max(sel.head.ch + 1, sel.anchor.ch));
+ height = Math.abs(sel.head.line - sel.anchor.line) + 1;
}
} else if (insertAt == 'inplace') {
if (vim.visualMode){
@@ -2929,128 +2889,68 @@ dom.importCssString(".normal-mode .ace_cursor{\
if (vim.visualMode) {
exitVisualMode(cm);
}
+ selectForInsert(cm, head, height);
},
toggleVisualMode: function(cm, actionArgs, vim) {
var repeat = actionArgs.repeat;
- var curStart = cm.getCursor();
- var curEnd;
- var selections = cm.listSelections();
+ var anchor = cm.getCursor();
+ var head;
// TODO: The repeat should actually select number of characters/lines
// equal to the repeat times the size of the previous visual
// operation.
if (!vim.visualMode) {
+ // Entering visual mode
vim.visualMode = true;
vim.visualLine = !!actionArgs.linewise;
vim.visualBlock = !!actionArgs.blockwise;
- if (vim.visualLine) {
- curStart.ch = 0;
- curEnd = clipCursorToContent(
- cm, Pos(curStart.line + repeat - 1, lineLength(cm, curStart.line)),
+ head = clipCursorToContent(
+ cm, Pos(anchor.line, anchor.ch + repeat - 1),
true /** includeLineBreak */);
- } else {
- curEnd = clipCursorToContent(
- cm, Pos(curStart.line, curStart.ch + repeat),
- true /** includeLineBreak */);
- }
- cm.setSelection(curStart, curEnd);
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
+ } else if (vim.visualLine ^ actionArgs.linewise ||
+ vim.visualBlock ^ actionArgs.blockwise) {
+ // Toggling between modes
+ vim.visualLine = !!actionArgs.linewise;
+ vim.visualBlock = !!actionArgs.blockwise;
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
+ updateCmSelection(cm);
} else {
- curStart = cm.getCursor('anchor');
- curEnd = cm.getCursor('head');
- if (vim.visualLine) {
- 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
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual'});
- } else {
- exitVisualMode(cm);
- }
- vim.visualLine = false;
- } else if (vim.visualBlock) {
- if (actionArgs.linewise) {
- // Shift-V pressed in blockwise visual mode
- vim.visualLine = true;
- 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
- 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);
- }
- vim.visualBlock = false;
- } 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) ?
- lineLength(cm, curEnd.line) : 0;
- cm.setSelection(curStart, curEnd);
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
- } else if (actionArgs.blockwise) {
- vim.visualBlock = true;
- selectBlock(cm, curEnd);
- CodeMirror.signal(cm, 'vim-mode-change', {mode: 'visual', subMode: 'blockwise'});
- } else {
- exitVisualMode(cm);
- }
+ exitVisualMode(cm);
}
- updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
- : curEnd);
- updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
- : curStart);
},
reselectLastSelection: function(cm, _actionArgs, vim) {
- var curStart = vim.marks['<'].find();
- var curEnd = vim.marks['>'].find();
var lastSelection = vim.lastSelection;
+ if (vim.visualMode) {
+ updateLastSelection(cm, vim);
+ }
if (lastSelection) {
- // 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');
+ var anchor = lastSelection.anchorMark.find();
+ var head = lastSelection.headMark.find();
+ if (!anchor || !head) {
+ // If the marks have been destroyed due to edits, do nothing.
+ return;
}
- if (vim.visualMode) {
- updateMark(cm, vim, '<', cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
- : selectionEnd);
- updateMark(cm, vim, '>', cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
- : selectionStart);
- }
- // Last selection is updated now
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
vim.visualMode = true;
- if (lastSelection.visualLine) {
- vim.visualLine = 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" : ""});
+ vim.visualLine = lastSelection.visualLine;
+ vim.visualBlock = lastSelection.visualBlock;
+ updateCmSelection(cm);
+ updateMark(cm, vim, '<', cursorMin(anchor, head));
+ updateMark(cm, vim, '>', cursorMax(anchor, head));
+ CodeMirror.signal(cm, 'vim-mode-change', {
+ mode: 'visual',
+ subMode: vim.visualLine ? 'linewise' :
+ vim.visualBlock ? 'blockwise' : ''});
}
},
joinLines: function(cm, actionArgs, vim) {
@@ -3067,18 +2967,19 @@ dom.importCssString(".normal-mode .ace_cursor{\
Infinity));
}
var finalCh = 0;
- cm.operation(function() {
- for (var i = curStart.line; i < curEnd.line; i++) {
- finalCh = lineLength(cm, curStart.line);
- var tmp = Pos(curStart.line + 1,
- lineLength(cm, curStart.line + 1));
- var text = cm.getRange(curStart, tmp);
- text = text.replace(/\n\s*/g, ' ');
- cm.replaceRange(text, curStart, tmp);
- }
- var curFinalPos = Pos(curStart.line, finalCh);
- cm.setCursor(curFinalPos);
- });
+ for (var i = curStart.line; i < curEnd.line; i++) {
+ finalCh = lineLength(cm, curStart.line);
+ var tmp = Pos(curStart.line + 1,
+ lineLength(cm, curStart.line + 1));
+ var text = cm.getRange(curStart, tmp);
+ text = text.replace(/\n\s*/g, ' ');
+ cm.replaceRange(text, curStart, tmp);
+ }
+ var curFinalPos = Pos(curStart.line, finalCh);
+ cm.setCursor(curFinalPos);
+ if (vim.visualMode) {
+ exitVisualMode(cm);
+ }
},
newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
vim.insertMode = true;
@@ -3174,7 +3075,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
var emptyStrings = new Array(selections.length).join('1').split('1');
// save the curEnd marker before it get cleared due to cm.replaceRange.
if (vim.lastSelection) {
- lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
+ lastSelectionCurEnd = vim.lastSelection.headMark.find();
}
// push the previously selected text to unnamed register
vimGlobalState.registerController.unnamedRegister.setText(selectedText);
@@ -3198,7 +3099,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
// restore the the curEnd marker
if(lastSelectionCurEnd) {
- vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
+ vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd);
}
if (linewise) {
curPosFinal.ch=0;
@@ -3240,10 +3141,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
}
}
- cm.setCursor(curPosFinal);
if (vim.visualMode) {
exitVisualMode(cm);
}
+ cm.setCursor(curPosFinal);
},
undo: function(cm, actionArgs) {
cm.operation(function() {
@@ -3372,8 +3273,18 @@ dom.importCssString(".normal-mode .ace_cursor{\
return ret;
}
function offsetCursor(cur, offsetLine, offsetCh) {
+ if (typeof offsetLine === 'object') {
+ offsetCh = offsetLine.ch;
+ offsetLine = offsetLine.line;
+ }
return Pos(cur.line + offsetLine, cur.ch + offsetCh);
}
+ function getOffset(anchor, head) {
+ return {
+ line: head.line - anchor.line,
+ ch: head.line - anchor.line
+ };
+ }
function commandMatches(keys, keyMap, context, inputState) {
// Partial matches are not applied. They inform the key handler
// that the current key sequence is a subsequence of a valid key
@@ -3446,9 +3357,15 @@ dom.importCssString(".normal-mode .ace_cursor{\
return false;
}
function cursorMin(cur1, cur2) {
+ if (arguments.length > 2) {
+ cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1));
+ }
return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
}
function cursorMax(cur1, cur2) {
+ if (arguments.length > 2) {
+ cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1));
+ }
return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
}
function cursorIsBetween(cur1, cur2, cur3) {
@@ -3516,11 +3433,19 @@ dom.importCssString(".normal-mode .ace_cursor{\
selections.push(range);
}
primIndex = head.line == lastLine ? selections.length - 1 : 0;
- cm.setSelections(selections, primIndex);
+ cm.setSelections(selections);
selectionEnd.ch = headCh;
base.ch = baseCh;
return base;
}
+ function selectForInsert(cm, head, height) {
+ var sel = [];
+ for (var i = 0; i < height; i++) {
+ var lineHead = offsetCursor(head, i, 0);
+ sel.push({anchor: lineHead, head: lineHead});
+ }
+ cm.setSelections(sel, 0);
+ }
// getIndex returns the index of the cursor in the selections.
function getIndex(ranges, cursor, end) {
for (var i = 0; i < ranges.length; i++) {
@@ -3561,8 +3486,8 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
cm.setSelections(selections);
} else {
- var start = lastSelection.curStartMark.find();
- var end = lastSelection.curEndMark.find();
+ var start = lastSelection.anchorMark.find();
+ var end = lastSelection.headMark.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};
@@ -3581,36 +3506,28 @@ dom.importCssString(".normal-mode .ace_cursor{\
return getCurrentSelectedAreaRange();
}
}
- function updateLastSelection(cm, vim, selectionStart, selectionEnd) {
- if (!selectionStart || !selectionEnd) {
- selectionStart = vim.marks['<'].find() || cm.getCursor('anchor');
- selectionEnd = vim.marks['>'].find() || cm.getCursor('head');
- }
+ // Updates the previous selection with the current selection's values. This
+ // should only be called in visual mode.
+ function updateLastSelection(cm, vim) {
+ var anchor = vim.sel.anchor;
+ var head = vim.sel.head;
// To accommodate the effect of lastPastedText in the last selection
if (vim.lastPastedText) {
- selectionEnd = cm.posFromIndex(cm.indexFromPos(selectionStart) + vim.lastPastedText.length);
+ head = cm.posFromIndex(cm.indexFromPos(anchor) + 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, 'head') > -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),
+ vim.lastSelection = {'anchorMark': cm.setBookmark(anchor),
+ 'headMark': cm.setBookmark(head),
+ 'anchor': copyCursor(anchor),
+ 'head': copyCursor(head),
'visualMode': vim.visualMode,
'visualLine': vim.visualLine,
- 'visualBlock': block};
+ 'visualBlock': vim.visualBlock};
}
function expandSelection(cm, start, end) {
- var head = cm.getCursor('head');
- var anchor = cm.getCursor('anchor');
+ var sel = cm.state.vim.sel;
+ var head = sel.head;
+ var anchor = sel.anchor;
var tmp;
if (cursorIsBefore(end, start)) {
tmp = end;
@@ -3623,9 +3540,75 @@ dom.importCssString(".normal-mode .ace_cursor{\
} else {
anchor = cursorMin(start, anchor);
head = cursorMax(head, end);
+ head = offsetCursor(head, 0, -1);
+ if (head.ch == -1 && head.line != cm.firstLine()) {
+ head = Pos(head.line - 1, lineLength(cm, head.line - 1));
+ }
}
return [anchor, head];
}
+ /**
+ * Updates the CodeMirror selection to match the provided vim selection.
+ * If no arguments are given, it uses the current vim selection state.
+ */
+ function updateCmSelection(cm, sel, mode) {
+ var vim = cm.state.vim;
+ sel = sel || vim.sel;
+ var mode = mode ||
+ vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char';
+ var cmSel = makeCmSelection(cm, sel, mode);
+ cm.setSelections(cmSel.ranges, cmSel.primary);
+ updateFakeCursor(cm);
+ }
+ function makeCmSelection(cm, sel, mode, exclusive) {
+ var head = copyCursor(sel.head);
+ var anchor = copyCursor(sel.anchor);
+ if (mode == 'char') {
+ var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
+ var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0;
+ head = offsetCursor(sel.head, 0, headOffset);
+ anchor = offsetCursor(sel.anchor, 0, anchorOffset);
+ return {
+ ranges: [{anchor: anchor, head: head}],
+ primary: 0
+ };
+ } else if (mode == 'line') {
+ if (!cursorIsBefore(sel.head, sel.anchor)) {
+ anchor.ch = 0;
+
+ var lastLine = cm.lastLine();
+ if (head.line > lastLine) {
+ head.line = lastLine;
+ }
+ head.ch = lineLength(cm, head.line);
+ } else {
+ head.ch = 0;
+ anchor.ch = lineLength(cm, anchor.line);
+ }
+ return {
+ ranges: [{anchor: anchor, head: head}],
+ primary: 0
+ };
+ } else if (mode == 'block') {
+ var top = Math.min(anchor.line, head.line),
+ left = Math.min(anchor.ch, head.ch),
+ bottom = Math.max(anchor.line, head.line),
+ right = Math.max(anchor.ch, head.ch) + 1;
+ var height = bottom - top + 1;
+ var primary = head.line == top ? 0 : height - 1;
+ var ranges = [];
+ for (var i = 0; i < height; i++) {
+ ranges.push({
+ anchor: Pos(top + i, left),
+ head: Pos(top + i, right)
+ });
+ }
+ return {
+ ranges: ranges,
+ primary: primary
+ };
+ }
+ }
function getHead(cm) {
var cur = cm.getCursor('head');
if (cm.getSelection().length == 1) {
@@ -3636,22 +3619,20 @@ dom.importCssString(".normal-mode .ace_cursor{\
return cur;
}
- function exitVisualMode(cm) {
+ /**
+ * If moveHead is set to false, the CodeMirror selection will not be
+ * touched. The caller assumes the responsibility of putting the cursor
+ * in the right place.
+ */
+ function exitVisualMode(cm, moveHead) {
var vim = cm.state.vim;
- var selectionStart = cm.getCursor('anchor');
- var selectionEnd = cm.getCursor('head');
- // hack to place the cursor at the right place
- // in case of visual block
- if (vim.visualBlock && (cursorIsBefore(selectionStart, selectionEnd))) {
- selectionEnd.ch--;
+ if (moveHead !== false) {
+ cm.setCursor(clipCursorToContent(cm, vim.sel.head));
}
updateLastSelection(cm, vim);
vim.visualMode = false;
vim.visualLine = false;
vim.visualBlock = false;
- if (!cursorEqual(selectionStart, selectionEnd)) {
- cm.setCursor(clipCursorToContent(cm, selectionEnd));
- }
CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
if (vim.fakeCursor) {
vim.fakeCursor.clear();
@@ -3981,6 +3962,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
/**
* @param {CodeMirror} cm CodeMirror object.
+ * @param {Pos} cur The position to start from.
* @param {int} repeat Number of words to move past.
* @param {boolean} forward True to search forward. False to search
* backward.
@@ -3990,8 +3972,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
* False if only alphabet characters count as part of the word.
* @return {Cursor} The position the cursor should move to.
*/
- function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
- var cur = cm.getCursor();
+ function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) {
var curStart = copyCursor(cur);
var words = [];
if (forward && !wordEnd || !forward && wordEnd) {
@@ -4091,8 +4072,8 @@ dom.importCssString(".normal-mode .ace_cursor{\
// TODO: perhaps this finagling of start and end positions belonds
// in codmirror/replaceRange?
- function selectCompanionObject(cm, symb, inclusive) {
- var cur = getHead(cm), start, end;
+ function selectCompanionObject(cm, head, symb, inclusive) {
+ var cur = head, start, end;
var bracketRegexp = ({
'(': /[()]/, ')': /[()]/,
@@ -4136,8 +4117,8 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Takes in a symbol and a cursor and tries to simulate text objects that
// have identical opening and closing symbols
// TODO support across multiple lines
- function findBeginningAndEnd(cm, symb, inclusive) {
- var cur = copyCursor(getHead(cm));
+ function findBeginningAndEnd(cm, head, symb, inclusive) {
+ var cur = copyCursor(head);
var line = cm.getLine(cur.line);
var chars = line.split('');
var start, end, i, len;
@@ -4560,13 +4541,6 @@ dom.importCssString(".normal-mode .ace_cursor{\
top: renderer.getFirstFullyVisibleRow(),
bottom: renderer.getLastFullyVisibleRow()
}
- var scrollInfo = cm.getScrollInfo();
- var occludeToleranceTop = 6;
- var occludeToleranceBottom = 10;
- var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
- var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
- var to = cm.coordsChar({left:0, top: bottomY}, 'local');
- return {top: from.line, bottom: to.line};
}
// Ex command handling
@@ -5483,45 +5457,49 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Cursor moved outside the context of an edit. Reset the change.
lastChange.changes = [];
}
- } else {
+ } else if (!cm.curOp.isVimOp) {
handleExternalSelection(cm, vim);
}
if (vim.visualMode) {
- var from, head;
- from = head = cm.getCursor('head');
- var anchor = cm.getCursor('anchor');
- var to = Pos(head.line, from.ch + (cursorIsBefore(anchor, head) ? -1 : 1));
- if (cursorIsBefore(to, from)) {
- var temp = from;
- from = to;
- to = temp;
- }
- if (vim.fakeCursor) {
- vim.fakeCursor.clear();
- }
- vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
+ updateFakeCursor(cm);
}
}
-
+ function updateFakeCursor(cm) {
+ var vim = cm.state.vim;
+ var from = copyCursor(vim.sel.head);
+ var to = offsetCursor(from, 0, 1);
+ if (vim.fakeCursor) {
+ vim.fakeCursor.clear();
+ }
+ vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'});
+ }
function handleExternalSelection(cm, vim) {
var anchor = cm.getCursor('anchor');
var head = cm.getCursor('head');
// Enter or exit visual mode to match mouse selection.
if (vim.visualMode && cursorEqual(head, anchor) && lineLength(cm, head.line) > head.ch) {
- exitVisualMode(cm);
- } else if (!cm.curOp.isVimOp && !vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
+ exitVisualMode(cm, false);
+ } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
vim.visualMode = true;
vim.visualLine = false;
CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
}
- if (!cm.curOp.isVimOp) {
- if (vim.visualMode) {
- updateMark(cm, vim, '<', cursorMin(head, anchor));
- updateMark(cm, vim, '>', cursorMax(head, anchor));
- } else if (!vim.insertMode) {
- // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
- vim.lastHPos = cm.getCursor().ch;
- }
+ if (vim.visualMode) {
+ // Bind CodeMirror selection model to vim selection model.
+ // Mouse selections are considered visual characterwise.
+ var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
+ var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
+ head = offsetCursor(head, 0, headOffset);
+ anchor = offsetCursor(anchor, 0, anchorOffset);
+ vim.sel = {
+ anchor: anchor,
+ head: head
+ };
+ updateMark(cm, vim, '<', cursorMin(head, anchor));
+ updateMark(cm, vim, '>', cursorMax(head, anchor));
+ } else if (!vim.insertMode) {
+ // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse.
+ vim.lastHPos = cm.getCursor().ch;
}
}
@@ -5613,19 +5591,21 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
return true;
}
- var curStart = cm.getCursor();
+ var head = cm.getCursor('head');
var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock;
if (inVisualBlock) {
// Set up block selection again for repeating the changes.
var vim = cm.state.vim;
- var block = vim.lastSelection.visualBlock;
- var curEnd = Pos(curStart.line + block.height-1, curStart.ch);
- cm.setCursor(curStart);
- selectBlock(cm, curEnd);
+ var lastSel = vim.lastSelection;
+ var offset = getOffset(lastSel.anchor, lastSel.head);
+ selectForInsert(cm, head, offset.line + 1);
repeat = cm.listSelections().length;
- cm.setCursor(curStart);
+ cm.setCursor(head);
}
for (var i = 0; i < repeat; i++) {
+ if (inVisualBlock) {
+ cm.setCursor(offsetCursor(head, i, 0));
+ }
for (var j = 0; j < changes.length; j++) {
var change = changes[j];
if (change instanceof InsertModeKey) {
@@ -5635,10 +5615,9 @@ dom.importCssString(".normal-mode .ace_cursor{\
cm.replaceRange(change, cur, cur);
}
}
- if (inVisualBlock) {
- curStart.line++;
- cm.setCursor(curStart);
- }
+ }
+ if (inVisualBlock) {
+ cm.setCursor(offsetCursor(head, 0, 1));
}
}
@@ -5683,6 +5662,12 @@ dom.importCssString(".normal-mode .ace_cursor{\
o = cloneVimState(o);
n[key] = o;
});
+ if (state.sel) {
+ n.sel = {
+ head: state.sel.head && copyCursor(state.sel.head),
+ anchor: state.sel.anchor && copyCursor(state.sel.anchor)
+ };
+ }
return n;
}
function multiSelectHandleKey(cm, key, origin) {
@@ -5705,6 +5690,15 @@ dom.importCssString(".normal-mode .ace_cursor{\
cm.ace.forEachSelection(function() {
var sel = cm.ace.selection;
cm.state.vim.lastHPos = sel.$desiredColumn == null ? sel.lead.column : sel.$desiredColumn;
+ var head = cm.getCursor("head");
+ var anchor = cm.getCursor("anchor");
+ var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0;
+ var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0;
+ head = offsetCursor(head, 0, headOffset);
+ anchor = offsetCursor(anchor, 0, anchorOffset);
+ cm.state.vim.sel.head = head;
+ cm.state.vim.sel.anchor = anchor;
+
isHandled = handleKey(cm, key, origin);
sel.$desiredColumn = cm.state.vim.lastHPos == -1 ? null : cm.state.vim.lastHPos;
if (cm.virtualSelectionMode()) {
@@ -5860,7 +5854,9 @@ dom.importCssString(".normal-mode .ace_cursor{\
// on mac, with some keyboard layouts (e.g swedish) ^ starts composition, we don't need it in normal mode
updateMacCompositionHandlers: function(editor, enable) {
var onCompositionUpdateOverride = function(text) {
- if (util.currentMode !== "insert") {
+ var cm = editor.state.cm;
+ var vim = getVim(cm);
+ if (!vim.insertMode) {
var el = this.textInput.getElement();
el.blur();
el.focus();
@@ -5870,7 +5866,9 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
};
var onCompositionStartOverride = function(text) {
- if (util.currentMode === "insert") {
+ var cm = editor.state.cm;
+ var vim = getVim(cm);
+ if (!vim.insertMode) {
this.onCompositionStartOrig(text);
}
};
@@ -5947,7 +5945,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
ace.off("beforeEndOperation", delayedExecAceCommand);
var cmd = ace.state.cm.vimCmd;
if (cmd) {
- ace.execCommand(cmd.name, cmd.args);
+ ace.execCommand(cmd.exec ? cmd : cmd.name, cmd.args);
}
ace.curOp = ace.prevOp;
}
@@ -5956,5 +5954,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
][(actionArgs.all ? 2 : 0) + (actionArgs.open ? 1 : 0)]);
},
+ exports.handler.defaultKeymap = defaultKeymap;
+
Vim.map("Y", "yy");
});
diff --git a/lib/ace/keyboard/vim_test.js b/lib/ace/keyboard/vim_test.js
index 1fd8c320..8c9eeb65 100644
--- a/lib/ace/keyboard/vim_test.js
+++ b/lib/ace/keyboard/vim_test.js
@@ -72,6 +72,9 @@ function test(name, fn) {
exports["test " + name] = fn;
}
+vim.CodeMirror.Vim.unmap("Y");
+
+
// cm.setBookmark({ch: 5, line: 0})
// cm.setBookmark({ch: 4, line: 0})
@@ -985,32 +988,36 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) {
is(register.linewise);
eq('vim-insert', cm.getOption('keyMap'));
});
-testVim('cc_append', function(cm, vim, helpers) {
+testVim('cc_should_not_append_to_document', function(cm, vim, helpers) {
var expectedLineCount = cm.lineCount();
cm.setCursor(cm.lastLine(), 0);
helpers.doKeys('c', 'c');
eq(expectedLineCount, cm.lineCount());
});
+function fillArray(val, times) {
+ var arr = [];
+ for (var i = 0; i < times; i++) {
+ arr.push(val);
+ }
+ return arr;
+}
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();
+ var replacement = fillArray('hello', 3);
cm.replaceSelections(replacement);
eq('1hello\n5hello\nahellofg', cm.getValue());
helpers.doKeys('');
cm.setCursor(2, 3);
helpers.doKeys('', '2', 'k', 'h', 'C');
- replacement = new Array(cm.listSelections().length+1).join('world ').split(' ');
- replacement.pop();
+ replacement = fillArray('world', 3);
cm.replaceSelections(replacement);
eq('1hworld\n5hworld\nahworld', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
testVim('c_visual_block_replay', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('', '2', 'j', 'l', 'c');
- var replacement = new Array(cm.listSelections().length+1).join('fo ').split(' ');
- replacement.pop();
+ var replacement = fillArray('fo', 3);
cm.replaceSelections(replacement);
eq('1fo4\n5fo8\nafodefg', cm.getValue());
helpers.doKeys('');
@@ -1019,6 +1026,17 @@ testVim('c_visual_block_replay', function(cm, vim, helpers) {
eq('foo4\nfoo8\nfoodefg', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
+testVim('d_visual_block', function(cm, vim, helpers) {
+ cm.setCursor(0, 1);
+ helpers.doKeys('', '2', 'j', 'l', 'l', 'l', 'd');
+ eq('1\n5\nafg', cm.getValue());
+}, {value: '1234\n5678\nabcdefg'});
+testVim('D_visual_block', function(cm, vim, helpers) {
+ cm.setCursor(0, 1);
+ helpers.doKeys('', '2', 'j', 'l', 'D');
+ eq('1\n5\na', 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
@@ -1042,7 +1060,7 @@ testVim('g~g~', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('', register.toString());
is(!register.linewise);
- eqPos({line: curStart.line, ch:0}, cm.getCursor());
+ eqPos(curStart, cm.getCursor());
}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' });
testVim('gu_and_gU', function(cm, vim, helpers) {
var curStart = makeCursor(0, 7);
@@ -1350,7 +1368,7 @@ testVim('a_eol', function(cm, vim, helpers) {
helpers.assertCursorAt(0, lines[0].length);
eq('vim-insert', cm.getOption('keyMap'));
});
-testVim('a_endOfSelectedArea', function(cm, vim, helpers) {
+testVim('A_endOfSelectedArea', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('v', 'j', 'l');
helpers.doKeys('A');
@@ -1813,6 +1831,49 @@ testVim('visual', function(cm, vim, helpers) {
helpers.doKeys('d');
eq('15', cm.getValue());
}, { value: '12345' });
+testVim('visual_yank', function(cm, vim, helpers) {
+ helpers.doKeys('v', '3', 'l', 'y');
+ helpers.assertCursorAt(0, 0);
+ helpers.doKeys('p');
+ eq('aa te test for yank', cm.getValue());
+}, { value: 'a test for yank' })
+testVim('visual_w', function(cm, vim, helpers) {
+ helpers.doKeys('v', 'w');
+ eq(cm.getSelection(), 'motion t');
+}, { value: 'motion test'});
+testVim('visual_initial_selection', function(cm, vim, helpers) {
+ cm.setCursor(0, 1);
+ helpers.doKeys('v');
+ cm.getSelection('n');
+}, { value: 'init'});
+testVim('visual_crossover_left', function(cm, vim, helpers) {
+ cm.setCursor(0, 2);
+ helpers.doKeys('v', 'l', 'h', 'h');
+ cm.getSelection('ro');
+}, { value: 'cross'});
+testVim('visual_crossover_left', function(cm, vim, helpers) {
+ cm.setCursor(0, 2);
+ helpers.doKeys('v', 'h', 'l', 'l');
+ cm.getSelection('os');
+}, { value: 'cross'});
+testVim('visual_crossover_up', function(cm, vim, helpers) {
+ cm.setCursor(3, 2);
+ helpers.doKeys('v', 'j', 'k', 'k');
+ eqPos(Pos(2, 2), cm.getCursor('head'));
+ eqPos(Pos(3, 3), cm.getCursor('anchor'));
+ helpers.doKeys('k');
+ eqPos(Pos(1, 2), cm.getCursor('head'));
+ eqPos(Pos(3, 3), cm.getCursor('anchor'));
+}, { value: 'cross\ncross\ncross\ncross\ncross\n'});
+testVim('visual_crossover_down', function(cm, vim, helpers) {
+ cm.setCursor(1, 2);
+ helpers.doKeys('v', 'k', 'j', 'j');
+ eqPos(Pos(2, 3), cm.getCursor('head'));
+ eqPos(Pos(1, 2), cm.getCursor('anchor'));
+ helpers.doKeys('j');
+ eqPos(Pos(3, 3), cm.getCursor('head'));
+ eqPos(Pos(1, 2), cm.getCursor('anchor'));
+}, { value: 'cross\ncross\ncross\ncross\ncross\n'});
testVim('visual_exit', function(cm, vim, helpers) {
helpers.doKeys('', 'l', 'j', 'j', '');
eqPos(cm.getCursor('anchor'), cm.getCursor('head'));
@@ -1822,20 +1883,23 @@ 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) {
+testVim('visual_block_different_line_lengths', 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());
+}, {value: '1234\n5678\nabcdefg'});
+testVim('visual_block_truncate_on_short_line', function(cm, vim, helpers) {
// 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.replaceRange('', cm.getCursor());
cm.setCursor(3, 4);
helpers.doKeys('', 'l', 'k', 'k', 'd');
eq('hello world\n{\ntis\nsa!', cm.getValue());
- cm.replaceRange('12345\n67891\nabcde', {line: 0, ch: 0}, {line: cm.lastLine(), ch: 6});
+}, {value: 'hello world\n{\nthis is\nsparta!'});
+testVim('visual_block_corners', function(cm, vim, helpers) {
cm.setCursor(1, 2);
helpers.doKeys('', '2', 'l', 'k');
// circle around the anchor
@@ -1851,6 +1915,8 @@ testVim('visual_block', function(cm, vim, helpers) {
helpers.doKeys('4', 'l');
selections = cm.getSelections();
eq('891cde', selections.join(''));
+}, {value: '12345\n67891\nabcde'});
+testVim('visual_block_mode_switch', function(cm, vim, helpers) {
// switch between visual modes
cm.setCursor(1, 1);
// blockwise to characterwise visual
@@ -1865,7 +1931,7 @@ testVim('visual_block', function(cm, vim, helpers) {
helpers.doKeys('V');
selections = cm.getSelections();
eq('67891\nabcde', selections.join(''));
-}, {value: '1234\n5678\nabcdefg'});
+}, {value: '12345\n67891\nabcde'});
testVim('visual_block_crossing_short_line', function(cm, vim, helpers) {
// visual block with long and short lines
cm.setCursor(0, 3);
@@ -1945,12 +2011,15 @@ testVim('reselect_visual_block', function(cm, vim, helpers) {
helpers.doKeys('', 'k', 'h', '');
cm.setCursor(2, 1);
helpers.doKeys('v', 'l', 'g', 'v');
- helpers.assertCursorAt(0, 1);
+ eqPos(Pos(1, 2), vim.sel.anchor);
+ eqPos(Pos(0, 1), vim.sel.head);
// 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);
+ eqPos(Pos(2, 1), vim.sel.anchor);
+ eqPos(Pos(2, 2), vim.sel.head);
+ helpers.doKeys('');
// Ensure selection of deleted range
cm.setCursor(1, 1);
helpers.doKeys('v', '', 'j', 'd', 'g', 'v');
@@ -1984,11 +2053,14 @@ testVim('o_visual', function(cm, vim, helpers) {
testVim('o_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('','3','j','l','l', 'o');
- helpers.assertCursorAt(0, 1);
+ eqPos(Pos(3, 3), vim.sel.anchor);
+ eqPos(Pos(0, 1), vim.sel.head);
helpers.doKeys('O');
- helpers.assertCursorAt(0, 4);
+ eqPos(Pos(3, 1), vim.sel.anchor);
+ eqPos(Pos(0, 3), vim.sel.head);
helpers.doKeys('o');
- helpers.assertCursorAt(3, 1);
+ eqPos(Pos(0, 3), vim.sel.anchor);
+ eqPos(Pos(3, 1), vim.sel.head);
}, { value: 'abcd\nefgh\nijkl\nmnop'});
testVim('changeCase_visual', function(cm, vim, helpers) {
cm.setCursor(0, 0);
@@ -2023,7 +2095,9 @@ testVim('changeCase_visual_block', function(cm, vim, helpers) {
}, { 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');
+ helpers.doKeys('v', 'l', 'l', 'y');
+ helpers.assertCursorAt(0, 0);
+ helpers.doKeys('3', 'l', 'j', 'v', 'l', 'p');
helpers.assertCursorAt(1, 5);
eq('this is a\nunithitest for visual paste', cm.getValue());
cm.setCursor(0, 0);