« get me outta code hell

sugar: matchMultiline - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-01-05 22:01:34 -0400
committer(quasar) nebula <qznebula@protonmail.com>2024-01-06 12:50:30 -0400
commitf2bcab465a645a38d95a4284617d381ac841e0a8 (patch)
tree3b267450581fdf8f4cbcaf5bc978e61e519a4822 /src/util
parentd35bf2f6a1e19ac03b4b808e3a58b1fee9b9ca9c (diff)
sugar: matchMultiline
Diffstat (limited to 'src/util')
-rw-r--r--src/util/sugar.js75
1 files changed, 75 insertions, 0 deletions
diff --git a/src/util/sugar.js b/src/util/sugar.js
index 8ed37ff..9435aec 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