SearchView

tcmts.vscode.search.SearchView · #ev3sccbf · class

The Search view: find in files, for the codebase. Every line of its definitions (as they read now, with current names) and pages with the text in them, by definition, as VS Code's search shows files: a definition opens its page, a line its document at that line. Searched again when the codebase changes.

import type { PageFind } from "#codebase/tcmts/vscode/pages_panel";
import type { Request } from "#codebase/tcmts/vscode/releases";
import { searchHtml } from "#codebase/tcmts/vscode/search";
import type { SearchHit, SearchQuery } from "#codebase/tcmts/vscode/search";
import * as vscode from "vscode";

export class SearchView implements vscode.WebviewViewProvider {
  request: Request;
  openPage: (hash: string, find?: PageFind) => unknown;
  view: vscode.WebviewView | undefined;
  last: SearchQuery | undefined;
  hits: SearchHit[] = [];
  /** the latest search: answers to earlier ones are dropped */
  seq = 0;

  constructor(request: Request, openPage: (hash: string, find?: PageFind) => unknown) {
    this.request = request;
    this.openPage = openPage;
  }

  resolveWebviewView(view: vscode.WebviewView): void {
    this.view = view;
    view.webview.options = { enableScripts: true };
    view.webview.html = searchHtml(view.webview);
    view.webview.onDidReceiveMessage((m: { type: string; query?: SearchQuery; hit?: number; line?: number; source?: boolean }) => {
      if (m.type === "search") return this.search(m.query!);
      const hit = this.hits[m.hit ?? -1];
      if (!hit) return;
      if (m.type === "page") return this.openPage(hit.hash);
      const line = hit.lines[m.line ?? 0]!;
      const [start, end] = line.ranges[0] ?? [0, 0];
      // a line opens the page, at the match; with Alt (Option), the definition's document there
      if (!m.source || !hit.uri) return this.openPage(hit.hash, { text: line.text.slice(start, end), line: line.text, offset: start, caseSensitive: !!this.last?.caseSensitive });
      return vscode.window.showTextDocument(vscode.Uri.parse(hit.uri), { selection: new vscode.Range(line.line, line.column, line.line, line.column + end - start), preview: true });
    });
  }

  async search(q: SearchQuery): Promise<void> {
    this.last = q;
    const n = ++this.seq;
    if (!q.query) {
      this.hits = [];
      this.view?.webview.postMessage({ type: "results", hits: [], matches: 0, truncated: false });
      return;
    }
    try {
      const r = await this.request("tcm/searchCode", q);
      if (n !== this.seq) return;
      this.hits = r.hits;
      this.view?.webview.postMessage({ type: "results", ...r });
    } catch (e) {
      if (n === this.seq) this.view?.webview.postMessage({ type: "results", hits: [], matches: 0, truncated: false, error: (e as Error).message });
    }
  }

  /** Show the view, searching for `text` if given (the editor's selection). */
  async show(text?: string): Promise<void> {
    await vscode.commands.executeCommand("tcm.search.focus");
    this.view?.webview.postMessage({ type: "focus", ...(text ? { query: text } : {}) });
  }

  /** The codebase changed: the last search, again. */
  refresh(): void {
    if (this.last?.query) void this.search(this.last);
  }
}

uses

used by