18 — The CLI as an IDE

Previous: Concurrency

日本語版: 18_cli.vibe.md

Everything an editor gets from the language server, you can ask for from the shell. The answers are shaped so you can pipe them: one record per line, fixed field order, and empty output means nothing is wrong.

That last one is worth internalising. vibe check printing nothing is the success case.

Questions you can ask

vibe check file.vibe                # empty = this file compiles (imports resolved)
vibe check --single-file file.vibe  # buffer-only; imports are not followed
vibe symbols file.vibe              # outline: NAME KIND START END [DOC]
vibe type-at file.vibe 12 4         # hover at 1-based line,col
vibe binding-at file.vibe 12 4      # rename / refs
vibe escapes file.vibe              # which let mut is boxed
vibe deps file.vibe                 # resolved import closure
vibe deps --direct file.vibe        # one hop; cache-friendly
vibe grep --pattern 'f($(x:exp))' --where '$x : Array[_]' lib

vibe lsp speaks LSP on stdin/stdout for editors that want a server.

--single-file is not a lesser check. It is the un-saved-buffer tool: it will report unknown name for imported constructors even when the project is fine. To know whether the file compiles, omit the flag.

vibe grep matches an AST pattern, not text. Filters can ask the checker (--where '$x : Array[_]', --where-row '$f with Async', --only-ill-typed). Those filters take the same import-resolving lane as vibe check.

A program the queries can see

fn add(x: Int, y: Int) -> Int {
  x + y
}

fn main with Console {
  println("\{add(2, 40)}")
}
42

On a file containing that add:

vibe symbols hello.vibe          # add 12 3 6 / main 12 46 50
vibe type-at hello.vibe 1 4      # (Int, Int) -> Int  (on the name `add`)
vibe type-at hello.vibe 5 4      # () -> () with Console  (on `main`)
vibe check hello.vibe            # empty

The 12 in the symbols lines is the LSP SymbolKind for a function, and the two numbers after it are byte offsets of the name — vibe symbols --legend prints the kind table. type-at takes a 1-based line and byte column and answers for the identifier there; on a position with no identifier it prints nothing and exits 0, the same empty-is-clean convention as everything else.

vibe test file.vibe compiles every test { } / test "name" { } block. inspect(value, "expected") is the snapshot form; vibe test --update rewrites the literal. inspect does not need an import — the compiler desugars it before checking.

See Tests for the language-level tour.

Next: Targeting wasm.