Environment
- elp version:
1.1.0+build-2026-06-11
- OS: Windows 10/11
- Workspace path containing mixed-case, non-ASCII segments (e.g.
G:\Arquivos Auxiliares\...), though we've since confirmed the underlying case-folding behavior is not limited to accented paths.
Summary
elp computes real diagnostics internally (visible via window/logMessage handle_event NativeDiagnostics/ErlangServiceDiagnostics with populated normal/labeled_syntax_errors content), but the actual textDocument/publishDiagnostics notification sent to the client comes back with an empty diagnostics: [] array for the same file, same revision.
Root cause (source-level, from a local build of main)
crates/elp/src/server.rs, in the diagnostics-publish path:
let uri = file_id_to_uri(&self.vfs.read(), *file_id);
...
let version = convert::vfs_path(&uri)
.ok()
.and_then(|path| self.mem_docs.read().get(&path).cloned())
.map(|d| d.version);
if version.is_none() {
// File is not in mem_docs, clear all but
// eqwalizer project diagnostics
Arc::make_mut(&mut self.diagnostics).clear(*file_id);
}
file_id_to_uri builds the URI from the vfs's own canonical path for file_id. On Windows, in our testing, this path comes back entirely lowercased (not just the drive letter - crates/elp/src/convert.rs's uri_from_abs_path only explicitly lowercases the drive letter, so the rest of the lowering must originate further upstream, in the paths/vfs crates pulled from the rust-analyzer repo per Cargo.toml, not in this repo's own source).
mem_docs is populated by the textDocument/didOpen handler using convert::abs_path(¶ms.text_document.uri) directly - i.e. whatever case the client sent, unmodified.
- On Windows, if the client sends the file's real on-disk case (which is the natural, unmodified thing for an LSP client to do), these two representations of the same physical file diverge in case,
mem_docs.get(&path) misses, version is None, and the diagnostics computed one line earlier get thrown away before ever reaching the client.
This reproduces reliably any time the workspace path's real case differs from what the internal vfs canonicalization normalizes it to (which, empirically, in our environment, was always fully lowercase - not just the drive letter).
Reproduction
- Open a file whose real on-disk path contains uppercase letters anywhere past the drive letter (this is the common case on Windows - most directory names aren't all-lowercase).
- Confirm via
log: "info" that NativeDiagnostics/ErlangServiceDiagnostics internally compute non-empty diagnostics for the file.
- Observe the corresponding
textDocument/publishDiagnostics notification: diagnostics: [].
Workaround (client-side, not a fix)
We worked around this in our LSP client by tracking the real-case URI at didOpen time and rewriting inbound file:// URIs from the server back to that real case before any client-side logic touches them. This does not address the actual inconsistency inside elp - mem_docs and the project-derived vfs path still disagree internally; we're just correcting for it after the fact on our end.
Suggested fix direction
Normalize case consistently at the single point where VfsPath/AbsPath values are constructed from client-provided URIs (didOpen, and anywhere else that ingests a client URI) so that they always match whatever canonical form vfs/file_id_to_uri produces internally - or, conversely, key mem_docs by FileId rather than by a re-derived path string, sidestepping the case question entirely.
Related, and possibly the same root cause surfacing differently: crates/ide/src/diagnostics/module_mismatch.rs compares -module() against path.name_and_extension() derived from the same internally-canonicalized path, which means on Windows this check can also produce false positives/negatives depending on which case convention the file's VfsPath happens to have been registered under.
Environment
1.1.0+build-2026-06-11G:\Arquivos Auxiliares\...), though we've since confirmed the underlying case-folding behavior is not limited to accented paths.Summary
elpcomputes real diagnostics internally (visible viawindow/logMessagehandle_event NativeDiagnostics/ErlangServiceDiagnosticswith populatednormal/labeled_syntax_errorscontent), but the actualtextDocument/publishDiagnosticsnotification sent to the client comes back with an emptydiagnostics: []array for the same file, same revision.Root cause (source-level, from a local build of
main)crates/elp/src/server.rs, in the diagnostics-publish path:file_id_to_uribuilds the URI from thevfs's own canonical path forfile_id. On Windows, in our testing, this path comes back entirely lowercased (not just the drive letter -crates/elp/src/convert.rs'suri_from_abs_pathonly explicitly lowercases the drive letter, so the rest of the lowering must originate further upstream, in thepaths/vfscrates pulled from therust-analyzerrepo perCargo.toml, not in this repo's own source).mem_docsis populated by thetextDocument/didOpenhandler usingconvert::abs_path(¶ms.text_document.uri)directly - i.e. whatever case the client sent, unmodified.mem_docs.get(&path)misses,versionisNone, and the diagnostics computed one line earlier get thrown away before ever reaching the client.This reproduces reliably any time the workspace path's real case differs from what the internal vfs canonicalization normalizes it to (which, empirically, in our environment, was always fully lowercase - not just the drive letter).
Reproduction
log: "info"thatNativeDiagnostics/ErlangServiceDiagnosticsinternally compute non-empty diagnostics for the file.textDocument/publishDiagnosticsnotification:diagnostics: [].Workaround (client-side, not a fix)
We worked around this in our LSP client by tracking the real-case URI at
didOpentime and rewriting inboundfile://URIs from the server back to that real case before any client-side logic touches them. This does not address the actual inconsistency insideelp-mem_docsand the project-derivedvfspath still disagree internally; we're just correcting for it after the fact on our end.Suggested fix direction
Normalize case consistently at the single point where
VfsPath/AbsPathvalues are constructed from client-provided URIs (didOpen, and anywhere else that ingests a client URI) so that they always match whatever canonical formvfs/file_id_to_uriproduces internally - or, conversely, keymem_docsbyFileIdrather than by a re-derived path string, sidestepping the case question entirely.Related, and possibly the same root cause surfacing differently:
crates/ide/src/diagnostics/module_mismatch.rscompares-module()againstpath.name_and_extension()derived from the same internally-canonicalized path, which means on Windows this check can also produce false positives/negatives depending on which case convention the file'sVfsPathhappens to have been registered under.