.. _utils_lsp:
.. index::
single: Utils; LSP Server
single: Utils; Language Server Protocol
single: Utils; Diagnostics
===========================================
LSP Server --- Editor & AI Diagnostics
===========================================
The daslang LSP server implements the
`Language Server Protocol `_
for ``.das`` files. What it buys over the :ref:`MCP tools `:
**push diagnostics** --- the compiler and lint report after *every* edit
with no explicit tool call --- plus native go-to-definition, references,
hover, document / workspace symbols, call hierarchy, and
go-to-implementation.
Zero setup beyond a daslang binary and Python 3: no sgconfig, no
tree-sitter, no MCP server.
.. contents::
:local:
:depth: 2
Quick start
===========
Claude Code sessions started at the daslang repository root (or at an
installed SDK root) load the server automatically --- the checked-in
``.claude/skills/daslang-lsp/.claude-plugin/plugin.json`` manifest
registers it on workspace trust. From any other directory, wire it in
explicitly with::
claude --plugin-dir /abs/path/to/daScript/utils/lsp/plugin
(The explicit form also takes precedence over the checked-in manifest
when both are present, which is what you want while developing the
server itself.)
Diagnostics then attach automatically to the next tool result after any
edit of a ``.das`` file, and the LSP tool exposes definition,
references, hover, documentSymbol, workspaceSymbol, implementation, and
call-hierarchy operations.
Any other stdio LSP client works too --- point it at::
python3 utils/lsp/lsp_supervisor.py
.. note::
The plugin manifest spawns ``python3``. On Windows, python.org
installs typically ship only ``python.exe`` --- either create a
``python3`` alias or edit the manifest's ``command`` locally.
Diagnostics
===========
One lint-profile compile serves both error classes:
- **Compile errors** publish with severity Error, the numeric daslang
error code (e.g. ``30341``), and the exact source range.
- On a clean compile, the repo's paranoid / performance / style lint
passes run on the same program: LINT and PERF rules publish as
Warning, STYLE rules as Information. Repo lint policy is honored
(``.lint_config``, default-off rules, ``// nolint`` comments).
dastest expect-files (negative tests that declare intentional compile
errors via ``expect`` directives) get a single informational note
instead of their intentional errors.
Unsaved buffers are compiled as-is: the server shadows every open
document and overlays the buffer text over the on-disk content for both
diagnostics and navigation, so clients that validate while typing get
correct positions without saving.
Navigation
==========
Definition, references, hover, and document / workspace symbols behave
as in any LSP server. The daslang-specific semantics:
- **Call hierarchy** covers direct calls (``foo()``, ``obj.method()``).
Virtual invocations through ``obj->method()`` and lambda /
function-pointer invokes are not statically resolvable and don't
appear. Generic instances collapse onto their generic; class-method
items display the bare method name.
- **Go to implementation**: on a class method --- its overrides in
derived classes; on a class or struct --- the types deriving from it.
- Search scope is the program of the file at the cursor (its
``require`` closure) --- callers or overrides in unrelated files that
are not part of that program are not found.
Configuration
=============
``initializationOptions`` (all optional):
.. list-table::
:header-rows: 1
:widths: 25 75
* - Key
- Description
* - ``compiler``
- Path to the daslang binary.
* - ``project``
- ``.das_project`` file passed to the compile (module roots /
source-resolver options).
* - ``project_root``
- Passed as ``-project_root`` (daspkg-style dynamic native module
resolution).
* - ``load_module``
- List; each entry passed as ``-load_module``.
Compiler lookup order: ``initializationOptions.compiler`` →
``$DASLANG_LSP_COMPILER`` → repo and installed-SDK layouts
(``bin/Release/daslang[.exe]``, ``bin/daslang[.exe]``,
``build/daslang``, ``build/bin/daslang``) under the workspace and the
supervisor's own tree → ``daslang`` on ``PATH``. All option paths
(``compiler``, ``project``, ``project_root``, ``load_module``) are
absolutized against the workspace root at initialize — subtools spawn
with a per-request cwd, so relative paths would otherwise re-resolve
per file.
Set ``DASLANG_LSP_LOG=`` for a message-by-message debug log
(default: ``daslang_lsp.log`` in the system temp directory).
Architecture
============
Two processes, hard split:
- ``utils/lsp/lsp_supervisor.py`` --- the endpoint the client spawns.
Owns all session state: Content-Length framing, the ``initialize``
handshake, the document shadow, debounce, and request dispatch.
Zero language knowledge.
- ``utils/lsp/subtools/*.das`` --- stateless batch tools
(``validate.das``, ``nav.das``). One fresh daslang process per
request: argv in, LSP-shaped JSON out, exit.
No resident daslang process ever runs, by design: no macro-state leaks
across compiles, no binary or DLL locks while builds run, and a
compiler crash on a broken buffer costs one request instead of the
session. Full rationale and wave history: ``utils/lsp/ROADMAP.md``.
Tests
=====
``tests/lsp/test_lsp_protocol.das`` drives the supervisor over a stdio
pipe: initialize → didOpen with broken buffer text against a clean disk
file (proving the unsaved-buffer overlay) → didChange back to clean →
definition → the call-hierarchy loop (prepare → incoming → outgoing) →
implementation → shutdown/exit::
bin/daslang dastest/dastest.das -- --test tests/lsp
The test probes ``python3`` then ``python`` and skips with a log notice
when neither is on ``PATH``.
.. seealso::
``utils/lsp/README.md`` --- setup and configuration details
:ref:`utils_mcp` --- the MCP server (pull-style tools: introspection,
navigation, execution, live-reload control)
`Language Server Protocol specification
`_