Cross-file queries waited 5s after the first file was opened. On a cold Metals
that is long before it has imported the build, indexed, and — crucially —
compiled: references come from SemanticDB, which the build server writes as it
compiles, so a query made in between returns some of the references rather than
none, with nothing to mark it partial. Against a ~700-file Scala 3 build the
first find-references returned 124 of 242.
Metals already reports all of this as work-done progress; Serena did not declare
`window.workDoneProgress`, so none of it arrived. Declare it, track the tokens,
and wait for them before the first cross-file query — the same event-based shape
as TypeScript's indexing wait (#1639), whose hooks this uses.
Readiness is "nothing outstanding for a moment", not "nothing outstanding":
Metals hands off between its phases rather than overlapping them, so the token
set empties for around a second between the import ending and indexing starting,
and a plain drain returns in that gap. Measured on the same build, the first
find-references now returns all 242, taking 32s rather than 22s.
Metals serves one build per workspace folder, and Serena sends only the
repository root. Where the builds live below the root, Metals falls back to
its own search (BuildTools.searchForBuildTool), which looks one level down
and takes the *first* match — so in a monorepo every build but that one is
served with no build target, and cross-file references silently come back
empty.
Detect the build roots instead and send them all, one Metals service each.
`ls_specific_settings.scala.project_roots` names them explicitly where the
detection guesses wrong, `project_root_scan_depth` bounds the search.
The configured `ls_workspace_folders` are not usable for this: they are about
what SolidLSP indexes and are shared by every language server of a project, so
in a polyglot monorepo no single value suits both Metals and, say, tsserver.
Where the repository root is itself a build root, nothing changes.
* scala: answer Metals' build-import prompt
Metals asks, via window/showMessageRequest, whether to import a workspace it
has not seen before. Serena registered no handler, so the request came back
`method 'window/showMessageRequest' not handled on client` and Metals logged
"Unexpected error initializing server" and gave up. No build server, no build
target, and every cross-file query answered by the fallback presentation
compiler — which cannot see past the file it is given.
A client cannot decline the question instead: Metals' `disableShowMessageRequest`
is server-side configuration, and its no-op fallback answers "Not now", which
imports nothing either. Answering is the only route to a build.
So answer the three prompts that lead to a build server, and dismiss anything
else with `null` — a prompt we do not recognise is one whose consequences we
cannot judge, and two of Metals' others offer to kill a process and to open a
window. "Don't show again" is never chosen; Metals persists that in the
project's own state.
Since answering yes lets Metals run the project's build tool,
`ls_specific_settings.scala.auto_import_build: false` declines instead.
* scala: name the build-tool choice as a gap, and test the setting
`Messages.ChooseBuildTool` ("Multiple build definitions found. Which would
you like to use?") offers the build tools' own executable names, and precedes
the import prompt wherever a workspace holds more than one kind of build. It
is dismissed like anything else unrecognised, so such a workspace is still
not imported — a deliberate choice, since picking one is a guess of a
different order, but one the comment and the setup guide should admit to
rather than claim every prompt on the path is answered.
Also: cover the route from `ls_specific_settings` to `auto_import_build`,
which nothing exercised, and correct the `ImportBuildChanges` message in the
fixtures, which had the notification variant's trailing full stop rather than
the request's own text.
* feat(scala): Add stale lock detection for Metals H2 database
Add detection and automatic cleanup of stale Metals H2 database locks
to improve multi-instance support. When a Metals process crashes without
proper cleanup, it can leave a lock file that prevents proper AUTO_SERVER
coordination with new instances.
Changes:
- Add metals_db_utils.py with detection logic for stale locks
- Add StaleLockMode enum (auto-clean/warn/fail) for configurable behavior
- Add MetalsStaleLockError exception for fail mode
- Integrate stale lock checking into ScalaLanguageServer init
- Add configuration options: on_stale_lock, log_multi_instance_notice
- Add comprehensive unit tests for detection logic
This enhancement allows Serena's Metals to coexist with VS Code or other
editors running Metals on the same project, with proper handling of edge
cases like crashed processes.
* test(scala): Add integration tests for stale lock handling modes
Add 7 integration tests for ScalaLanguageServer stale lock detection:
- AUTO_CLEAN mode cleans stale locks and proceeds
- WARN mode logs warning without cleanup
- FAIL mode raises MetalsStaleLockError
- ACTIVE_INSTANCE logs info when notice enabled
- ACTIVE_INSTANCE silent when notice disabled
- NO_DATABASE proceeds silently
- NO_LOCK proceeds silently
Tests use mocking to avoid requiring Java/Coursier/Metals.
* docs(scala): Add multi-instance and stale lock documentation
Document Metals multi-instance support via H2 AUTO_SERVER:
- Explain how multiple Metals instances share database and Bloop
- Document memory considerations (~500MB per JVM)
- Add stale lock detection configuration options
- Document on_stale_lock modes (auto-clean, warn, fail)
- Add YAML configuration examples
* docs: Clean up docs
Removed memory considerations and additional configuration sections from the Scala setup guide.
* refactor(scala): Extract settings helper and add module-level constants
- Add DEFAULT_METALS_VERSION, DEFAULT_CLIENT_NAME, DEFAULT_ON_STALE_LOCK,
DEFAULT_LOG_MULTI_INSTANCE_NOTICE constants to eliminate duplication
- Extract _get_scala_settings() pure function for centralized settings parsing
- Remove redundant cleanup_stale_lock import
- Add warning log when AUTO_CLEAN mode fails to remove stale lock
- Simplify lock path handling with ternary expression
This refactoring improves code maintainability by centralizing Scala
settings extraction and applying functional programming principles.
* chore(scala): fix typo in docstring and comments
Unlike other language servers, metals LS only works properly when the project has been compiled in a particular way. Compiling like this on the fly or in CI is unfeasible, so instead, we expect the user to setup and compile the project correctly when using Serena for scala. Extensive documentation has been added.
Scala tests exist, but we don't run them in CI. They have been executed manually on macOS and Windows, thereby verifying that after following the instructions, Scala support works in Serena.