Finding code — tcm-ts

Finding code

names, by words in the name, signature or doc
tcm find <words>
terms by the shape of their type
tcm find ':<type>'
text in the code and pages
tcm search <text>
lines of one definition
tcm view <name> --grep <text>

Agents have the same as tcm_find, tcm_search and tcm_view's grep.

By name, signature or doc

tcm find <query> lists the names with every word of the query in the name, its signature or its doc, in any case:

$ tcm find square number

Matches in the name or signature come first, then those found by their doc.

  • It reads the branch it runs on. A session's changes to a page, doc or comment are found on its branch as soon as they're saved, and on main once they land. --branch <name> searches another branch's text, as that branch has it.
  • Signatures use current type names. After tcm mv lib.User lib.Person, find, view, ls, the editor and the agent tools show (u: Person) => string.

Text in the code

tcm search <text> is find in files for the codebase (the editor's Search view too). It lists each line with the text in the codebase's definitions (as they read now, with current names) and pages, by definition, numbered as tcm view --lines counts them. tcm view <name> --grep <text> shows one definition's lines with it, and two either side.

Both read the text the same way:

  • Text is as written: a|b is those three characters; ( is a parenthesis.
  • A regular expression is written /…/, with flags after it: /get|set/i. Its case follows its flags: /get/ matches only get.

tcm search options:

--case
match case (by default any case matches; --grep always matches case)
--word
whole words only
--under <namespace>
only a namespace, or one definition
--limit <n>
lines to show (default 200)
--regex
the old way: all the text is a regular expression, without slashes (the editor's Use Regular Expression)
--branch <name>
search another branch

Text that starts with - goes after --: tcm search -- --lines.

Finding nothing says what it looked for: no lines with the text "a|b" (text, as written: a regular expression is written /…/), or no lines matching /a|b/.

A search stops at 500 definitions or 5000 matches, and says so: it stopped at 500 definitions or 5000 matches: the rest of the codebase wasn't searched. Narrow it with more of the text, --word, or --under.

By type

tcm find ':<type>' finds terms by the shape of their type, as Unison's type search does. The query is a TypeScript type:

$ tcm find ':(xs: T[]) => T | undefined'
type variables: T
lib.first  #q5xh6tqo  <A>(xs: A[]) => A | undefined
lib.head   #glnvgmf6  <T>(xs: T[]) => T | undefined

$ tcm find ':(n: number, s: string) => string'
lib.repeat  #66hwcb26  (s: string, n: number) => string  arguments reordered

$ tcm find ':(id: string) => User'
lib.loadUser  #sjksocbz  (id: string) => Promise<User>  async
  • Type variables: names in the query that are neither codebase types nor TypeScript globals become type variables, so (xs: a[]) => a works like Unison's [a] -> a. Explicit generics (<T>(xs: T[]) => T) work too.
  • Codebase types are found by name: User, or lib.User when the short name is ambiguous (tcm says which paths it could be).
  • Parameter and type-variable names don't matter: <A>(xs: A[]) => A and <T>(ys: T[]) => T are the same shape.
  • A class itself (typeof Server, as in http.Server<typeof IncomingMessage>) is compared by the class, not by its static members.
  • Code that came from a push or pull gets its shapes as the branch comes to name it; a search works out any still missing.

Ranking

Exact matches (the same type up to renaming) come first. Then, closest first, each labelled:

arguments reordered
the same parameters in another order
async
returns a Promise of the queried result type
more specific
an instance of the query: sum: (xs: number[]) => number for (xs: a[]) => a
more general
the query is an instance of it: last: <T>(xs: T[]) => T for (xs: number[]) => number
partly compared
parts too deep or too big, or conditional or mapped types, were compared only as "some type"

Overloads are searched one by one; a function matches through its closest overload.

How it works

When a term is added, tcm records its type as a shape, kept beside it, not in its hash: functions, parameters, arrays, tuples, unions, object types and literals. Type parameters become numbered variables. Codebase types are referenced by hash, so renaming a type (tcm mv lib.User lib.Person) loses no matches and signatures show its new name. Library types (Promise, Map, a package's types) are referenced by name.

The query is typechecked, turned into a shape the same way, and unified with each term's. Query variables can stand for parts of a term's type (more specific), and the term's variables for parts of the query (more general).

Built from c482a14e