Skip to content

Commit 342b504

Browse files
committed
Normalize rootUri handling for LSP clients
1 parent 7384562 commit 342b504

File tree

1 file changed

+151
-12
lines changed

1 file changed

+151
-12
lines changed

src/cm/lsp/clientManager.js

Lines changed: 151 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ensureServerRunning } from "./serverLauncher";
99
import serverRegistry from "./serverRegistry";
1010
import { createTransport } from "./transport";
1111
import AcodeWorkspace from "./workspace";
12+
import Uri from "utils/Uri";
1213

1314
function asArray(value) {
1415
if (!value) return [];
@@ -157,8 +158,10 @@ export class LspClientManager {
157158
}
158159

159160
async #ensureClient(server, context) {
160-
const rootUri = await this.#resolveRootUri(server, context);
161-
const key = pluginKey(server.id, rootUri);
161+
const resolvedRoot = await this.#resolveRootUri(server, context);
162+
const { normalizedRootUri, originalRootUri } =
163+
normalizeRootUriForServer(server, resolvedRoot);
164+
const key = pluginKey(server.id, normalizedRootUri);
162165
if (this.#clients.has(key)) {
163166
return this.#clients.get(key);
164167
}
@@ -224,8 +227,12 @@ export class LspClientManager {
224227
new AcodeWorkspace(client, workspaceOptions);
225228
}
226229

227-
if (rootUri && !clientConfig.rootUri) {
228-
clientConfig.rootUri = rootUri;
230+
if (normalizedRootUri && !clientConfig.rootUri) {
231+
clientConfig.rootUri = normalizedRootUri;
232+
}
233+
234+
if (!normalizedRootUri && clientConfig.rootUri) {
235+
delete clientConfig.rootUri;
229236
}
230237

231238
if (server.startupTimeout && !clientConfig.timeout) {
@@ -239,7 +246,8 @@ export class LspClientManager {
239246
await ensureServerRunning(server);
240247
transportHandle = createTransport(server, {
241248
...context,
242-
rootUri,
249+
rootUri: normalizedRootUri ?? null,
250+
originalRootUri,
243251
});
244252
await transportHandle.ready;
245253
client = new LSPClient(clientConfig);
@@ -250,8 +258,25 @@ export class LspClientManager {
250258
if (info) {
251259
console.info(`[LSP:${server.id}] server info`, info);
252260
}
253-
if (rootUri) {
254-
console.info(`[LSP:${server.id}] root`, rootUri);
261+
if (normalizedRootUri) {
262+
if (
263+
originalRootUri &&
264+
originalRootUri !== normalizedRootUri
265+
) {
266+
console.info(
267+
`[LSP:${server.id}] root ${normalizedRootUri} (from ${originalRootUri})`,
268+
);
269+
} else {
270+
console.info(
271+
`[LSP:${server.id}] root`,
272+
normalizedRootUri,
273+
);
274+
}
275+
} else if (originalRootUri) {
276+
console.info(
277+
`[LSP:${server.id}] root ignored`,
278+
originalRootUri,
279+
);
255280
}
256281
client.__acodeLoggedInfo = true;
257282
}
@@ -265,21 +290,30 @@ export class LspClientManager {
265290
server,
266291
client,
267292
transportHandle,
268-
rootUri,
293+
normalizedRootUri,
294+
originalRootUri,
269295
});
270296

271297
this.#clients.set(key, state);
272298
return state;
273299
}
274300

275-
#createClientState({ key, server, client, transportHandle, rootUri }) {
301+
#createClientState({
302+
key,
303+
server,
304+
client,
305+
transportHandle,
306+
normalizedRootUri,
307+
originalRootUri,
308+
}) {
276309
const fileRefs = new Map();
310+
const effectiveRoot = normalizedRootUri ?? originalRootUri ?? null;
277311

278312
const attach = (uri, view) => {
279313
const existing = fileRefs.get(uri) || new Set();
280314
existing.add(view);
281315
fileRefs.set(uri, existing);
282-
const suffix = rootUri ? ` (root ${rootUri})` : "";
316+
const suffix = effectiveRoot ? ` (root ${effectiveRoot})` : "";
283317
console.info(`[LSP:${server.id}] attached to ${uri}${suffix}`);
284318
};
285319

@@ -297,7 +331,11 @@ export class LspClientManager {
297331
}
298332

299333
if (!fileRefs.size) {
300-
this.options.onClientIdle?.({ server, client, rootUri });
334+
this.options.onClientIdle?.({
335+
server,
336+
client,
337+
rootUri: effectiveRoot,
338+
});
301339
}
302340
};
303341

@@ -319,7 +357,7 @@ export class LspClientManager {
319357
server,
320358
client,
321359
transport: transportHandle,
322-
rootUri,
360+
rootUri: effectiveRoot,
323361
attach,
324362
detach,
325363
dispose,
@@ -417,3 +455,104 @@ function resolveIndentWidth(unit) {
417455
const defaultManager = new LspClientManager();
418456

419457
export default defaultManager;
458+
459+
const FILE_SCHEME_REQUIRED_SERVERS = new Set(["typescript"]);
460+
461+
function normalizeRootUriForServer(server, rootUri) {
462+
if (!rootUri || typeof rootUri !== "string") {
463+
return { normalizedRootUri: null, originalRootUri: null };
464+
}
465+
const schemeMatch = /^([a-zA-Z][\w+\-.]*):/.exec(rootUri);
466+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
467+
if (scheme === "file") {
468+
return { normalizedRootUri: rootUri, originalRootUri: rootUri };
469+
}
470+
471+
if (scheme === "content") {
472+
const fileUri = contentUriToFileUri(rootUri);
473+
if (fileUri) {
474+
return { normalizedRootUri: fileUri, originalRootUri: rootUri };
475+
}
476+
if (FILE_SCHEME_REQUIRED_SERVERS.has(server.id)) {
477+
return { normalizedRootUri: null, originalRootUri: rootUri };
478+
}
479+
}
480+
481+
return { normalizedRootUri: rootUri, originalRootUri: rootUri };
482+
}
483+
484+
function contentUriToFileUri(uri) {
485+
try {
486+
const parsed = Uri.parse(uri);
487+
if (!parsed || typeof parsed !== "object") return null;
488+
const { docId, rootUri, isFileUri } = parsed;
489+
if (!docId) return null;
490+
491+
if (isFileUri && rootUri) {
492+
return rootUri;
493+
}
494+
495+
const providerMatch =
496+
/^content:\/\/com\.((?![:<>"/\\|?*]).*)\.documents\//.exec(rootUri);
497+
const providerId = providerMatch ? providerMatch[1] : null;
498+
499+
let normalized = docId.trim();
500+
if (!normalized) return null;
501+
502+
switch (providerId) {
503+
case "foxdebug.acode":
504+
normalized = normalized.replace(/:+$/, "");
505+
if (!normalized) return null;
506+
if (normalized.startsWith("raw:/")) {
507+
normalized = normalized.slice(4);
508+
} else if (normalized.startsWith("raw:")) {
509+
normalized = normalized.slice(4);
510+
}
511+
if (!normalized.startsWith("/")) return null;
512+
return buildFileUri(normalized);
513+
case "android.externalstorage":
514+
normalized = normalized.replace(/:+$/, "");
515+
if (!normalized) return null;
516+
517+
if (normalized.startsWith("/")) {
518+
return buildFileUri(normalized);
519+
}
520+
521+
if (normalized.startsWith("raw:/")) {
522+
return buildFileUri(normalized.slice(4));
523+
}
524+
525+
if (normalized.startsWith("raw:")) {
526+
return buildFileUri(normalized.slice(4));
527+
}
528+
529+
const separator = normalized.indexOf(":");
530+
if (separator === -1) return null;
531+
532+
const root = normalized.slice(0, separator);
533+
const remainder = normalized.slice(separator + 1);
534+
if (!remainder) return null;
535+
536+
switch (root) {
537+
case "primary":
538+
return buildFileUri(`/storage/emulated/0/${remainder}`);
539+
default:
540+
if (/^[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}$/.test(root)) {
541+
return buildFileUri(`/storage/${root}/${remainder}`);
542+
}
543+
}
544+
return null;
545+
default:
546+
return null;
547+
}
548+
} catch (_) {
549+
return null;
550+
}
551+
}
552+
553+
function buildFileUri(pathname) {
554+
if (!pathname) return null;
555+
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
556+
const encoded = encodeURI(normalized).replace(/#/g, "%23");
557+
return `file://${encoded}`;
558+
}

0 commit comments

Comments
 (0)