Skip to content

LSP Server

lash provides a Language Server Protocol implementation as a separate lash-lsp binary. It imports only lang/ and runtime/ — it does not connect to the backend daemon.

lash uses a Lisp-2 namespace model, separating Scope into three maps:

  • vars — variable namespace (let/mut bindings)
  • fns — function namespace (fn declarations)
  • types — user-defined type namespace (type declarations)

declareFn/lookupFn and declareTypeDecl/lookupTypeDecl walk the scope chain for their respective namespaces, parallel to declareVar/lookupVar for variables.

Pre-execution AST validation, located in lang/semantic/, depends only on runtime/.

CheckDescription
Undeclared variable useReference not in any enclosing scope.
Undeclared function callNot in function namespace and not a builtin.
Immutable assignmentAssignment targets let binding.
Redeclarationlet in same scope where name exists.
Drop undeclareddrop on name not in scope.
Break/continue outside loopNot inside for/while.

Integration points:

  • Backend may call analyze() on scripts before execution.
  • LSP uses analyze() on partial ASTs for diagnostics.
  • Interactive mode skips analysis.

Located at lang/parser/tolerant.d. Wraps the existing tokenizer with error recovery:

  • On parse error: records diagnostic, skips to recovery point, continues.
  • Returns: statements, diagnostics, tokens.
  • Handles: incomplete expressions, unclosed braces, trailing dots, missing arguments.

Recovery points:

  • Statement-level: next ;, }, or start-of-line keyword.
  • Expression-level: next ), ], }, ,, or ;.
CapabilityTrigger
textDocumentSyncFull document sync
completionProvider. and $, with resolveProvider:true for lazy per-item docs
hoverProvider
diagnosticProviderPush on change
definitionProvider
semanticTokensProvidercomment token type with a documentation modifier for doc comments
  • Keywords: let, mut, fn, type, if, for, while, match, break, continue, exit, true, false, write, …
  • Builtins: cd, exit, jobs, fg, bg, z, set, session, audit, profile, echo, cat, ls, pwd, grep, head, tail, sort, uniq, wc — each with a one-line description shown in the popup.
  • Scope variables and functions: from the current analysis context.
  • Native-plugin functions and types: fns and type declarations loaded from ~/.lash/plugins/*.lash and the compiled-in bundle show up at command position with full signatures and doc comments.
  • Subcommands (arg position): after a known CLI (git, docker, kubectl, helm, cargo, npm, dub, systemctl), the matching subcommands appear with descriptions. Falls back to <cmd> --help parsing, then bash-completion invocation, for unknown commands.
  • PATH executables: at command position only, emitted alongside keywords and builtins. whatis descriptions attach lazily via completionItem/resolve.
  • Type methods: based on inferred type of the expression before the .
  • Environment variables: after $
  • Variables: show type and mutability.
  • Functions: show typed signature (fn gco(ref: GitBranch)) and attached doc comments.
  • Types: show name, cache TTL, method list, and doc comments.
  • Parameters inside a fn body: show the param’s type annotation and attached doc lines.
  • Methods: show signature and description.

Doc-comment spans (///, /** ... */) attached to a following fn, type, or unittest declaration are tagged with the documentation modifier, letting editors style them differently from ordinary // comments. Non-declaration-attached doc-comment syntax falls back to a plain comment token.

  1. didOpen — tokenize + tolerant parse + analyze — cache and publish diagnostics.
  2. didChange — re-parse + re-analyze — update and publish.
  3. didClose — remove from cache.