mirror of
https://github.com/esm7/obsidian-vimrc-support.git
synced 2026-07-22 05:00:25 +00:00
refactor: new jumpToPattern function that can be used for motions
This commit is contained in:
parent
0053c8f1b2
commit
128f1834e9
4 changed files with 75 additions and 85 deletions
|
|
@ -1,17 +1,16 @@
|
|||
import { MotionFn } from "./utils/defineObsidianVimMotion";
|
||||
import { jumpToPattern } from "./utils/getNthInstanceOfPattern";
|
||||
|
||||
const HEADING_REGEX = /^#+ /;
|
||||
const HEADING_REGEX = /^#+ /gm;
|
||||
|
||||
export const jumpToNextHeading: MotionFn = (cm, oldPosition, { repeat }) => {
|
||||
const { line, ch } = oldPosition;
|
||||
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 };
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
oldPosition,
|
||||
repeat,
|
||||
regex: HEADING_REGEX,
|
||||
direction: "next",
|
||||
});
|
||||
};
|
||||
|
||||
export const jumpToPreviousHeading: MotionFn = (
|
||||
|
|
@ -19,34 +18,11 @@ export const jumpToPreviousHeading: MotionFn = (
|
|||
oldPosition,
|
||||
{ repeat }
|
||||
) => {
|
||||
const { line, ch } = oldPosition;
|
||||
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 };
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
oldPosition,
|
||||
repeat,
|
||||
regex: HEADING_REGEX,
|
||||
direction: "previous",
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,24 @@
|
|||
import { MotionFn } from "./utils/defineObsidianVimMotion";
|
||||
import {
|
||||
getNthNextInstanceOfPattern,
|
||||
getNthPreviousInstanceOfPattern,
|
||||
} from "./utils/getNthInstanceOfPattern";
|
||||
import { jumpToPattern } from "./utils/getNthInstanceOfPattern";
|
||||
|
||||
const LINK_REGEX = /\[\[[^\]\]]+?\]\]/g;
|
||||
|
||||
export const jumpToNextLink: MotionFn = (cm, oldPosition, { repeat }) => {
|
||||
const content = cm.getValue();
|
||||
const cursorOffset = cm.indexFromPos(oldPosition);
|
||||
const nextLinkIdx = getNthNextInstanceOfPattern({
|
||||
content,
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
oldPosition,
|
||||
repeat,
|
||||
regex: LINK_REGEX,
|
||||
startingIdx: cursorOffset,
|
||||
n: repeat,
|
||||
direction: "next",
|
||||
});
|
||||
if (nextLinkIdx === undefined) {
|
||||
return oldPosition;
|
||||
}
|
||||
const newPosition = cm.posFromIndex(nextLinkIdx + 2);
|
||||
return newPosition;
|
||||
};
|
||||
|
||||
export const jumpToPreviousLink: MotionFn = (cm, oldPosition, { repeat }) => {
|
||||
const content = cm.getValue();
|
||||
const cursorOffset = cm.indexFromPos(oldPosition);
|
||||
const previousLinkIdx = getNthPreviousInstanceOfPattern({
|
||||
content,
|
||||
return jumpToPattern({
|
||||
cm,
|
||||
oldPosition,
|
||||
repeat,
|
||||
regex: LINK_REGEX,
|
||||
startingIdx: cursorOffset,
|
||||
n: repeat,
|
||||
direction: "previous",
|
||||
});
|
||||
if (previousLinkIdx === undefined) {
|
||||
return oldPosition;
|
||||
}
|
||||
const newPosition = cm.posFromIndex(previousLinkIdx + 2);
|
||||
return newPosition;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,8 +7,12 @@ export type MotionFn = (
|
|||
motionArgs: { repeat: number }
|
||||
) => EditorPosition;
|
||||
|
||||
// Reference: @replit/codemirror-vim/src/vim.js
|
||||
type VimApi = {
|
||||
/**
|
||||
* Partial representation of the CodeMirror Vim API that we use to define motions, commands, etc.
|
||||
*
|
||||
* Reference: https://github.com/replit/codemirror-vim/blob/master/src/vim.js
|
||||
*/
|
||||
export type VimApi = {
|
||||
defineMotion: (name: string, fn: MotionFn) => void;
|
||||
mapCommand: (
|
||||
keys: string,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,38 @@
|
|||
import { Editor as CodeMirrorEditor } from "codemirror";
|
||||
import { EditorPosition } from "obsidian";
|
||||
import { shim as matchAllShim } from "string.prototype.matchall";
|
||||
matchAllShim();
|
||||
|
||||
export function jumpToPattern({
|
||||
cm,
|
||||
oldPosition,
|
||||
repeat,
|
||||
regex,
|
||||
direction,
|
||||
}: {
|
||||
cm: CodeMirrorEditor;
|
||||
oldPosition: EditorPosition;
|
||||
repeat: number;
|
||||
regex: RegExp;
|
||||
direction: "next" | "previous";
|
||||
}): EditorPosition {
|
||||
const content = cm.getValue();
|
||||
const startingIdx = cm.indexFromPos(oldPosition);
|
||||
const jumpFn =
|
||||
direction === "next"
|
||||
? getNthNextInstanceOfPattern
|
||||
: getNthPreviousInstanceOfPattern;
|
||||
const matchIdx = jumpFn({ content, regex, startingIdx, n: repeat });
|
||||
if (matchIdx === undefined) {
|
||||
return oldPosition;
|
||||
}
|
||||
const newPosition = cm.posFromIndex(matchIdx);
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the first instance of a pattern in a string after a given starting index.
|
||||
* If the pattern is not found, returns the starting index.
|
||||
* Returns the index of (up to) the n-th instance of a pattern in a string after a given starting
|
||||
* index. If the pattern is not found at all, returns undefined.
|
||||
*/
|
||||
export function getNthNextInstanceOfPattern({
|
||||
content,
|
||||
|
|
@ -16,24 +45,19 @@ export function getNthNextInstanceOfPattern({
|
|||
startingIdx: number;
|
||||
n: number;
|
||||
}): number {
|
||||
const globalRegex = makeGlobalRegex(regex);
|
||||
globalRegex.lastIndex = startingIdx + 1;
|
||||
let currMatch;
|
||||
let numMatchesFound = 0;
|
||||
let currMatchIdx = startingIdx;
|
||||
const globalRegex = addGlobalFlagIfNeeded(regex);
|
||||
while (currMatchIdx < content.length - 1 && numMatchesFound < n) {
|
||||
const contentToSearch = content.substring(currMatchIdx + 1);
|
||||
const substringMatchIdx = contentToSearch.search(globalRegex);
|
||||
if (substringMatchIdx === -1) {
|
||||
return currMatchIdx;
|
||||
}
|
||||
currMatchIdx = currMatchIdx + substringMatchIdx + 1;
|
||||
while (numMatchesFound < n && (currMatch = globalRegex.exec(content)) != null) {
|
||||
numMatchesFound++;
|
||||
}
|
||||
return currMatchIdx;
|
||||
return currMatch?.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the last found instance of a pattern in a string before a given starting
|
||||
* index. If the pattern is not found, returns undefined.
|
||||
* Returns the index of (up to) the nth-last instance of a pattern in a string before a given
|
||||
* starting index. If the pattern is not found at all, returns undefined.
|
||||
*/
|
||||
export function getNthPreviousInstanceOfPattern({
|
||||
content,
|
||||
|
|
@ -46,7 +70,7 @@ export function getNthPreviousInstanceOfPattern({
|
|||
startingIdx: number;
|
||||
n: number;
|
||||
}): number | undefined {
|
||||
const globalRegex = addGlobalFlagIfNeeded(regex);
|
||||
const globalRegex = makeGlobalRegex(regex);
|
||||
const contentToSearch = content.substring(0, startingIdx);
|
||||
const previousMatches = [...contentToSearch.matchAll(globalRegex)];
|
||||
if (previousMatches.length < n) {
|
||||
|
|
@ -55,8 +79,9 @@ export function getNthPreviousInstanceOfPattern({
|
|||
return previousMatches[previousMatches.length - n].index;
|
||||
}
|
||||
|
||||
function addGlobalFlagIfNeeded(regex: RegExp): RegExp {
|
||||
return regex.global ? regex : new RegExp(regex.source, getGlobalFlags(regex));
|
||||
function makeGlobalRegex(regex: RegExp): RegExp {
|
||||
const globalFlags = getGlobalFlags(regex);
|
||||
return new RegExp(regex.source, globalFlags);
|
||||
}
|
||||
|
||||
function getGlobalFlags(regex: RegExp): string {
|
||||
|
|
|
|||
Loading…
Reference in a new issue