feat: implement looping around

This commit is contained in:
Aly Thobani 2024-05-11 12:23:23 -07:00
parent 2adf45cdbc
commit 19d095883e

View file

@ -5,6 +5,12 @@ import { shim as matchAllShim } from "string.prototype.matchall";
// Polyfill for String.prototype.matchAll, in case it's not available (pre-ES2020)
matchAllShim();
/**
* Returns the position of the repeat-th instance of a pattern from a given starting position, in
* the given direction; looping to the other end of the document when reaching one end.
*
* Under the hood, we avoid repeated loops around the document by using modulo arithmetic.
*/
export function jumpToPattern({
cm,
oldPosition,
@ -31,8 +37,11 @@ export function jumpToPattern({
}
/**
* Returns the index of (up to) the n-th next instance of a pattern in a string after a given
* starting index. If the pattern is not found at all, returns undefined.
* Returns the index (from the start of the content) of the nth-next instance of a pattern after a
* given starting index.
*
* "Loops" to the top of the document when the bottom is reached; under the hood, we avoid repeated
* loops by using modulo arithmetic.
*/
function findNthNextRegexMatch({
content,
@ -46,22 +55,23 @@ function findNthNextRegexMatch({
n: number;
}): number | undefined {
const globalRegex = makeGlobalRegex(regex);
globalRegex.lastIndex = startingIdx + 1;
let currMatch, lastMatch;
let numMatchesFound = 0;
while (
numMatchesFound < n &&
(currMatch = globalRegex.exec(content)) != null
) {
lastMatch = currMatch;
numMatchesFound++;
const allMatches = [...content.matchAll(globalRegex)];
const previousMatches = allMatches.filter((match) => match.index <= startingIdx);
const nextMatches = allMatches.filter((match) => match.index > startingIdx);
const nModulo = n % allMatches.length;
const effectiveN = nModulo === 0 ? allMatches.length : nModulo;
if (effectiveN <= nextMatches.length) {
return nextMatches[effectiveN - 1].index;
}
return lastMatch?.index;
return previousMatches[effectiveN - nextMatches.length - 1].index;
}
/**
* Returns the index of (up to) the nth-previous instance of a pattern in a string before a given
* starting index. If the pattern is not found at all, returns undefined.
* Returns the index (from the start of the content) of the nth-previous instance of a pattern
* before a given starting index.
*
* "Loops" to the bottom of the document when the top is reached; under the hood, we avoid repeated
* loops by using modulo arithmetic.
*/
function findNthPreviousRegexMatch({
content,
@ -75,12 +85,11 @@ function findNthPreviousRegexMatch({
n: number;
}): number | undefined {
const globalRegex = makeGlobalRegex(regex);
const contentToSearch = content.substring(0, startingIdx);
const previousMatches = [...contentToSearch.matchAll(globalRegex)];
if (previousMatches.length < n) {
return previousMatches[0]?.index;
}
return previousMatches[previousMatches.length - n].index;
const allMatches = [...content.matchAll(globalRegex)];
const previousMatches = allMatches.filter((match) => match.index < startingIdx);
const nextMatches = allMatches.filter((match) => match.index >= startingIdx);
const match = getNthPreviousMatch(previousMatches, nextMatches, n);
return match?.index;
}
function makeGlobalRegex(regex: RegExp): RegExp {
@ -92,3 +101,29 @@ function getGlobalFlags(regex: RegExp): string {
const { flags } = regex;
return flags.includes("g") ? flags : `${flags}g`;
}
function getNthPreviousMatch(
previousMatches: RegExpExecArray[],
nextMatches: RegExpExecArray[],
n: number
): RegExpExecArray | undefined {
const numMatches = previousMatches.length + nextMatches.length;
const effectiveN = n % numMatches; // every `numMatches` is a full loop
if (effectiveN <= previousMatches.length) {
return getNthItemFromEnd(previousMatches, effectiveN);
}
return getNthItemFromEnd(nextMatches, effectiveN - previousMatches.length);
}
/**
* Returns the nth (1-indexed) item from the end of an array. Expects 1 <= n <= items.length, but
* just returns undefined if n is out of bounds.
*/
function getNthItemFromEnd<T>(items: T[], n: number): T | undefined {
const numItems = items.length;
if (n < 1 || n > numItems) {
console.warn(`Invalid n: ${n} for array of length ${numItems}`);
return undefined;
}
return items[numItems - n];
}