mirror of
https://github.com/esm7/obsidian-vimrc-support.git
synced 2026-07-22 05:00:25 +00:00
Implement and provide default mappings for some Obsidian-specific Vim motions/commands (#222)
* feat: define and expose obsidian-specific vim commands jumpToNextHeading: g] jumpToPreviousHeading: g[ * Implement jumpToPreviousLink motion * Refactoring and implementing jumpToNextLink * refactor: new jumpToPattern function that can be used for motions * refactor: renamed file and removed unneeded exports * fix: return last found index even if fewer than n instances found, instead of undefined * feat: implement moveUpSkipFold and moveDownSkipFold * refactor: extract out helper functions for defining obsidian vim actions * refactor: split vimApi.ts into two files * refactor: add comment * refactor: update names, types, etc * feat: followLinkUnderCursor action * feat: jumpToLink now jumps to both markdown and wiki links * refactor: rename fns * refactor: add docstrings / change var names * feat: implement looping around * refactor: cleaner implementation of jumpToPattern * Change mappings for next/prev heading to [[ and ]] * Tiny fixes * docs: update docs now that some more motions are provided by default --------- Co-authored-by: Erez Shermer <erezshermer@gmail.com>
This commit is contained in:
parent
89cd8c71b8
commit
d2583078ac
12 changed files with 382 additions and 32 deletions
|
|
@ -4,12 +4,11 @@ In this document I will collect some of my and user-contributed ideas for how to
|
|||
|
||||
If you have interesting snippets, please contribute by opening a pull request!
|
||||
|
||||
Note that these examples are included for demonstration purposes, and many of them are now provided by default in this plugin. Their actual implementations can be found under [`motions/`](https://github.com/esm7/obsidian-vimrc-support/blob/master/motions/), which you can also use as reference (either for your own custom motions, or if you wish to submit a PR for a new motion to be provided by this plugin).
|
||||
|
||||
## Jump to Next/Prev Markdown Header
|
||||
## Jump to Next/Previous Markdown Heading
|
||||
|
||||
To map `]]` and `[[` to next/prev markdown header, I use the following.
|
||||
|
||||
In a file I call `mdHelpers.js`, put this:
|
||||
In a file you can call `mdHelpers.js`, put this:
|
||||
|
||||
```js
|
||||
// Taken from https://stackoverflow.com/questions/273789/is-there-a-version-of-javascripts-string-indexof-that-allows-for-regular-expr
|
||||
|
|
|
|||
|
|
@ -75,6 +75,13 @@ Commands that fail don't generate any visible error for now.
|
|||
CodeMirror's Vim mode has some limitations and bugs and not all commands will work like you'd expect.
|
||||
In some cases you can find workarounds by experimenting, and the easiest way to do that is by trying interactively rather than via the Vimrc file.
|
||||
|
||||
Finally, this plugin also provides the following motions/mappings by default:
|
||||
|
||||
- `[[` and `]]` to jump to the previous and next Markdown heading.
|
||||
- `zk` and `zj` to move up and down while skipping folds.
|
||||
- `gl` and `gL` to jump to the next and previous link.
|
||||
- `gf` to open the link or file under the cursor (temporarily moving the cursor if necessary—e.g. if it's on the first square bracket of a [[Wikilink]]).
|
||||
|
||||
## Installation
|
||||
|
||||
In the Obsidian.md settings under "Community plugins", click on "Turn on community plugins", then browse to this plugin.
|
||||
|
|
@ -283,7 +290,7 @@ The `jsfile` should be placed in your vault (alongside, e.g., your markdown file
|
|||
|
||||
As above, the code running as part of `jsfile` has the arguments `editor: Editor`, `view: MarkdownView` and `selection: EditorSelection`.
|
||||
|
||||
Here's an example from my own `.obsidian.vimrc` that maps `]]` and `[[` to jump to the next/previous Markdown header:
|
||||
Here's an example `.obsidian.vimrc` entry that maps `]]` and `[[` to jump to the next/previous Markdown heading. Note that `]]` and `[[` are already provided by default in this plugin, but this is a good example of how to use `jsfile`:
|
||||
|
||||
```
|
||||
exmap nextHeading jsfile mdHelpers.js {jumpHeading(true)}
|
||||
|
|
|
|||
26
actions/followLinkUnderCursor.ts
Normal file
26
actions/followLinkUnderCursor.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { ObsidianActionFn } from "../utils/obsidianVimCommand";
|
||||
|
||||
/**
|
||||
* Follows the link under the cursor, temporarily moving the cursor if necessary for follow-link to
|
||||
* work (i.e. if the cursor is on a starting square bracket).
|
||||
*/
|
||||
export const followLinkUnderCursor: ObsidianActionFn = (vimrcPlugin) => {
|
||||
const obsidianEditor = vimrcPlugin.getActiveObsidianEditor();
|
||||
const { line, ch } = obsidianEditor.getCursor();
|
||||
const firstTwoChars = obsidianEditor.getRange(
|
||||
{ line, ch },
|
||||
{ line, ch: ch + 2 }
|
||||
);
|
||||
let numCharsMoved = 0;
|
||||
for (const char of firstTwoChars) {
|
||||
if (char === "[") {
|
||||
obsidianEditor.exec("goRight");
|
||||
numCharsMoved++;
|
||||
}
|
||||
}
|
||||
vimrcPlugin.executeObsidianCommand("editor:follow-link");
|
||||
// Move the cursor back to where it was
|
||||
for (let i = 0; i < numCharsMoved; i++) {
|
||||
obsidianEditor.exec("goLeft");
|
||||
}
|
||||
};
|
||||
43
actions/moveSkippingFolds.ts
Normal file
43
actions/moveSkippingFolds.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import VimrcPlugin from "../main";
|
||||
import { ObsidianActionFn } from "../utils/obsidianVimCommand";
|
||||
|
||||
/**
|
||||
* Moves the cursor down `repeat` lines, skipping over folded sections.
|
||||
*/
|
||||
export const moveDownSkippingFolds: ObsidianActionFn = (
|
||||
vimrcPlugin,
|
||||
cm,
|
||||
{ repeat }
|
||||
) => {
|
||||
moveSkippingFolds(vimrcPlugin, repeat, "down");
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves the cursor up `repeat` lines, skipping over folded sections.
|
||||
*/
|
||||
export const moveUpSkippingFolds: ObsidianActionFn = (
|
||||
vimrcPlugin,
|
||||
cm,
|
||||
{ repeat }
|
||||
) => {
|
||||
moveSkippingFolds(vimrcPlugin, repeat, "up");
|
||||
};
|
||||
|
||||
function moveSkippingFolds(
|
||||
vimrcPlugin: VimrcPlugin,
|
||||
repeat: number,
|
||||
direction: "up" | "down"
|
||||
) {
|
||||
const obsidianEditor = vimrcPlugin.getActiveObsidianEditor();
|
||||
let { line: oldLine, ch: oldCh } = obsidianEditor.getCursor();
|
||||
const commandName = direction === "up" ? "goUp" : "goDown";
|
||||
for (let i = 0; i < repeat; i++) {
|
||||
obsidianEditor.exec(commandName);
|
||||
const { line: newLine, ch: newCh } = obsidianEditor.getCursor();
|
||||
if (newLine === oldLine && newCh === oldCh) {
|
||||
// Going in the specified direction doesn't do anything anymore, stop now
|
||||
return;
|
||||
}
|
||||
[oldLine, oldCh] = [newLine, newCh];
|
||||
}
|
||||
}
|
||||
70
main.ts
70
main.ts
|
|
@ -1,5 +1,12 @@
|
|||
import * as keyFromAccelerator from 'keyboardevent-from-electron-accelerator';
|
||||
import { EditorSelection, Notice, App, MarkdownView, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
import { App, EditorSelection, MarkdownView, Notice, Editor as ObsidianEditor, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
import { followLinkUnderCursor } from './actions/followLinkUnderCursor';
|
||||
import { moveDownSkippingFolds, moveUpSkippingFolds } from './actions/moveSkippingFolds';
|
||||
import { jumpToNextHeading, jumpToPreviousHeading } from './motions/jumpToHeading';
|
||||
import { jumpToNextLink, jumpToPreviousLink } from './motions/jumpToLink';
|
||||
import { defineAndMapObsidianVimAction, defineAndMapObsidianVimMotion } from './utils/obsidianVimCommand';
|
||||
import { VimApi } from './utils/vimApi';
|
||||
|
||||
declare const CodeMirror: any;
|
||||
|
||||
|
|
@ -249,6 +256,10 @@ export default class VimrcPlugin extends Plugin {
|
|||
return this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
}
|
||||
|
||||
getActiveObsidianEditor(): ObsidianEditor {
|
||||
return this.getActiveView().editor;
|
||||
}
|
||||
|
||||
private getCodeMirror(view: MarkdownView): CodeMirror.Editor {
|
||||
return (view as any).editMode?.editor?.cm?.cm;
|
||||
}
|
||||
|
|
@ -259,6 +270,7 @@ export default class VimrcPlugin extends Plugin {
|
|||
var cmEditor = this.getCodeMirror(view);
|
||||
if (cmEditor && !this.codeMirrorVimObject.loadedVimrc) {
|
||||
this.defineBasicCommands(this.codeMirrorVimObject);
|
||||
this.defineAndMapObsidianVimCommands(this.codeMirrorVimObject);
|
||||
this.defineSendKeys(this.codeMirrorVimObject);
|
||||
this.defineObCommand(this.codeMirrorVimObject);
|
||||
this.defineSurround(this.codeMirrorVimObject);
|
||||
|
|
@ -369,6 +381,17 @@ export default class VimrcPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
defineAndMapObsidianVimCommands(vimObject: VimApi) {
|
||||
defineAndMapObsidianVimMotion(vimObject, jumpToNextHeading, ']]');
|
||||
defineAndMapObsidianVimMotion(vimObject, jumpToPreviousHeading, '[[');
|
||||
defineAndMapObsidianVimMotion(vimObject, jumpToNextLink, 'gl');
|
||||
defineAndMapObsidianVimMotion(vimObject, jumpToPreviousLink, 'gL');
|
||||
|
||||
defineAndMapObsidianVimAction(vimObject, this, moveDownSkippingFolds, 'zj');
|
||||
defineAndMapObsidianVimAction(vimObject, this, moveUpSkippingFolds, 'zk');
|
||||
defineAndMapObsidianVimAction(vimObject, this, followLinkUnderCursor, 'gf');
|
||||
}
|
||||
|
||||
defineSendKeys(vimObject: any) {
|
||||
vimObject.defineEx('sendkeys', '', async (cm: any, params: any) => {
|
||||
if (!params?.args?.length) {
|
||||
|
|
@ -403,33 +426,36 @@ export default class VimrcPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
executeObsidianCommand(commandName: string) {
|
||||
const availableCommands = (this.app as any).commands.commands;
|
||||
if (!(commandName in availableCommands)) {
|
||||
throw new Error(`Command ${commandName} was not found, try 'obcommand' with no params to see in the developer console what's available`);
|
||||
}
|
||||
const view = this.getActiveView();
|
||||
const editor = view.editor;
|
||||
const command = availableCommands[commandName];
|
||||
const {callback, checkCallback, editorCallback, editorCheckCallback} = command;
|
||||
if (editorCheckCallback)
|
||||
editorCheckCallback(false, editor, view);
|
||||
else if (editorCallback)
|
||||
editorCallback(editor, view);
|
||||
else if (checkCallback)
|
||||
checkCallback(false);
|
||||
else if (callback)
|
||||
callback();
|
||||
else
|
||||
throw new Error(`Command ${commandName} doesn't have an Obsidian callback`);
|
||||
}
|
||||
|
||||
defineObCommand(vimObject: any) {
|
||||
vimObject.defineEx('obcommand', '', async (cm: any, params: any) => {
|
||||
const availableCommands = (this.app as any).commands.commands;
|
||||
if (!params?.args?.length || params.args.length != 1) {
|
||||
const availableCommands = (this.app as any).commands.commands;
|
||||
console.log(`Available commands: ${Object.keys(availableCommands).join('\n')}`)
|
||||
throw new Error(`obcommand requires exactly 1 parameter`);
|
||||
}
|
||||
let view = this.getActiveView();
|
||||
let editor = view.editor;
|
||||
const command = params.args[0];
|
||||
if (command in availableCommands) {
|
||||
let callback = availableCommands[command].callback;
|
||||
let checkCallback = availableCommands[command].checkCallback;
|
||||
let editorCallback = availableCommands[command].editorCallback;
|
||||
let editorCheckCallback = availableCommands[command].editorCheckCallback;
|
||||
if (editorCheckCallback)
|
||||
editorCheckCallback(false, editor, view);
|
||||
else if (editorCallback)
|
||||
editorCallback(editor, view);
|
||||
else if (checkCallback)
|
||||
checkCallback(false);
|
||||
else if (callback)
|
||||
callback();
|
||||
else
|
||||
throw new Error(`Command ${command} doesn't have an Obsidian callback`);
|
||||
} else
|
||||
throw new Error(`Command ${command} was not found, try 'obcommand' with no params to see in the developer console what's available`);
|
||||
const commandName = params.args[0];
|
||||
this.executeObsidianCommand(commandName);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
34
motions/jumpToHeading.ts
Normal file
34
motions/jumpToHeading.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { jumpToPattern } from "../utils/jumpToPattern";
|
||||
import { MotionFn } from "../utils/vimApi";
|
||||
|
||||
const HEADING_REGEX = /^#+ /gm;
|
||||
|
||||
/**
|
||||
* Jumps to the repeat-th next heading.
|
||||
*/
|
||||
export const jumpToNextHeading: MotionFn = (cm, cursorPosition, { repeat }) => {
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
cursorPosition,
|
||||
repeat,
|
||||
regex: HEADING_REGEX,
|
||||
direction: "next",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Jumps to the repeat-th previous heading.
|
||||
*/
|
||||
export const jumpToPreviousHeading: MotionFn = (
|
||||
cm,
|
||||
cursorPosition,
|
||||
{ repeat }
|
||||
) => {
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
cursorPosition,
|
||||
repeat,
|
||||
regex: HEADING_REGEX,
|
||||
direction: "previous",
|
||||
});
|
||||
};
|
||||
33
motions/jumpToLink.ts
Normal file
33
motions/jumpToLink.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { jumpToPattern } from "../utils/jumpToPattern";
|
||||
import { MotionFn } from "../utils/vimApi";
|
||||
|
||||
const WIKILINK_REGEX_STRING = "\\[\\[[^\\]\\]]+?\\]\\]";
|
||||
const MARKDOWN_LINK_REGEX_STRING = "\\[[^\\]]+?\\]\\([^)]+?\\)";
|
||||
const LINK_REGEX_STRING = `${WIKILINK_REGEX_STRING}|${MARKDOWN_LINK_REGEX_STRING}`;
|
||||
const LINK_REGEX = new RegExp(LINK_REGEX_STRING, "g");
|
||||
|
||||
/**
|
||||
* Jumps to the repeat-th next link.
|
||||
*/
|
||||
export const jumpToNextLink: MotionFn = (cm, cursorPosition, { repeat }) => {
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
cursorPosition,
|
||||
repeat,
|
||||
regex: LINK_REGEX,
|
||||
direction: "next",
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Jumps to the repeat-th previous link.
|
||||
*/
|
||||
export const jumpToPreviousLink: MotionFn = (cm, cursorPosition, { repeat }) => {
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
cursorPosition,
|
||||
repeat,
|
||||
regex: LINK_REGEX,
|
||||
direction: "previous",
|
||||
});
|
||||
};
|
||||
|
|
@ -15,11 +15,15 @@
|
|||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@types/node": "^14.14.6",
|
||||
"@types/string.prototype.matchall": "^4.0.4",
|
||||
"codemirror": "^5.62.2",
|
||||
"keyboardevent-from-electron-accelerator": "*",
|
||||
"obsidian": "^1.1.1",
|
||||
"rollup": "^2.33.0",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.9.4"
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"string.prototype.matchall": "^4.0.11"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@
|
|||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "es5",
|
||||
"target": "ES2020",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"downlevelIteration": true,
|
||||
"importHelpers": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
"scripthost",
|
||||
"es2015"
|
||||
"ES2020"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
|
|
|||
110
utils/jumpToPattern.ts
Normal file
110
utils/jumpToPattern.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import { Editor as CodeMirrorEditor } from "codemirror";
|
||||
import { EditorPosition } from "obsidian";
|
||||
|
||||
/**
|
||||
* Returns the position of the repeat-th instance of a pattern from a given cursor position, in the
|
||||
* given direction; looping to the other end of the document when reaching one end. Returns the
|
||||
* original cursor position if no match is found.
|
||||
*
|
||||
* Under the hood, to avoid repeated loops of the document: we get all matches at once, order them
|
||||
* according to `direction` and `cursorPosition`, and use modulo arithmetic to return the
|
||||
* appropriate match.
|
||||
*/
|
||||
export function jumpToPattern({
|
||||
cm,
|
||||
cursorPosition,
|
||||
repeat,
|
||||
regex,
|
||||
direction,
|
||||
}: {
|
||||
cm: CodeMirrorEditor;
|
||||
cursorPosition: EditorPosition;
|
||||
repeat: number;
|
||||
regex: RegExp;
|
||||
direction: "next" | "previous";
|
||||
}): EditorPosition {
|
||||
const content = cm.getValue();
|
||||
const cursorIdx = cm.indexFromPos(cursorPosition);
|
||||
const orderedMatches = getOrderedMatches({
|
||||
content,
|
||||
regex,
|
||||
cursorIdx,
|
||||
direction,
|
||||
});
|
||||
const effectiveRepeat = (repeat % orderedMatches.length) || orderedMatches.length;
|
||||
const matchIdx = orderedMatches[effectiveRepeat - 1]?.index;
|
||||
if (matchIdx === undefined) {
|
||||
return cursorPosition;
|
||||
}
|
||||
const newCursorPosition = cm.posFromIndex(matchIdx);
|
||||
return newCursorPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered array of all matches of a regex in a string in the given direction from the
|
||||
* cursor index (looping around to the other end of the document when reaching one end).
|
||||
*/
|
||||
function getOrderedMatches({
|
||||
content,
|
||||
regex,
|
||||
cursorIdx,
|
||||
direction,
|
||||
}: {
|
||||
content: string;
|
||||
regex: RegExp;
|
||||
cursorIdx: number;
|
||||
direction: "next" | "previous";
|
||||
}): RegExpExecArray[] {
|
||||
const { previousMatches, currentMatches, nextMatches } = getAndGroupMatches(
|
||||
content,
|
||||
regex,
|
||||
cursorIdx
|
||||
);
|
||||
if (direction === "next") {
|
||||
return [...nextMatches, ...previousMatches, ...currentMatches];
|
||||
}
|
||||
return [
|
||||
...previousMatches.reverse(),
|
||||
...nextMatches.reverse(),
|
||||
...currentMatches.reverse(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all matches of a regex in a string and groups them by their positions relative to the
|
||||
* cursor.
|
||||
*/
|
||||
function getAndGroupMatches(
|
||||
content: string,
|
||||
regex: RegExp,
|
||||
cursorIdx: number
|
||||
): {
|
||||
previousMatches: RegExpExecArray[];
|
||||
currentMatches: RegExpExecArray[];
|
||||
nextMatches: RegExpExecArray[];
|
||||
} {
|
||||
const globalRegex = makeGlobalRegex(regex);
|
||||
const allMatches = [...content.matchAll(globalRegex)];
|
||||
const previousMatches = allMatches.filter(
|
||||
(match) => match.index < cursorIdx && !isCursorOnMatch(match, cursorIdx)
|
||||
);
|
||||
const currentMatches = allMatches.filter((match) =>
|
||||
isCursorOnMatch(match, cursorIdx)
|
||||
);
|
||||
const nextMatches = allMatches.filter((match) => match.index > cursorIdx);
|
||||
return { previousMatches, currentMatches, nextMatches };
|
||||
}
|
||||
|
||||
function makeGlobalRegex(regex: RegExp): RegExp {
|
||||
const globalFlags = getGlobalFlags(regex);
|
||||
return new RegExp(regex.source, globalFlags);
|
||||
}
|
||||
|
||||
function getGlobalFlags(regex: RegExp): string {
|
||||
const { flags } = regex;
|
||||
return flags.includes("g") ? flags : `${flags}g`;
|
||||
}
|
||||
|
||||
function isCursorOnMatch(match: RegExpExecArray, cursorIdx: number): boolean {
|
||||
return match.index <= cursorIdx && cursorIdx < match.index + match[0].length;
|
||||
}
|
||||
35
utils/obsidianVimCommand.ts
Normal file
35
utils/obsidianVimCommand.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Utility types and functions for defining Obsidian-specific Vim commands.
|
||||
*/
|
||||
|
||||
import { Editor as CodeMirrorEditor } from "codemirror";
|
||||
|
||||
import VimrcPlugin from "../main";
|
||||
import { MotionFn, VimApi } from "./vimApi";
|
||||
|
||||
export type ObsidianActionFn = (
|
||||
vimrcPlugin: VimrcPlugin, // Included so we can run Obsidian commands as part of the action
|
||||
cm: CodeMirrorEditor,
|
||||
actionArgs: { repeat: number },
|
||||
) => void;
|
||||
|
||||
export function defineAndMapObsidianVimMotion(
|
||||
vimObject: VimApi,
|
||||
motionFn: MotionFn,
|
||||
mapping: string
|
||||
) {
|
||||
vimObject.defineMotion(motionFn.name, motionFn);
|
||||
vimObject.mapCommand(mapping, "motion", motionFn.name, undefined, {});
|
||||
}
|
||||
|
||||
export function defineAndMapObsidianVimAction(
|
||||
vimObject: VimApi,
|
||||
vimrcPlugin: VimrcPlugin,
|
||||
obsidianActionFn: ObsidianActionFn,
|
||||
mapping: string
|
||||
) {
|
||||
vimObject.defineAction(obsidianActionFn.name, (cm, actionArgs) => {
|
||||
obsidianActionFn(vimrcPlugin, cm, actionArgs);
|
||||
});
|
||||
vimObject.mapCommand(mapping, "action", obsidianActionFn.name, undefined, {});
|
||||
}
|
||||
33
utils/vimApi.ts
Normal file
33
utils/vimApi.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Partial representation of the CodeMirror Vim API that we use to define motions, commands, etc.
|
||||
*
|
||||
* References:
|
||||
* https://github.com/replit/codemirror-vim/blob/master/src/vim.js
|
||||
* https://libvoyant.ucr.edu/resources/codemirror/doc/manual.html
|
||||
*/
|
||||
|
||||
import { Editor as CodeMirrorEditor } from "codemirror";
|
||||
import { EditorPosition } from "obsidian";
|
||||
|
||||
export type MotionFn = (
|
||||
cm: CodeMirrorEditor,
|
||||
cursorPosition: EditorPosition, // called `head` in the API
|
||||
motionArgs: { repeat: number }
|
||||
) => EditorPosition;
|
||||
|
||||
export type ActionFn = (
|
||||
cm: CodeMirrorEditor,
|
||||
actionArgs: { repeat: number },
|
||||
) => void;
|
||||
|
||||
export type VimApi = {
|
||||
defineMotion: (name: string, fn: MotionFn) => void;
|
||||
defineAction: (name: string, fn: ActionFn) => void;
|
||||
mapCommand: (
|
||||
keys: string,
|
||||
type: string,
|
||||
name: string,
|
||||
args: any,
|
||||
extra: { [x: string]: any }
|
||||
) => void;
|
||||
};
|
||||
Loading…
Reference in a new issue