PluginsCode intelligence

Code intelligence

Text search finds every line that mentions parse. A language server knows which parse you mean: where it is defined, who calls it, and what type it returns. Syntic Code can use language servers through its Lsp tool, and plugins are how you give it one.

A plugin that ships a .lsp.json file adds one or more language servers. Once that plugin is enabled, the agent can ask precise questions about code in matching files instead of guessing from text matches.

What the agent gains

The Lsp tool is read-only, so it never needs approval. It takes a file, an action and, for some actions, a position:

ActionWhat comes back
diagnosticsErrors and warnings the server reports for the file.
hoverThe type or signature at a position.
definitionWhere the symbol at a position is defined.
referencesEvery place the symbol is used, including its definition.
implementationWhere an interface or trait method is implemented.
symbolsAn outline of the file.
workspace_symbolsSymbols across the project whose name matches query.
incoming_callsFunctions that call the one at a position.
outgoing_callsFunctions that the one at a position calls.

Positions are zero-based { "line": 12, "character": 4 }. hover, definition, references, implementation and both call actions need one; the rest do not.

Without a language server for the file type, the tool answers that no server is connected, and the agent falls back to Grep and Read. The tools reference lists Lsp with the other built-in tools.

Add a language server with a plugin

There is no catalog of ready-made language-server plugins yet, but writing one takes a few minutes. You need the language server installed on your machine (or inside the plugin’s bin/ folder) and a plugin with a .lsp.json at its root.

rust-intel/
  .syntic-plugin/plugin.json
  .lsp.json

plugin.json:

{
  "name": "rust-intel",
  "version": "0.1.0",
  "description": "Rust code intelligence"
}

.lsp.json:

{
  "rust": {
    "command": "rust-analyzer",
    "extensionToLanguage": { ".rs": "rust" },
    "startupTimeout": 20000
  }
}

Try it without installing:

syntic --plugin-dir ./rust-intel

Then ask something that needs precise navigation, such as “where is Config::load called?”. When you are happy with it, publish it through a marketplace (Release a plugin) so others can install it.

Server entry fields

Each key in .lsp.json names a server; its value describes how to start it. A { "lspServers": { ... } } wrapper around the map is also accepted.

FieldTypeMeaning
commandstringRequired. The program to run.
argsarray of stringsArguments passed to it.
envobjectExtra environment variables for the server process.
extensionToLanguageobjectMaps file extensions to language ids, for example { ".ts": "typescript", ".tsx": "typescriptreact" }.
fileExtensions or extensionsarray of stringsA shorter form: each extension is served, and its language id is the extension without the dot.
startupTimeoutnumber (ms)How long the server has to finish starting. Default 30 seconds.

A server needs a command and at least one file extension, or it is ignored. Extensions are matched without regard to case, with or without a leading dot.

${SYNTIC_PLUGIN_ROOT} and ${SYNTIC_PLUGIN_DATA} work in command, args and env, so a plugin can run a server it bundles:

{
  "proto": {
    "command": "${SYNTIC_PLUGIN_ROOT}/bin/proto-ls",
    "args": ["--stdio"],
    "fileExtensions": ["proto"]
  }
}

A plugin’s bin/ folder is also put first on the server’s PATH. You can point the manifest’s lspServers field at a different file if you prefer; see the manifest reference.

How servers run

  • Started on demand. Nothing launches at startup. The first Lsp call on a matching file starts that server for the session’s working folder, and later calls reuse it.
  • One file at a time. Before a question, the file is opened on the server (or its current text is sent again), so answers reflect your latest edits.
  • Time limits. Each request may take up to 30 seconds. After opening a file, the tool waits up to 5 seconds for diagnostics to arrive.
  • Restarts. If a server exits, the next call starts it again, up to three times per session.
  • First match wins. When two plugins serve the same extension, the first one loaded answers.
  • No sign-in secrets. A language server is someone else’s program. It gets the variables its entry sets, but never the CLI’s own credentials from your environment.

Check what is configured

/plugin lists the language servers each enabled plugin declares, with the command it will run and the languages it covers. From the shell, syntic plugin details <plugin> shows a plugin’s language servers by name.

Troubleshooting

SymptomLikely cause and fix
”no language server is connected in this session”No enabled plugin declares a server for that file extension. Check extensionToLanguage or fileExtensions, then run /reload-plugins.
The server is listed but never answersThe command is not on your PATH. Run it yourself in a terminal; use an absolute path or ship it in the plugin’s bin/.
Start-up times out on a large projectRaise startupTimeout (milliseconds). Some servers index the whole workspace before they answer.
Answers look staleThe tool re-sends the file before each question, but some servers need a project build first. Run the build, then ask again.
The server needs a tokenPut it in the entry’s env, ideally through a plugin option marked sensitive and ${user_config.KEY}. See Plugin components.

Language servers in .lsp.json only work through a plugin. A .lsp.json in your project folder is not read on its own.