textPattern

tcmts.cli.pattern.textPattern · #gycza5hg · term · runs anywhere

A pattern as tcm's text searches take it (tcm search, tcm view --grep, the agent tools', code search in an editor): the text as written (a|b is that text), or a regular expression written /…/, flags after it (/get|set/i). regex takes all the text as a regular expression, without slashes (an editor's toggle, and tcm search --regex, the old way). caseSensitive is for text and a regular expression without slashes: one written /…/ matches case as its i flag says. wholeWord keeps to matches that are whole words. find gives a line's matches, as [start, end); a regular expression that isn't one is an error.

import { writtenRegex } from "#codebase/tcmts/cli/pattern";

export function textPattern(
  text: string,
  opts: { caseSensitive?: boolean; wholeWord?: boolean; regex?: boolean } = {},
):
  | { find: (line: string) => [number, number][]; regex: boolean; error?: undefined }
  | { error: string } {
  const written = opts.regex ? undefined : writtenRegex(text);
  const source = written
    ? written.source
    : opts.regex
      ? text
      : text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  // (g, to find each match in a line; a written g or y would only carry on from where the last line's ended)
  const flags = written
    ? `${written.flags.replace(/[gy]/g, "")}g`
    : opts.caseSensitive
      ? "g"
      : "gi";
  let re: RegExp;
  try {
    re = new RegExp(opts.wholeWord ? `\\b(?:${source})\\b` : source, flags);
  } catch (e) {
    return { error: (e as Error).message };
  }
  const find = (line: string) => {
    const ranges: [number, number][] = [];
    re.lastIndex = 0;
    for (let m = re.exec(line); m; m = re.exec(line)) {
      if (!m[0].length) {
        re.lastIndex++;
        continue;
      }
      ranges.push([m.index, m.index + m[0].length]);
    }
    return ranges;
  };
  return { find, regex: !!written || !!opts.regex };
}

uses

used by