view

tcmts.cli.main.view · #appejnyt · term · runs on the server

uses process

Show definitions, with current names. part shows only some of each one's code, numbered: lines ("120-180", or several: "1-20,90-"), or the lines with grep in them and two lines either side (a large definition's one method, say): its text, or a regular expression written /…/ (flags after it: /a|b/i), as [textPattern] takes it. A name that isn't found is said first, with the names meant most likely (a namespace's, with what's in it), and the rest are shown: 1 if any wasn't.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { fileContent, formatOf, isBytes } from "#codebase/tcmts/cli/files";
import { header, lineSpans } from "#codebase/tcmts/cli/main";
import { namesElsewhere } from "#codebase/tcmts/cli/merge";
import { noLines, textPattern } from "#codebase/tcmts/cli/pattern";
import { bold, dim, red } from "#codebase/tcmts/cli/style";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { shortHash } from "#codebase/tcmts/core/hash";
import { render } from "#codebase/tcmts/loader";

export function view(
  cb: Codebase,
  targets: string[],
  part: { lines?: string; grep?: string } = {},
): number {
  const spans = part.lines === undefined ? undefined : lineSpans(part.lines);
  if (part.lines !== undefined && !spans)
    throw new UsageError(
      `--lines ${part.lines}: give a line (120), lines (120-180), from a line on (120-), or several (1-20,90-)`,
    );
  // (text matched in case, as written)
  const pattern =
    part.grep === undefined ? undefined : textPattern(part.grep, { caseSensitive: true });
  if (pattern?.error !== undefined) throw new UsageError(`--grep ${part.grep}: ${pattern.error}`);
  const has = (line: string) => pattern!.find(line).length > 0;
  const show = (text: string) => {
    if (!spans && !part.grep) return process.stdout.write(text);
    const lines = text.split("\n");
    if (lines.at(-1) === "") lines.pop();
    const keep = new Set<number>();
    for (const [from, to] of spans ?? [])
      for (let i = from - 1; i < Math.min(lines.length, to); i++) keep.add(i);
    if (part.grep)
      lines.forEach(
        (l, i) =>
          has(l) &&
          [-2, -1, 0, 1, 2].forEach((d) => i + d >= 0 && i + d < lines.length && keep.add(i + d)),
      );
    const width = String(lines.length).length;
    let last = -2;
    for (const i of [...keep].sort((a, b) => a - b)) {
      if (i !== last + 1 && last >= 0) console.log(dim("…"));
      console.log(`${dim(String(i + 1).padStart(width))} ${lines[i]}`);
      last = i;
    }
    if (!keep.size)
      console.log(
        dim(part.grep && !spans ? noLines(part.grep) : `no such lines: it has ${lines.length}`),
      );
  };
  // what's found and what isn't, before anything is shown
  const found: { t: string; h: string }[] = [];
  const missing: string[] = [];
  for (const t of targets) {
    const hs = cb.lookup(t);
    if (hs.length) for (const h of hs) found.push({ t, h });
    else missing.push(cb.notFound(t));
  }
  for (const m of missing) console.error(red(m));
  // what nothing here names (the other side of a merge's code uses it, say) shows by a name it has elsewhere
  const elsewhere = namesElsewhere(cb);
  for (const { t, h } of found) {
    const rec = cb.store.getDef(h)!;
    if (rec.kind === "file") {
      const format = formatOf(rec);
      const name = cb.store.pathsOf(h)[0] ?? t;
      console.log(
        `${dim("//")} ${bold(cb.store.pathsOf(h).join(", ") || "(unnamed)")} ${dim(shortHash(h))} ${dim(format)}`,
      );
      const content = fileContent(cb, rec, name.slice(0, Math.max(0, name.lastIndexOf("."))));
      if (isBytes(rec)) console.log(dim(`(${format || "bytes"}, ${content.length} bytes)`));
      else show(`${content.toString("utf8")}\n`);
      continue;
    }
    console.log(header(cb, h));
    const src = cb.source();
    const unnamed = new Map<string, string>();
    const nameOf = (d: string) => {
      const n = src.nameOf(d);
      if (n || d === h) return n;
      const k = elsewhere(d);
      if (k) unnamed.set(d, k.note);
      return k?.name;
    };
    show(render([rec], src, cb.lay, { style: "scratch", exportDeclarations: true, nameOf }).text);
    if (unnamed.size)
      console.log(
        dim(
          `// not named on this branch: ${[...unnamed].map(([d, note]) => `${shortHash(d)} is ${note}`).join("; ")}`,
        ),
      );
    console.log();
  }
  return missing.length ? 1 : 0;
}

uses

used by