char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
ab5df08
main
files
| src/ | |
| tests/ | |
| typescript/ | |
| BACKEND.md | 3.7 KiB |
| Cargo.toml | 465 B |
| README.md | 8.0 KiB |
avril LSP
avril-lsp contains the Rust native TypeScript adapter and stdio LSP frontend.
avril lsp supplies avril's live project service without initializing a guest
runtime. A small in-tree patch exposes the pinned
TypeScript 7.1.0-dev.20261013.1 native language service through --api.
Build the executable using the backend instructions.
cargo build-p avril-cli--bin avriltarget/debug/avril lsp--typescript=/absolute/path/to/avril-tsc \--project=/absolute/path/to/workspace
The project directory defaults to the current directory. With unrestricted execution authority, workspace JS/TS files are graph roots (respecting ignore files, excluding hidden directories, node_modules, cache and vendor directories, and not following directory symlinks). With restricted authority, only explicit open documents are roots; discovery never broadens that authority. Imports grow the graph automatically, including unsaved import edits. No manifest or generated tsconfig is needed. The VS Code client launches this mode.
avril check --typescript=PATH <module> reuses the same project service and
backend without an LSP session: it builds one generation rooted at the entry
module, prints every diagnostic, and exits nonzero on errors. See
the check documentation.
Resolution and policy
The live service reuses ModuleGraph, LocalPackages, RemotePackages, and
PackageStore, not a parallel implementation of avril resolution. The in-memory
type collector in runtime/src/type_graph.rs feeds live generations. It follows
type-only/dynamic literal imports, package exports, per-referrer maps, and
@ts-self-types declaration redirects.
Builtin declarations are embedded and need no source checkout.
- Existing
avril.confownership, dependency bindings, and private import maps remain authoritative. TypeScriptpathsare not resolution authority. - Cache, lock selection/integrity, vendor input, frozen/offline behavior, and
capability checks use the runtime implementation. Flags include
--offline,--frozen,--jsr-registry=URL,--cache-dir=PATH,--lock=PATH,--vendor=PATH,-0, and--allow-*/--deny-*. There is no implicit entry grant in LSP mode. - The editor may acquire permitted dependencies into the cache. It never writes lockfiles, generated tsconfigs, editor snapshots, or application sources. Use an explicit runtime cache/update/vendor command to change those artifacts.
- Unsaved contents do not bypass
js-execchecks. Dependencies and configs use execution/acquisition authority, not guest filesystem read permission. - The shared scoped JSX/JSONC-extends loader supplies per-file compiler settings. Separate native programs provide checking contexts for differing JSX settings. Other TypeScript checking settings currently use the adapter host defaults.
Document changes and close/revert rebuild the graph. Directory watches cover the
workspace, known source/config parents, explicit lock and vendor inputs, and
external tsconfig extends (including failed config probes). Watch invalidation
is coalesced on a 500 ms tick. Cache writes are excluded to avoid refresh loops.
Under restricted execution capabilities, native directory watches are disabled;
client workspace/didChangeWatchedFiles notifications still refresh the graph.
The VS Code client supplies workspace file notifications. Malformed/unavailable
inputs produce resolver diagnostics rather than silently retaining old edges.
Language service and boundaries
The native backend supplies rich hover and signature help, completion and resolve (including snippets, rewrites and graph-scoped auto-imports), definitions/type definitions/implementations, references, prepare-rename/rename, document and workspace symbols, highlights, folding/selection ranges, semantic tokens, inlay hints, call hierarchy, code lenses, JSX linked editing, formatting, and native quick fixes/fix-all/organize-import actions. Capabilities and the method allowlist come from the patch, not a parallel Rust feature catalog. Diagnostics include native suggestion tags and related locations as well as Avril's resolver errors.
ProjectServiceproduces immutableProjectInputgenerations from overlays. Acquisition/configuration stay in the host. Resolved imports contain canonical module URIs and separate import/require targets. The adapter installs a static TypeScript resolution table with no fallback discovery; resolution never calls back into the runtime. Authorized auto-import candidates are supplied separately.projectowns compiler-only per-module directories, snapshots and checking contexts. Original source text is unchanged. Cross-program searches visit only explicit host-created compiler-option groups.bridgemaps document identities and native LSP results, stores bounded opaque continuations, and rejects stale resolves withContentModified(-32801). A rename touching a read-only source is rejected as a whole, never partially applied. No AST decoding, signature synthesis, or completion reconstruction remains in Rust.backendsupervises bidirectional requests and callbacks. Cancellation reaches native request contexts; disconnect and shutdown kill/reap the subprocess with bounded cleanup.- Auto-import indexing uses acquired programs only. The runtime reverse-resolves local candidates through its normal scoped resolver; the patch uses those explicit specifiers instead of inventing imports from compiler-only paths.
- Workspace locations retain canonical file URIs. Acquired npm files use
read-only
avril-builtin:/dependencies/identities, and their overlays are ignored even when opened through their underlying file paths. Remote and builtin sources use read-onlyavril-https:,avril-http:, andavril-builtin:providers. Embedded TypeScript libraries useavril-builtin:/typescript/lib.*.d.tsand are readable only when present in an explicit program.
Native TypeScript preferences can be provided in
initialize.initializationOptions.preferences and replaced with
workspace/didChangeConfiguration.settings.preferences. These include inlay
hint and code-lens preferences (disabled by upstream defaults), formatting, and
completion preferences. Missing properties preserve snapshot defaults.
Remaining limits
This is not a claim of complete classic tsserver parity. The pinned native backend does not implement the full classic refactor catalog or type hierarchy. File-move/import-update operations, project-reference discovery, untitled documents without file identities, and acquisition of otherwise unreachable packages solely for auto-import suggestions are not implemented. Unsupported providers are not advertised; unknown methods return MethodNotFound.
Document notifications are coalesced, and failed backends recover before the next query. Generations still rebuild the host graph and native session, favoring straightforward invalidation over incremental performance. Directory watches are coarse; deferred results must be requested again after a refresh. Compiler library globals are not yet curated to Avril's exact runtime.
The native executable must be trusted. Its environment is cleared and filesystem callbacks restrict compiler inputs, but they are not an OS sandbox. Project code and external compiler plugins are not executed. Guest npm/Node compatibility and automatic backend distribution are not introduced.
Validation
AVRIL_TEST_TYPESCRIPT =" $ TSC " cargo nextest run--workspace cargo test--workspace --doc cargo clippy-p avril-lsp-p avril-runtime--all-targets -- -D warningscd editors/vscode&& pnpm test
$TSC is the backend's absolute path; tests that need it are skipped when
AVRIL_TEST_TYPESCRIPT is unset. Lifecycle tests inspect Linux /proc.
The pinned backend is the test suite's only external dependency. Actual
editor-host tests and the required success marker are described in
the client README.