Skip to content

Commit

Permalink
[vim] simpler way of drawing cursor
Browse files Browse the repository at this point in the history
  • Loading branch information
nightwing authored and marijnh committed Oct 27, 2021
1 parent e67ca32 commit 27be898
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 105 deletions.
118 changes: 30 additions & 88 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@
})(function(CodeMirror) {
'use strict';

function transformCursor(cm, range) {
var vim = cm.state.vim
if (!vim || vim.insertMode) return range.head;
var head = vim.sel.head;
if (!head) return range.head;

if (vim.visualBlock) {
if (range.head.line != head.line) {
return;
}
}
if (range.from() == range.anchor && !range.empty()) {
if (range.head.line == head.line && range.head.ch != head.ch)
return new CodeMirror.Pos(range.head.line, range.head.ch - 1);
}

return range.head;
}

var defaultKeymap = [
// Key to key mapping. This goes first to make it possible to override
// existing mappings.
Expand Down Expand Up @@ -270,69 +289,24 @@

function detachVimMap(cm, next) {
if (this == CodeMirror.keyMap.vim) {
cm.options.$customCursor = null;
CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
disableFatCursorMark(cm);
cm.getInputField().style.caretColor = "";
}
}

if (!next || next.attach != attachVimMap)
leaveVimMode(cm);
}
function attachVimMap(cm, prev) {
if (this == CodeMirror.keyMap.vim) {
if (cm.curOp) cm.curOp.selectionChanged = true;
cm.options.$customCursor = transformCursor;
CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
enableFatCursorMark(cm);
cm.getInputField().style.caretColor = "transparent";
}
}

if (!prev || prev.attach != attachVimMap)
enterVimMode(cm);
}

function updateFatCursorMark(cm) {
if (!cm.state.fatCursorMarks) return;
clearFatCursorMark(cm);
var ranges = cm.listSelections(), result = []
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (range.empty()) {
var lineLength = cm.getLine(range.anchor.line).length;
if (range.anchor.ch < lineLength) {
result.push(cm.markText(range.anchor, Pos(range.anchor.line, range.anchor.ch + 1),
{className: "cm-fat-cursor-mark"}));
} else {
result.push(cm.markText(Pos(range.anchor.line, lineLength - 1),
Pos(range.anchor.line, lineLength),
{className: "cm-fat-cursor-mark"}));
}
}
}
cm.state.fatCursorMarks = result;
}

function clearFatCursorMark(cm) {
var marks = cm.state.fatCursorMarks;
if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
}

function enableFatCursorMark(cm) {
cm.state.fatCursorMarks = [];
updateFatCursorMark(cm)
cm.on("cursorActivity", updateFatCursorMark)
}

function disableFatCursorMark(cm) {
clearFatCursorMark(cm);
cm.off("cursorActivity", updateFatCursorMark);
// explicitly set fatCursorMarks to null because event listener above
// can be invoke after removing it, if off is called from operation
cm.state.fatCursorMarks = null;
}

// Deprecated, simply setting the keymap works again.
CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
if (val && cm.getOption("keyMap") != "vim")
Expand Down Expand Up @@ -692,8 +666,6 @@
// executed in between.
lastMotion: null,
marks: {},
// Mark for rendering fake cursor for visual mode.
fakeCursor: null,
insertMode: false,
// Repeat count for changes made in insert mode, triggered by key
// sequences like 3,i. Only exists when insertMode is true.
Expand Down Expand Up @@ -2286,7 +2258,7 @@
text = cm.getSelection();
var replacement = fillArray('', ranges.length);
cm.replaceSelections(replacement);
finalHead = ranges[0].anchor;
finalHead = cursorMin(ranges[0].head, ranges[0].anchor);
}
vimGlobalState.registerController.pushText(
args.registerName, 'delete', text,
Expand Down Expand Up @@ -2506,7 +2478,7 @@
} else {
head = Pos(
Math.min(sel.head.line, sel.anchor.line),
Math.max(sel.head.ch + 1, sel.anchor.ch));
Math.max(sel.head.ch, sel.anchor.ch) + 1);
height = Math.abs(sel.head.line - sel.anchor.line) + 1;
}
} else if (insertAt == 'inplace') {
Expand Down Expand Up @@ -3220,7 +3192,6 @@
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);
Expand Down Expand Up @@ -3253,16 +3224,18 @@
};
} else if (mode == 'block') {
var top = Math.min(anchor.line, head.line),
left = Math.min(anchor.ch, head.ch),
fromCh = anchor.ch,
bottom = Math.max(anchor.line, head.line),
right = Math.max(anchor.ch, head.ch) + 1;
toCh = head.ch;
if (fromCh < toCh) { toCh += 1 }
else { fromCh += 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)
anchor: Pos(top + i, fromCh),
head: Pos(top + i, toCh)
});
}
return {
Expand Down Expand Up @@ -3296,7 +3269,6 @@
vim.visualLine = false;
vim.visualBlock = false;
if (!vim.insertMode) CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
clearFakeCursor(vim);
}

// Remove any trailing newlines from the selection. For
Expand Down Expand Up @@ -5581,36 +5553,6 @@
} else if (!cm.curOp.isVimOp) {
handleExternalSelection(cm, vim);
}
if (vim.visualMode) {
updateFakeCursor(cm);
}
}
/**
* Keeps track of a fake cursor to support visual mode cursor behavior.
*/
function updateFakeCursor(cm) {
var className = 'cm-animate-fat-cursor';
var vim = cm.state.vim;
var from = clipCursorToContent(cm, copyCursor(vim.sel.head));
var to = offsetCursor(from, 0, 1);
clearFakeCursor(vim);
// In visual mode, the cursor may be positioned over EOL.
if (from.ch == cm.getLine(from.line).length) {
var widget = dom('span', { 'class': className }, '\u00a0');
vim.fakeCursorBookmark = cm.setBookmark(from, {widget: widget});
} else {
vim.fakeCursor = cm.markText(from, to, {className: className});
}
}
function clearFakeCursor(vim) {
if (vim.fakeCursor) {
vim.fakeCursor.clear();
vim.fakeCursor = null;
}
if (vim.fakeCursorBookmark) {
vim.fakeCursorBookmark.clear();
vim.fakeCursorBookmark = null;
}
}
function handleExternalSelection(cm, vim) {
var anchor = cm.getCursor('anchor');
Expand Down
20 changes: 7 additions & 13 deletions lib/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,13 @@
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
.cm-fat-cursor .CodeMirror-line::selection,
.cm-fat-cursor .CodeMirror-line > span::selection,
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
.cm-fat-cursor .CodeMirror-line::-moz-selection,
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
.cm-fat-cursor { caret-color: transparent; }
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
Expand Down
13 changes: 9 additions & 4 deletions src/display/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ export function prepareSelection(cm, primary = true) {
let curFragment = result.cursors = document.createDocumentFragment()
let selFragment = result.selection = document.createDocumentFragment()

let customCursor = cm.options.$customCursor
if (customCursor) primary = true
for (let i = 0; i < doc.sel.ranges.length; i++) {
if (!primary && i == doc.sel.primIndex) continue
let range = doc.sel.ranges[i]
if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue
let collapsed = range.empty()
if (collapsed || cm.options.showCursorWhenSelecting)
if (customCursor) {
let head = customCursor(cm, range)
if (head) drawSelectionCursor(cm, head, curFragment)
} else if (collapsed || cm.options.showCursorWhenSelecting) {
drawSelectionCursor(cm, range.head, curFragment)
}
if (!collapsed)
drawSelectionRange(cm, range, selFragment)
}
Expand All @@ -39,9 +45,8 @@ export function drawSelectionCursor(cm, head, output) {

if (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) {
let charPos = charCoords(cm, head, "div", null, null)
if (charPos.right - charPos.left > 0) {
cursor.style.width = (charPos.right - charPos.left) + "px"
}
let width = charPos.right - charPos.left
cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "px"
}

if (pos.other) {
Expand Down
17 changes: 17 additions & 0 deletions test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,23 @@ testVim('I_visual_block_replay', function(cm, vim, helpers) {
eq('12+-34\n5+-6+-78\na+-b+-cdefg\nx+-yz', cm.getValue());
}, {value: '1234\n5678\nabcdefg\nxyz'});

testVim('visual_block_backwards', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('3', 'l');
helpers.doKeys('<C-v>', '2', 'j', '2', '<Left>');
eq('123\n678\nbcd', cm.getSelection());
helpers.doKeys('A');
helpers.assertCursorAt(0, 4);
helpers.doKeys('A', '<Esc>');
helpers.assertCursorAt(0, 4);
helpers.doKeys('g', 'v');
eq('123\n678\nbcd', cm.getSelection());
helpers.doKeys('x');
helpers.assertCursorAt(0, 1);
helpers.doKeys('g', 'v');
eq('A4 \nA9 \nAef', cm.getSelection());
}, {value: '01234 line 1\n56789 line 2\nabcdefg line 3\nline 4'});

testVim('d_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'd');
Expand Down

0 comments on commit 27be898

Please sign in to comment.