diff --git a/main.ts b/main.ts index aa55406..c7426e6 100644 --- a/main.ts +++ b/main.ts @@ -1,5 +1,7 @@ import * as keyFromAccelerator from 'keyboardevent-from-electron-accelerator'; -import { EditorSelection, Notice, App, MarkdownView, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian'; +import { App, EditorSelection, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; +import { jumpToNextHeading, jumpToPreviousHeading } from './motions/jumpToHeading'; +import { moveDownSkipFold, moveUpSkipFold } from './motions/moveAndSkipFold'; declare const CodeMirror: any; @@ -257,6 +259,7 @@ export default class VimrcPlugin extends Plugin { var cmEditor = this.getCodeMirror(view); if (cmEditor && !this.codeMirrorVimObject.loadedVimrc) { this.defineBasicCommands(this.codeMirrorVimObject); + this.defineObsidianVimMotions(this.codeMirrorVimObject); this.defineSendKeys(this.codeMirrorVimObject); this.defineObCommand(this.codeMirrorVimObject); this.defineSurround(this.codeMirrorVimObject); @@ -367,6 +370,21 @@ export default class VimrcPlugin extends Plugin { }); } + + defineObsidianVimMotions(vimObject: any) { + vimObject.defineMotion('jumpToNextHeading', jumpToNextHeading); + vimObject.mapCommand('g]', 'motion', 'jumpToNextHeading'); + + vimObject.defineMotion('jumpToPreviousHeading', jumpToPreviousHeading); + vimObject.mapCommand('g[', 'motion', 'jumpToPreviousHeading'); + + vimObject.defineMotion('moveUpSkipFold', moveUpSkipFold); + vimObject.mapCommand('zk', 'motion', 'moveUpSkipFold'); + + vimObject.defineEx('moveDownSkipFold', moveDownSkipFold); + vimObject.mapCommand('zj', 'motion', 'moveDownSkipFold'); + } + defineSendKeys(vimObject: any) { vimObject.defineEx('sendkeys', '', async (cm: any, params: any) => { if (!params?.args?.length) { diff --git a/motions/jumpToHeading.ts b/motions/jumpToHeading.ts new file mode 100644 index 0000000..7a143cb --- /dev/null +++ b/motions/jumpToHeading.ts @@ -0,0 +1,54 @@ +import { Editor, EditorPosition } from "obsidian"; + +export function jumpToNextHeading( + cm: Editor, + { line, ch }: EditorPosition, + motionArgs: { repeat: number } +): EditorPosition { + const { repeat } = motionArgs; + const noteContent = cm.getValue(); + const nextContentLines = noteContent.split("\n").slice(line + 1); + const nextHeadingIdx = + getNthHeadingIndex(nextContentLines, repeat) + line + 1; + if (nextHeadingIdx === -1) { + return { line, ch }; + } + return { line: nextHeadingIdx, ch: 0 }; +} + +export function jumpToPreviousHeading( + cm: Editor, + { line, ch }: EditorPosition, + motionArgs: { repeat: number } +): EditorPosition { + const { repeat } = motionArgs; + const noteContent = cm.getValue(); + const isAlreadyOnHeading = cm.getLine(line).startsWith("#"); + const lastIdxToConsider = isAlreadyOnHeading ? line - 1 : line; + const previousContentLines = noteContent + .split("\n") + .slice(0, lastIdxToConsider + 1) + .reverse(); + const previousHeadingIdx = getNthHeadingIndex(previousContentLines, repeat); + if (previousHeadingIdx === -1) { + return { line, ch }; + } + return { line: lastIdxToConsider - previousHeadingIdx, ch: 0 }; +} + +function getNthHeadingIndex(contentLines: string[], n: number): number { + let numHeadingsFound = 0; + let currHeadingIndex = -1; + for (let i = 0; i < contentLines.length; i++) { + const headingRegex = /^#+ /; + if (!headingRegex.test(contentLines[i])) { + continue; + } + currHeadingIndex = i; + numHeadingsFound++; + if (numHeadingsFound === n) { + return currHeadingIndex; + } + } + return currHeadingIndex; +} diff --git a/motions/moveAndSkipFold.ts b/motions/moveAndSkipFold.ts new file mode 100644 index 0000000..d129b8e --- /dev/null +++ b/motions/moveAndSkipFold.ts @@ -0,0 +1,19 @@ +import { Editor, EditorPosition } from "obsidian"; + +export function moveUpSkipFold( + cm: Editor, + { line, ch }: EditorPosition, + motionArgs: { repeat: number } +): EditorPosition { + // TODO: Implement this + return { line, ch }; +} + +export function moveDownSkipFold( + cm: Editor, + { line, ch }: EditorPosition, + motionArgs: { repeat: number } +): EditorPosition { + // TODO: Implement this + return { line, ch }; +}