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
- Variables
- func CacheRoot(stateDir string) string
- func RemoveProjectCaches(project, cacheRoot, dockerHost string)
- type Detection
- type IndexState
- type Launcher
- type Location
- type Profile
- type Request
- type Result
- type Service
- func (s *Service) Close()
- func (s *Service) CloseDocument(client, project, path string)
- func (s *Service) CloseProject(project string)
- func (s *Service) ConnectionCount() int
- func (s *Service) Definition(ctx context.Context, req Request) (Result, error)
- func (s *Service) IndexStatus(project string) []IndexState
- func (s *Service) OnChange(fn func(project string))
- func (s *Service) References(ctx context.Context, req Request) (Result, error)
- func (s *Service) SweepStale()
- func (s *Service) Touch(project string)
- func (s *Service) Warm(project, root string, modes []WarmMode)
- type SourceRoot
- type WarmMode
Constants ¶
const ( StatusNoLanguage = "no-language" StatusNotInstalled = "not-installed" StatusBusy = "busy" StatusCanceled = "canceled" StatusError = "error" // 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ConnectionCount reports the live and starting language server connections. Nil-receiver-safe like the other web-facing entry points.
func (*Service) Definition ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.