editorintelligence

package
v1.56.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package editorintelligence provides code navigation for the project editor: go to definition and find usages answered by language servers over stdio JSON-RPC. The servers run server side, the browser only exchanges bounded document snapshots and locations.

Index

Constants

View Source
const (
	StatusNoLanguage   = "no-language"
	StatusNotInstalled = "not-installed"
	StatusBusy         = "busy"
	StatusCanceled     = "canceled"
	StatusError        = "error"
	StatusUnavailable  = "unavailable"
	// StatusDisabled is the handler's answer for a profile switched off in
	// the settings; the service itself knows no settings.
	StatusDisabled = "disabled"
)

Statuses reported to the client when the language server cannot answer. The set only ever grows.

Variables

View Source
var ErrNoSourceRoot = errors.New("The file is outside the language servers' source directories.")

ErrNoSourceRoot is what a path outside every root answers, the read route's 400.

Functions

func CacheRoot

func CacheRoot(stateDir string) string

CacheRoot is where the per project cache directories live, one place under the state directory this serve process owns. Resolved, because the path travels into a container as a mount and comes back out inside the file URIs the server answers.

func RemoveProjectCaches

func RemoveProjectCaches(project, cacheRoot, dockerHost string)

RemoveProjectCaches takes the project's per-server cache directories away, close its servers first: the delete owns the whole project, index and module downloads included. Removal is retried briefly, a container still draining its exit writes into the directory for a moment; a directory that is already gone is skipped without a wait.

dockerHost names the configured daemon, empty for the ambient one; it is what removeCacheDir's container fallback and the removal of a volume an older release left behind travel on.

Types

type Detection

type Detection struct {
	Found bool
	// Path is the resolved executable when found, the docker client for
	// the Docker way.
	Path string
}

Detection is a launcher's answer whether its way can run, see Launcher.Detect. It never starts a process.

type IndexState

type IndexState struct {
	ProfileID string `json:"id"`
	Label     string `json:"label"`
	Indexing  bool   `json:"indexing"`
	// Preparing marks the stretch before the server process answers: the
	// launcher stands up what the start needs, which for the Docker way is
	// the image build on first use. The client words that phase apart from
	// the indexing, so a first activation is never a silent minute.
	Preparing bool `json:"preparing,omitempty"`
	// Percentage is the server's reported progress, -1 while it reports
	// none, which the client shows as an indeterminate indicator.
	Percentage int `json:"percentage"`
}

IndexState is one profile's indexing picture for the editor's statusbar indicator.

type Launcher

type Launcher interface {
	// ID tells launchers apart in the connection slots: a server whose
	// way to run changed is evicted and restarted the way a changed root
	// evicts it. The Docker way includes its configured daemon, so a
	// moved docker-host setting evicts too.
	ID() string
	// Detect answers whether the profile's server can run this way, and
	// where the way's own executable was found.
	Detect(p *Profile) Detection
	// Prepare stands up what one start needs, bounded by ctx. The Docker
	// way builds its image on first use, creates the labeled cache volume
	// and clears the stale container's name; a way with nothing to
	// prepare returns nil. projectsRoot is the ownership boundary the
	// labels carry.
	Prepare(ctx context.Context, projectsRoot, project string, p *Profile) error
	// Argv is the command line that starts the profile's server for the
	// workspace at root. projectsRoot is the directory a container mounts
	// at its own path so file URIs match inside and outside.
	Argv(projectsRoot, project, root string, p *Profile) []string
	// ProcEnv is the extra process environment the start command needs,
	// nil for none. The Docker way carries DOCKER_HOST when the cockpit
	// is configured for a daemon of its own, so the server runs on the
	// same daemon the availability gate read.
	ProcEnv() []string
	// InitOptions are the initializationOptions this way hands the server
	// for the project, nil for none. The container way points the server's
	// index storage into its cache mount, whose per-project directory is
	// the project boundary.
	InitOptions(project string, p *Profile) any
	// SourceRoots are the directories outside the project this way lets a
	// definition land in and read back: the dependency sources the server
	// downloaded, the standard library, the server's stubs. They are the
	// whole allowlist, an answer pointing anywhere else stays counted and
	// unopened.
	SourceRoots(project string, p *Profile) []SourceRoot
	// ReadSource answers the text of one file under one of its own roots.
	// The caller has checked that the root holds the path; whether the
	// file can be read at all, and how, is this way's business.
	ReadSource(ctx context.Context, root SourceRoot, path string) (string, error)
	// WantsRestart reads a dead server's exit code: true means the way
	// asks for an immediate fresh start (the container's workspace watcher
	// ends the container with an agreed code on a relevant change), every
	// other death stays an error.
	WantsRestart(exitCode int) bool
}

Launcher is one way to run a language server process; the Docker container is the one way today, a second container runtime would be the next. Everything that differs between the ways lives behind this interface: whether the way can run at all, what must stand before a start, the command line and environment that start the server, and what a death means. The service programs against the interface and carries no flavor branches, so another way is one more implementation and nothing else.

func DockerLauncher

func DockerLauncher(cacheRoot string, dockerHost func() string) Launcher

DockerLauncher runs the server inside a container the cockpit builds and names itself. cacheRoot is where the per project cache directories live, the binds that make a dependency's sources readable from both sides. dockerHost answers the configured daemon, nil or empty for the ambient one.

type Location

type Location struct {
	Path      string `json:"path"`
	Line      int    `json:"line"`
	Character int    `json:"character"`
	External  bool   `json:"external,omitempty"`
}

Location is one navigation target in editor coordinates: Line is 1-based, Character a 0-based UTF-16 offset into that line, both as the server reported them. Path is project relative, or the absolute path of a file under one of the language server's own source roots, which External marks: a dependency's downloaded sources, the standard library, a stub. Those open read only and through a route of their own, so a client can never confuse one with a file of the project.

type Profile

type Profile struct {
	// ID is the stable profile identifier, used by the settings key.
	ID string
	// Label names the language on the settings page and in the indexing
	// indicator.
	Label string
	// Command is the server's argv, run inside the profile's container
	// over the Docker option.
	Command []string
	// Server is the short name this server wears everywhere the cockpit
	// names it itself: the image, the container, the per project cache
	// directory and the stored settings value. It is deliberately its own
	// field and not the command's leading token, which is a program name
	// and may be long enough to eat the room a container name leaves for
	// the project.
	Server string
	// Marker is the file at a project root whose presence already says
	// the project holds this language, so the warm skips the tree walk.
	Marker string
	// SilentStart marks a server that announces no work when it starts
	// because it has none: it has the workspace ready by the time it
	// answers the first request, so a lookup is never held back waiting
	// for an announcement that is not coming. The servers that index the
	// workspace at the handshake announce that run seconds late, and their
	// silence until then has to be waited out, which is the zero value
	// here: a profile that says nothing keeps the careful behaviour.
	SilentStart bool
	// contains filtered or unexported fields
}

Profile is one fixed language server profile compiled into the binary. The command, and the container recipe of the Docker option, are never configurable, so no setting can become a command execution surface; a setting only picks which of the fixed ways runs, or none.

func ProfileForPath

func ProfileForPath(rel string) (*Profile, string, bool)

ProfileForPath returns the profile owning the file's extension and the LSP language id for it.

func Profiles

func Profiles() []*Profile

Profiles returns the fixed profile registry in rendering order.

func (*Profile) Extensions

func (p *Profile) Extensions() []string

Extensions returns the file extensions the profile owns, sorted.

type Request

type Request struct {
	Client      string
	ProjectName string
	ProjectRoot string
	Launcher    Launcher
	// Path is the file the cursor stands in, already validated by the
	// caller: project relative, or the absolute path of a source outside
	// the project the caller checked against the allowlist, which is what
	// a lookup from inside a read only tab asks with. Both travel the same
	// way from here, see documentPath.
	Path    string
	Content string
	// Line and Character are the 0-based LSP position, Character in UTF-16
	// units like the CodeMirror document.
	Line      int
	Character int
}

Request is one navigation request against the active document snapshot. Client names the asking editor instance: the connection is shared per project, the client only scopes document holds and in-flight cancellation. Launcher is the way the settings picked for the language's server; nil means the Docker way, the default.

type Result

type Result struct {
	Available bool       `json:"available"`
	Status    string     `json:"status,omitempty"`
	Locations []Location `json:"locations"`
	// Outside counts targets dropped because they lie outside the project.
	Outside   int  `json:"outside,omitempty"`
	Truncated bool `json:"truncated,omitempty"`
	// Declaration reports that a definition answer covers the asked
	// position itself: the cursor already sits on the declaration, and a
	// jump would lead nowhere new.
	Declaration bool `json:"declaration,omitempty"`
}

Result is one navigation answer. An unavailable server travels as a status inside an available=false result, never as an error.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service owns every language server connection. One Service belongs to one serve process. projectsRoot is what the Docker option mounts into a container, at its own path, so file URIs match inside and outside.

func New

func New(projectsRoot, cacheRoot string, dockerHost func() string) *Service

New returns a running service. cacheRoot is where the per project cache directories live, CacheRoot of the serve process's state directory. dockerHost names the configured daemon, nil for the ambient one.

func (*Service) Close

func (s *Service) Close()

Close shuts every language server down and stops the janitor. The preparation context falls first, so a shutdown never waits an in flight image build out; the graceful shutdown runs before the process contexts are cancelled, so servers get their shutdown request instead of a bare kill.

func (*Service) CloseDocument

func (s *Service) CloseDocument(client, project, path string)

CloseDocument lets the client go of the document on the project's shared connections, sent when a tab closes. The document really closes only when no other editor instance holds it, see closeDocument. Nil-receiver-safe like the other web-facing entry points.

func (*Service) CloseProject

func (s *Service) CloseProject(project string)

CloseProject shuts the project's language servers down the graceful way and forgets their slots, so the next warm starts fresh servers over a fresh scan; the manual reindex and a project delete are the callers. Nil-receiver-safe like the other web-facing entry points.

func (*Service) ConnectionCount

func (s *Service) ConnectionCount() int

ConnectionCount reports the live and starting language server connections. Nil-receiver-safe like the other web-facing entry points.

func (*Service) Definition

func (s *Service) Definition(ctx context.Context, req Request) (Result, error)

Definition answers where the symbol at the position is defined. Nil-receiver-safe like the other web-facing entry points.

func (*Service) IndexStatus

func (s *Service) IndexStatus(project string) []IndexState

IndexStatus answers which of the project's servers are indexing right now: a starting connection counts as indexing (its announcement has not arrived yet), a ready one by its announced work, and a ready one that has announced nothing counts as indexing for the warming window, because the announcement itself arrives seconds after the handshake and the indicator must not flicker off in that gap.

That last rule is about the gap between a handshake and the work it started, so it holds only for the servers that work there. A `SilentStart` server has no such gap: it is ready when it answers, so counting its silence as indexing would put a bar on the screen for work that is not happening, waiting for an end that is not coming. Its silence is readiness and is reported as such; the work it does announce later, fetching types for an untyped dependency, shows like anybody else's.

func (*Service) OnChange

func (s *Service) OnChange(fn func(project string))

OnChange registers the one listener for indexing moves; call before the service serves. Nil-receiver-safe like the other web-facing entry points.

func (*Service) References

func (s *Service) References(ctx context.Context, req Request) (Result, error)

References answers every location the symbol at the position is used at, its declaration included. Nil-receiver-safe like the other web-facing entry points.

func (*Service) SweepStale

func (s *Service) SweepStale()

SweepStale starts the boot sweep in the background and gates the first server starts behind it. It removes every container of the LSP naming scheme labeled with this service's own projects root, and every cache directory whose project no longer exists on disk: at serve start none of them has a living owner, the previous process and its pipes are gone, while the lazy removal before a start only ever covers the same project and language starting again. The root label is the ownership boundary, so another live instance's servers on the same daemon are never touched. Call once, right after New.

func (*Service) Touch

func (s *Service) Touch(project string)

Touch marks editor action for the project: every connection of it counts as used now, which is what the idle shutdown measures. Safe on a nil service, which is what the web tests build their server without.

func (*Service) Warm

func (s *Service) Warm(project, root string, modes []WarmMode)

Warm makes sure the project's server for each given profile runs, so the indexing starts when the editor page opens instead of with the first lookup. It answers nothing: a profile that cannot start (not installed, table full of working slots) simply stays cold and the first lookup says why.

type SourceRoot

type SourceRoot struct {
	Path  string
	Image string
}

SourceRoot is one directory outside the project a navigation answer may legitimately point into: the downloaded sources of a dependency, the standard library, a server's own stubs. Together they are the allowlist the read route stands on, and nothing outside them is readable through it, so the editor never becomes a way to read this machine.

Path is the same path inside the container and outside, which is what the cache mount is arranged for and what the image roots are by nature. Image is empty for a directory on this host and otherwise names the image the tree lives in, which is then the only way to read it.

func FindSourceRoot

func FindSourceRoot(roots []SourceRoot, p string) (SourceRoot, bool)

FindSourceRoot answers the root holding the path, in the order the roots were given.

func (SourceRoot) Holds

func (r SourceRoot) Holds(p string) bool

Holds reports whether the file path lies inside the root. The path has to be absolute and already clean: a relative path, a "..", a doubled or a trailing separator is refused and never repaired, because a repaired path is a second spelling of a file and the check would then be about a path nobody asked for. The root itself is a directory and no file, so it is not in it either.

type WarmMode

type WarmMode struct {
	ProfileID string
	Launcher  Launcher
}

WarmMode names one profile to warm and the way its server runs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL