Skip to content

Commit cab25e4

Browse files
authored
refactor(lsp): simplify server management and installer flow (Acode-Foundation#1929)
1 parent 6b24251 commit cab25e4

21 files changed

+3000
-1095
lines changed

src/cm/lsp/api.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { defineBundle, defineServer, installers } from "./providerUtils";
2+
import {
3+
getServerBundle,
4+
listServerBundles,
5+
registerServerBundle,
6+
unregisterServerBundle,
7+
} from "./serverCatalog";
8+
import {
9+
getServer,
10+
getServersForLanguage,
11+
listServers,
12+
onRegistryChange,
13+
type RegisterServerOptions,
14+
registerServer,
15+
type ServerUpdater,
16+
unregisterServer,
17+
updateServer,
18+
} from "./serverRegistry";
19+
import type {
20+
LspServerBundle,
21+
LspServerDefinition,
22+
LspServerManifest,
23+
} from "./types";
24+
25+
export { defineBundle, defineServer, installers };
26+
27+
export type LspRegistrationEntry = LspServerManifest | LspServerBundle;
28+
29+
function isBundleEntry(entry: LspRegistrationEntry): entry is LspServerBundle {
30+
return typeof (entry as LspServerBundle)?.getServers === "function";
31+
}
32+
33+
export function register(
34+
entry: LspRegistrationEntry,
35+
options?: RegisterServerOptions & { replace?: boolean },
36+
): LspServerDefinition | LspServerBundle {
37+
if (isBundleEntry(entry)) {
38+
return registerServerBundle(entry, options);
39+
}
40+
41+
return registerServer(entry, options);
42+
}
43+
44+
export function upsert(
45+
entry: LspRegistrationEntry,
46+
): LspServerDefinition | LspServerBundle {
47+
return register(entry, { replace: true });
48+
}
49+
50+
export const servers = {
51+
get(id: string): LspServerDefinition | null {
52+
return getServer(id);
53+
},
54+
list(): LspServerDefinition[] {
55+
return listServers();
56+
},
57+
listForLanguage(
58+
languageId: string,
59+
options?: { includeDisabled?: boolean },
60+
): LspServerDefinition[] {
61+
return getServersForLanguage(languageId, options);
62+
},
63+
update(id: string, updater: ServerUpdater): LspServerDefinition | null {
64+
return updateServer(id, updater);
65+
},
66+
unregister(id: string): boolean {
67+
return unregisterServer(id);
68+
},
69+
onChange(listener: Parameters<typeof onRegistryChange>[0]): () => void {
70+
return onRegistryChange(listener);
71+
},
72+
};
73+
74+
export const bundles = {
75+
list(): LspServerBundle[] {
76+
return listServerBundles();
77+
},
78+
getForServer(id: string): LspServerBundle | null {
79+
return getServerBundle(id);
80+
},
81+
unregister(id: string): boolean {
82+
return unregisterServerBundle(id);
83+
},
84+
};
85+
86+
const lspApi = {
87+
defineServer,
88+
defineBundle,
89+
register,
90+
upsert,
91+
installers,
92+
servers,
93+
bundles,
94+
};
95+
96+
export default lspApi;

src/cm/lsp/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
export {
2+
bundles,
3+
default as lspApi,
4+
defineBundle,
5+
defineServer,
6+
installers,
7+
register,
8+
servers,
9+
upsert,
10+
} from "./api";
111
export { default as clientManager, LspClientManager } from "./clientManager";
212
export type { CodeActionItem } from "./codeActions";
313
export {

src/cm/lsp/installRuntime.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
function getExecutor(): Executor {
2+
const executor = (globalThis as unknown as { Executor?: Executor }).Executor;
3+
if (!executor) {
4+
throw new Error("Executor plugin is not available");
5+
}
6+
return executor;
7+
}
8+
9+
function getBackgroundExecutor(): Executor {
10+
const executor = getExecutor();
11+
return executor.BackgroundExecutor ?? executor;
12+
}
13+
14+
export function quoteArg(value: unknown): string {
15+
const str = String(value ?? "");
16+
if (!str.length) return "''";
17+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(str)) return str;
18+
return `'${str.replace(/'/g, "'\\''")}'`;
19+
}
20+
21+
export function formatCommand(
22+
command: string | string[] | null | undefined,
23+
): string {
24+
if (Array.isArray(command)) {
25+
return command.map((part) => quoteArg(part)).join(" ");
26+
}
27+
if (typeof command === "string") {
28+
return command.trim();
29+
}
30+
return "";
31+
}
32+
33+
function wrapShellCommand(command: string): string {
34+
const script = command.trim();
35+
return `sh -lc ${quoteArg(`set -e\n${script}`)}`;
36+
}
37+
38+
export async function runQuickCommand(command: string): Promise<string> {
39+
const wrapped = wrapShellCommand(command);
40+
return getBackgroundExecutor().execute(wrapped, true);
41+
}
42+
43+
export async function runForegroundCommand(command: string): Promise<string> {
44+
const wrapped = wrapShellCommand(command);
45+
return getExecutor().execute(wrapped, true);
46+
}

src/cm/lsp/installerUtils.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const ARCH_ALIASES = {
2+
aarch64: ["aarch64", "arm64", "arm64-v8a"],
3+
x86_64: ["x86_64", "amd64"],
4+
armv7: ["armv7", "armv7l", "armeabi-v7a"],
5+
} as const;
6+
7+
export type NormalizedArch = keyof typeof ARCH_ALIASES;
8+
9+
export function normalizeArchitecture(arch: string | null | undefined): string {
10+
const normalized = String(arch || "")
11+
.trim()
12+
.toLowerCase();
13+
14+
for (const [canonical, aliases] of Object.entries(ARCH_ALIASES)) {
15+
if (aliases.includes(normalized as never)) {
16+
return canonical;
17+
}
18+
}
19+
20+
return normalized;
21+
}
22+
23+
export function getArchitectureMatchers(
24+
assets: Record<string, string> | undefined | null,
25+
): Array<{ canonicalArch: string; aliases: string[]; asset: string }> {
26+
if (!assets || typeof assets !== "object") return [];
27+
28+
const resolved = new Map<string, { aliases: string[]; asset: string }>();
29+
for (const [rawArch, rawAsset] of Object.entries(assets)) {
30+
const asset = String(rawAsset || "").trim();
31+
if (!asset) continue;
32+
33+
const canonicalArch = normalizeArchitecture(rawArch);
34+
if (!canonicalArch) continue;
35+
36+
const aliases = (
37+
ARCH_ALIASES[canonicalArch as NormalizedArch] || [canonicalArch]
38+
).map((value) => String(value));
39+
resolved.set(canonicalArch, { aliases, asset });
40+
}
41+
42+
return Array.from(resolved.entries()).map(([canonicalArch, value]) => ({
43+
canonicalArch,
44+
aliases: value.aliases,
45+
asset: value.asset,
46+
}));
47+
}
48+
49+
export function buildShellArchCase(
50+
assets: Record<string, string> | undefined | null,
51+
quote: (value: unknown) => string,
52+
): string {
53+
return getArchitectureMatchers(assets)
54+
.map(
55+
({ aliases, asset }) =>
56+
`\t${aliases.join("|")}) ASSET=${quote(asset)} ;;`,
57+
)
58+
.join("\n");
59+
}

0 commit comments

Comments
 (0)