Dependency packages in the editor
Status: Implemented · ComlineProject/language-server (+ core and cli pieces) · Relates to core#6, core#50, core#59, core#60, core#61, core#63, cli#38, cli#39, cli#40, cli#42, language-server#24, language-server#25, language-server#26, language-server#27, language-server#28, language-server#29
How use shared_types::foo::X should work in the editor when shared_types is a
dependency declared in config.idp: hover, go-to-definition into the
dependency, completion, missing-import fixes, and a real error for a package that
doesn't exist. The language server never fetches anything itself.
Where things stand (2026-10-04)
| Piece | State |
|---|---|
dependencies block in config.idp (Path / Git / Registry) |
✅ parsed (core#50) |
Path + Git resolution, hash check, CAS vendoring |
✅ comline-core's opt-in deps feature (core#50); the CLI enables it, so comline check / build resolve dependencies |
Registry dependencies |
parsed, then rejected: there's no registry server yet (package-registry is on hold) |
comline add |
✅ resolves the dependency, then writes its entry with hash pinned (cli#40) |
Editor: use of a local schema |
✅ resolved across the whole package, open or not (language-server#22–#25) |
Editor: use of a dependency |
✅ resolved for path dependencies and fetched git pins; hover, go-to-definition, completion work into them (language-server#26) |
| Unresolved imports | ✅ rejected by comline check / build (core#59, cli#38) and reported by the editor with a "did you mean" quick fix (language-server#26) |
Editor: completion for use paths |
✅ namespaces, dependencies, declarations, * / {…} (language-server#27) |
std:: |
✅ embedded in core and merged into a build when imported (core#62, cli#42); the editor resolves it too (language-server#28) |
| Packages guide | ✅ updated alongside this record (it still said dependencies weren't implemented) |
How the build resolves a dependency use
From core#50 (core/src/package/deps.rs):
- A dependency's declared name becomes its namespace prefix.
shared_types = { path = "../shared-types" }makes the dependency'ssrc/foo.idsthe schemashared_types::foo, merged into the same pass as the package's own schemas, souse shared_types::foo::Xresolves like any otheruse. - Where the sources come from:
Path: relative to the consuming package's root.Git: cloned by thegitbinary into<package>/.comline/deps-cache/<blake3("{uri}#{commit}")>, once per pin.Registry: rejected.
- Direct dependencies only. The consumer sees the dependency's own
src/(glob_schema_sources), not the dependency's dependencies. - Until core#59, a
usethat matched no schema was trusted, not reported. When no schema had the namespace, theuselowered to a rawFrozenUnit::Importand the validator registered its last segment as a bare name. That coveredstd::collections::HashMap(std schemas aren't merged into builds), but alsouse shard_types::foo::Xwith a typo:comline buildaccepted it without a word. A glob or whole-namespaceuseof an unknown namespace failed only as "Unknown type" at each use site. Builds now reject all of these (decision 1 below).std::paths stayed trusted until core#62 made std resolvable.
Proposal
1. Find the package's dependencies, without fetching
On the workspace scan (language-server#25), and whenever a config.idp changes
(the VS Code client already watches **/*.{ids,idp}), the server would:
- Parse each package's
config.idpwith core's grammar, and readdependencieswith core's ownDependencyConfig::parse_dict. That is plain parsing, not behind thedepsfeature, and wasm-safe. - Locate each dependency's sources:
Path:<package>/<path>/src/**/*.ids.Git: the CLI's cache checkout,.comline/deps-cache/<key>/src, which exists oncecomline checkorbuildhas run. If it's missing, show a diagnostic on the entry inconfig.idpand on eachusenaming the dependency: "shared_typesisn't fetched yet — runcomline check". The editor never clones: that would mean network access, credentials and agitbinary, from inside an editor process.Registry: the same message the build gives ("registry dependencies aren't supported yet").
- Index dependency files read-only, each under the namespace
[dependency name] + its namespace inside the dependency.
Small changes this needs:
- core: make the cache-path computation public outside the
depsgate, for exampledeps::git_checkout_path(package_root, uri, commit). It's pure (blake3 is already a default dependency), and it saves the editor from re-deriving the cache key, which would silently break if the key ever changes. - language-server:
Projecttakes each file's namespace from the caller instead of always deriving it from the file's URI. A dependency file's path saysfoo; the consumer seesshared_types::foo. - language-server: index dependencies per consuming package and dependency name. Two packages may depend on the same directory under different names.
2. Resolution and diagnostics
With dependency files in each request's project view, everything built in
language-server#22–#25 works unchanged: go-to-definition, references, hover, the
import check, auto-import completion and the "Add use ..." quick fix.
On top of that:
- Rename never edits a dependency's files. Starting a rename on a symbol declared in a dependency is refused: it isn't this package's to change.
- "Unresolved import" error. A
usewhose first segment is none of these:- a local namespace,
- a declared dependency,
std,self/parent/package.
Example: use shard_types::foo::X → "no schema or dependency named
shard_types — declared dependencies: shared_types", with a quick fix to the
closest name. This replaces the benefit of the doubt that globs and
whole-namespace imports still get when their target exists nowhere (the known
gap noted in language-server#25).
- The build agrees. comline build and check reject an unresolved import
too (decided below), so the editor and the build say the same thing: both
report it as an error. std:: was exempt in both until core#62 made std
resolvable; it's checked the same way now.
3. Completion for use paths
A use line got no completion (use counts as a declaration keyword, so
nothing was offered after it). With the project view above, language-server#27
completes one segment at a time:
use→ the package's top-level namespaces, its dependency names,std,self,parent,package.use shared_types::→ the dependency's namespaces.use shared_types::foo::→ its declarations, plus{and*.use shared_types::foo::{A,→ the declarations not listed yet.
Paths resolve the way the build resolves them, relative prefixes included.
4. config.idp
Key completion and hover for dependency entries already exist client-side
(comline-vscode's idpSchema.ts). The server adds diagnostics on an entry:
- a
paththat doesn't exist or isn't a package (noconfig.idp); - a
Gitpin that isn't fetched yet; - a
Registrysource.
Checking the declared hash needs a full compile of the dependency. That stays
with comline check.
5. comline add
comline add shared_types ../shared-types
comline add net --git https://github.com/acme/net --commit 4f2c9e1 --version 1.2.0
Shipped in cli#40. A path dependency is a positional directory rather than
--path, which is already the CLI's global "run against this directory" flag.
It:
- resolves the dependency once (fetch, compile, hash) with the existing
deps::resolve, so an entry that wouldn't resolve is never written; - writes the entry into
config.idpas a text edit, preserving the file's formatting, and reads it back with core's parser before saving; - writes
hash = "blake3:…": the pinresolvecomputes, and otherwise only suggests ("Consider pinning").--no-hashleaves it out.
A side effect that helps the editor: after comline add, a Git dependency's
cache checkout exists, so the editor can index it immediately. The registry form
waits for a registry.
Phasing
- ✅ core:
DependencyConfig::package_dir, the one definition of where a dependency lives, git cache key included (core#60). - ✅ language-server: read
config.idp, indexPathand fetchedGitdependencies under their namespace, so resolution, completion, hover and definition work into dependencies, plus the "not fetched" diagnostics (language-server#26). - ✅ language-server: unresolved-import errors, worded like the build's,
with a "did you mean" quick fix (language-server#26), and completion for
usepaths (language-server#27). - ✅ cli:
comline add(cli#40). - core: ✅ reject unresolved imports in builds (core#59, in the CLI since
cli#38). That also rejected relative prefixes in glob and
{ ... }imports, which never resolved; core#61 fixed them (cli#39), and core#65 made a prefix straight before::*or::{(use parent::*) parse. ✅ std ships with the toolchain, embedded in core, and resolves (core#62, cli#42, language-server#28).
Decisions (2026-10-04)
- Unresolved imports are rejected.
comline buildandcheckfail on ausethat matches no schema of the package or its dependencies, including a named item the target schema doesn't declare.std::was exempt until std became resolvable (core#62); it's checked like any other path now. - Whole-namespace imports bring names in bare. After
use pkg::types, bothUserandpkg::types::Userwork, as core and the editor already behave. Qualified-only was never the goal: theuseguide is corrected. - Direct dependencies only, for now. A dependency's own dependencies stay invisible to the consumer.
- The packages guide documents what shipped in core#50, updated alongside this record.
- std is embedded and implicit. Its schemas (
core_stdlib/packages/std) are compiled intocomline-core, versioned with the toolchain, and available in every package with noconfig.idpentry. A build merges only the std schemas its imports reach. The first std isstd::http(HttpMethod,Request,Response, ported from the old placeholders as plain types) andstd::validators(StringBounds). std and each module document themselves with module docs (//!, core#63), which the editor shows on hover and in completion (language-server#29). Its schemas stay GPL-3.0-only for now (see Licensing); the oldcore/stdlibplaceholders became test fixtures.
Still open
- Auto-import from dependencies. language-server#26 went with the proposal:
a dependency's types are offered after the package's own, labeled with the
dependency they come from. If that proves noisy, the alternative is to offer
them only once the dependency is already
used somewhere.