3131// server's `URI.toString()` form. This guarantees the server tracks the open document under
3232// the key it uses during diagnostics, enabling `computeDiagnostics()` and `markdown/fs/*` requests.
3333//
34+ // 3) Windows path suggestions (client→server) producing incorrect absolute drive paths:
35+ // Problem: When resolving workspace header/path completions, the current document URI and target
36+ // document URIs sometimes differ in Windows drive case/encoding (e.g., `file:///D:/...` vs
37+ // `file:///d%3A/...`). This causes relative path computation to fall back to odd absolute
38+ // paths such as `../../../../d:/...`.
39+ // Fix: Normalize the document URI in `textDocument/completion` requests so that the server sees a
40+ // consistent Windows-encoded form and computes clean relative paths (e.g., `GUIDE.md#...`).
41+ //
3442// Note: On non-Windows URIs, normalization is a no-op; messages are forwarded unchanged.
3543
3644const { spawn } = require ( 'child_process' ) ;
@@ -45,7 +53,8 @@ const serverArgs = process.argv.slice(3);
4553// Launch the wrapped language server; we proxy its stdio
4654const child = spawn ( process . execPath , [ serverMain , ...serverArgs ] , { stdio : [ 'pipe' , 'pipe' , 'inherit' ] } ) ;
4755
48- // Client → Server (Problem 2): normalize Windows URIs and mirror lifecycle notifications
56+ // Client → Server (Problem 2 & 3): normalize Windows URIs, mirror lifecycle notifications,
57+ // and normalize completion requests
4958let inBuffer = Buffer . alloc ( 0 ) ;
5059process . stdin . on ( 'data' , chunk => {
5160 inBuffer = Buffer . concat ( [ inBuffer , chunk ] ) ;
@@ -123,15 +132,14 @@ function processInboundBuffer() {
123132 }
124133}
125134
126- // Duplicate lifecycle notifications with a normalized URI so diagnostics can run (Problem 2)
135+ // Client → Server normalization (Problem 2 & 3 )
127136function transformInbound ( bodyBuf ) {
128137 try {
129138 const text = bodyBuf . toString ( 'utf8' ) ;
130139 const msg = JSON . parse ( text ) ;
131140 const method = msg && msg . method ;
132141
133- // For text document lifecycle events, also send a duplicate event
134- // with a normalized file URI so the server's URI.toString() lookups match.
142+ // Duplicate lifecycle notifications with a normalized URI (Problem 2)
135143 if ( ( method === 'textDocument/didOpen' || method === 'textDocument/didChange' || method === 'textDocument/didClose' || method === 'textDocument/willSave' || method === 'textDocument/didSave' ) && msg . params && msg . params . textDocument ) {
136144 const origUri = msg . params . textDocument . uri ;
137145 const normUri = normalizeFileUriForServer ( origUri ) ;
@@ -142,22 +150,35 @@ function transformInbound(bodyBuf) {
142150 return [ Buffer . from ( JSON . stringify ( msg ) , 'utf8' ) , Buffer . from ( JSON . stringify ( dup ) , 'utf8' ) ] ;
143151 }
144152 }
153+
154+ // Normalize completion requests so relative path suggestions are correct on Windows (Problem 3)
155+ if ( method === 'textDocument/completion' && msg . params && msg . params . textDocument ) {
156+ const origUri = msg . params . textDocument . uri ;
157+ const normUri = normalizeFileUriForServer ( origUri ) ;
158+ if ( normUri && normUri !== origUri ) {
159+ const req = structuredClone ( msg ) ;
160+ req . params . textDocument . uri = normUri ;
161+ return [ Buffer . from ( JSON . stringify ( req ) , 'utf8' ) ] ;
162+ }
163+ }
164+
145165 } catch { }
146166 return [ bodyBuf ] ;
147167}
148168
149169// Normalize Windows file URIs to match vscode-uri’s URI.toString() (Problem 2)
150170function normalizeFileUriForServer ( uri ) {
151- if ( typeof uri !== 'string' || ! uri . startsWith ( 'file:///' ) )
152- return undefined ;
153-
154- // Normalize Windows drive letter and encode colon to match vscode-uri toString()
155- // Example: file:///D:/path -> file:///d%3A/path
156- const after = uri . slice ( 'file:///' . length ) ;
171+ if ( typeof uri !== 'string' || ! uri . startsWith ( 'file:' ) ) return undefined ;
172+ // Strip scheme and leading slashes to get to drive letter
173+ let after = uri . slice ( 'file:' . length ) ;
174+ while ( after . startsWith ( '/' ) ) after = after . slice ( 1 ) ;
175+ // Example accepted forms: D:/path or d:/path
157176 if ( / ^ [ A - Z a - z ] : / . test ( after ) ) {
158177 const drive = after [ 0 ] . toLowerCase ( ) ;
159- const rest = after . slice ( 2 ) ; // drop ":"
160- return 'file:///' + drive + '%3A' + rest ;
178+ const rest = after . slice ( 2 ) ; // drop ':'
179+ // Ensure a leading slash for the path segment
180+ const pathPart = rest . startsWith ( '/' ) ? rest : '/' + rest ;
181+ return 'file:///' + drive + '%3A' + pathPart ;
161182 }
162183 return undefined ;
163184}
0 commit comments