Skip to content

Commit

Permalink
automatic langmap for non ascii + fix composition replace issue (#152)
Browse files Browse the repository at this point in the history
* automatic langmap for non ascii + fix composition replace issue

* move out cm5 key handling logic
  • Loading branch information
nightwing authored Jan 15, 2024
1 parent ff439cb commit df2ec64
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 264 deletions.
128 changes: 125 additions & 3 deletions dev/cm5/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,128 @@
import { initVim as initVimInternal } from "../../src/vim.js";

export function initVim(CodeMirror5) {
CodeMirror5.Vim = initVimInternal(CodeMirror5);
return CodeMirror5.Vim;
export function initVim(CodeMirror) {
var Vim = CodeMirror.Vim = initVimInternal(CodeMirror);
var Pos = CodeMirror.Pos;

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 Pos(range.head.line, range.head.ch - 1);
}

return range.head;
}

CodeMirror.keyMap['vim-insert'] = {
// TODO: override navigation keys so that Esc will cancel automatic
// indentation from o, O, i_<CR>
fallthrough: ['default'],
attach: attachVimMap,
detach: detachVimMap,
call: cmKey
};

CodeMirror.keyMap['vim-replace'] = {
'Backspace': 'goCharLeft',
fallthrough: ['vim-insert'],
attach: attachVimMap,
detach: detachVimMap
};


CodeMirror.keyMap.vim = {
attach: attachVimMap,
detach: detachVimMap,
call: cmKey
};

// Deprecated, simply setting the keymap works again.
CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
if (val && cm.getOption("keyMap") != "vim")
cm.setOption("keyMap", "vim");
else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
cm.setOption("keyMap", "default");
});

function cmKey(key, cm) {
if (!cm) { return undefined; }
if (this[key]) { return this[key]; }
var vimKey = cmKeyToVimKey(key);
if (!vimKey) {
return false;
}
var cmd = Vim.findKey(cm, vimKey);
if (typeof cmd == 'function') {
CodeMirror.signal(cm, 'vim-keypress', vimKey);
}
return cmd;
}

var modifiers = {Shift:'S',Ctrl:'C',Alt:'A',Cmd:'D',Mod:'A',CapsLock:''};
var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del',Insert:'Ins'};

function cmKeyToVimKey(key) {
if (key.charAt(0) == '\'') {
// Keypress character binding of format "'a'"
return key.charAt(1);
}
var pieces = key.split(/-(?!$)/);
var lastPiece = pieces[pieces.length - 1];
if (pieces.length == 1 && pieces[0].length == 1) {
// No-modifier bindings use literal character bindings above. Skip.
return false;
} else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) {
// Ignore Shift+char bindings as they should be handled by literal character.
return false;
}
var hasCharacter = false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece in modifiers) { pieces[i] = modifiers[piece]; }
else { hasCharacter = true; }
if (piece in specialKeys) { pieces[i] = specialKeys[piece]; }
}
if (!hasCharacter) {
// Vim does not support modifier only keys.
return false;
}
// TODO: Current bindings expect the character to be lower case, but
// it looks like vim key notation uses upper case.
if (/^[A-Z]$/.test(lastPiece))
pieces[pieces.length - 1] = lastPiece.toLowerCase();

return '<' + pieces.join('-') + '>';
}

function detachVimMap(cm, next) {
if (this == CodeMirror.keyMap.vim) {
cm.options.$customCursor = null;
CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
}

if (!next || next.attach != attachVimMap)
Vim.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 (!prev || prev.attach != attachVimMap)
Vim.enterVimMode(cm);
}

return CodeMirror.Vim;
}
48 changes: 0 additions & 48 deletions src/cm_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,6 @@ function signalTo(handlers: any, ...args: any[]) {
for (var i = 0; i < handlers.length; ++i) { handlers[i](...args); }
}

var specialKey: any = {
Return: 'CR', Backspace: 'BS', 'Delete': 'Del', Escape: 'Esc', Insert: 'Ins',
ArrowLeft: 'Left', ArrowRight: 'Right', ArrowUp: 'Up', ArrowDown: 'Down',
Enter: 'CR', ' ': 'Space'
};
var ignoredKeys: any = { Shift: 1, Alt: 1, Command: 1, Control: 1,
CapsLock: 1, AltGraph: 1, Dead: 1, Unidentified: 1 };


let wordChar: RegExp
try {
wordChar = new RegExp("[\\w\\p{Alphabetic}\\p{Number}_]", "u")
Expand Down Expand Up @@ -170,45 +161,6 @@ export class CodeMirror {
e?.stopPropagation?.()
e?.preventDefault?.()
};
static keyName = function (e: KeyboardEvent) {
var key = e.key;
if (ignoredKeys[key]) return;
if (key == "Escape") key = "Esc";
if (key == " ") key = "Space";
if (key.length > 1) {
key = key.replace(/Numpad|Arrow/, "");
}
if (key.length == 1) key = key.toUpperCase();
var name = '';
if (e.ctrlKey) { name += 'Ctrl-'; }
if (e.altKey) { name += 'Alt-'; }
if ((name || key.length > 1) && e.shiftKey) { name += 'Shift-'; }
name += key;
return name;
};
static vimKey = function vimKey(e: KeyboardEvent) {
var key = e.key;
if (ignoredKeys[key]) return;
if (key.length > 1 && key[0] == "n") {
key = key.replace("Numpad", "");
}
key = specialKey[key] || key;
var name = '';
if (e.ctrlKey) { name += 'C-'; }
if (e.altKey) { name += 'A-'; }
if (e.metaKey) { name += 'M-'; }
// on mac many characters are entered as option- combos
// (e.g. on swiss keyboard { is option-8)
// so we ignore lonely A- modifier for keypress event on mac
if (CodeMirror.isMac && e.altKey && !e.metaKey && !e.ctrlKey) {
name = name.slice(2);
}
if ((name || key.length > 1) && e.shiftKey) { name += 'S-'; }

name += key;
if (name.length > 1) { name = '<' + name + '>'; }
return name;
};

static lookupKey = function lookupKey(key: string, map: string, handle: Function) {
var result = CodeMirror.keys[key];
Expand Down
21 changes: 17 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,12 @@ const vimPlugin = ViewPlugin.fromClass(
decorations = Decoration.none;
waitForCopy = false;
handleKey(e: KeyboardEvent, view: EditorView) {
const rawKey = CodeMirror.vimKey(e);
if (!rawKey) return;

const cm = this.cm;
let vim = cm.state.vim;
if (!vim) return;

const key = vim.expectLiteralNext ? rawKey : Vim.langmapRemapKey(rawKey);
const key = Vim.vimKeyFromEvent(e, vim);
if (!key) return;

// clear search highlight
if (
Expand Down Expand Up @@ -253,6 +251,7 @@ const vimPlugin = ViewPlugin.fromClass(
}
lastKeydown = ''
useNextTextInput = false
compositionText = ''
},
{
eventHandlers: {
Expand Down Expand Up @@ -307,6 +306,20 @@ const vimPlugin = ViewPlugin.fromClass(
return true;
}
if (text.length == 1 && vimPlugin.useNextTextInput) {
if (vim.expectLiteralNext && view.composing) {
vimPlugin.compositionText = text;
return false
}
if (vimPlugin.compositionText) {
var toRemove = vimPlugin.compositionText;
vimPlugin.compositionText = '';
var head = view.state.selection.main.head
var textInDoc = view.state.sliceDoc(head - toRemove.length, head);
if (toRemove === textInDoc) {
var pos = cm.getCursor();
cm.replaceRange('', cm.posFromIndex(head - toRemove.length), pos);
}
}
vimPlugin.handleKey({
key: text,
preventDefault: ()=>{},
Expand Down
Loading

0 comments on commit df2ec64

Please sign in to comment.