From f2bcab465a645a38d95a4284617d381ac841e0a8 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 5 Jan 2024 22:01:34 -0400 Subject: sugar: matchMultiline --- src/util/sugar.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'src/util') diff --git a/src/util/sugar.js b/src/util/sugar.js index 8ed37ff8..9435aecf 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -260,6 +260,81 @@ export function cut(text, length = 40) { } } +// Iterates over regular expression matches within a single- or multiline +// string, yielding each match as well as: +// +// * its line and column numbers; +// * if `formatWhere` is true (the default), a pretty-formatted, +// human-readable indication of the match's placement in the string; +// * if `getContainingLine` is true, the entire line (or multiple lines) +// of text containing the match. +// +export function* matchMultiline(content, matchRegexp, { + formatWhere = true, + getContainingLine = false, +} = {}) { + const lineRegexp = /\n/g; + const isMultiline = content.includes('\n'); + + let lineNumber = 0; + let startOfLine = 0; + let previousIndex = 0; + + const countLineBreaks = range => { + const lineBreaks = Array.from(range.matchAll(lineRegexp)); + if (!empty(lineBreaks)) { + lineNumber += lineBreaks.length; + startOfLine = lineBreaks.at(-1).index + 1; + } + }; + + for (const match of content.matchAll(matchRegexp)) { + countLineBreaks(content.slice(previousIndex, match.index)); + + const matchStartOfLine = startOfLine; + + previousIndex = match.index + match[0].length; + + const columnNumber = match.index - startOfLine; + + let where = null; + if (formatWhere) { + where = + colors.yellow( + (isMultiline + ? `line: ${lineNumber + 1}, col: ${columnNumber + 1}` + : `pos: ${match.index}`)); + } + + countLineBreaks(match[0]); + + let containingLine = null; + if (getContainingLine) { + const nextLineResult = + content + .slice(previousIndex) + .matchAll(lineRegexp) + .next(); + + const nextStartOfLine = + (nextLineResult.done + ? content.length + : previousIndex + nextLineResult.value.index); + + containingLine = + content.slice(matchStartOfLine, nextStartOfLine); + } + + yield { + match, + lineNumber, + columnNumber, + where, + containingLine, + }; + } +} + // Binds default values for arguments in a {key: value} type function argument // (typically the second argument, but may be overridden by providing a // [bindOpts.bindIndex] argument). Typically useful for preparing a function for -- cgit 1.3.0-6-gf8a5