Skip to content
Merged
7 changes: 6 additions & 1 deletion rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,15 @@ module.exports = (env, options) => {
resourceQuery: /raw/,
type: 'asset/source',
},
{
test: /\.(png|svg|jpg|jpeg|ico|webp)(\?.*)?$/,
resourceQuery: /inline/,
type: 'asset/inline',
},
// Asset files
{
test: /\.(png|svg|jpg|jpeg|ico|ttf|webp|eot|woff|webm|mp4|wav)(\?.*)?$/,
resourceQuery: { not: [/raw/] },
resourceQuery: { not: [/raw/, /inline/] },
type: 'asset/resource',
},
// Regular CSS/SCSS files
Expand Down
7 changes: 7 additions & 0 deletions src/cm/lsp/clientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Url from "utils/Url";
import { clearDiagnosticsEffect } from "./diagnostics";
import { supportsBuiltinFormatting } from "./formattingSupport";
import { inlayHintsExtension } from "./inlayHints";
import { addLspLog } from "./logs";
import { acodeRenameKeymap } from "./rename";
import { selectRuntimeProvider } from "./runtimeProviders";
import serverRegistry from "./serverRegistry";
Expand Down Expand Up @@ -267,6 +268,7 @@ interface ResolvedRuntimeTarget {
}

interface ExtendedLSPClient extends LSPClient {
__acodeServerId?: string;
__acodeLoggedInfo?: boolean;
}

Expand Down Expand Up @@ -689,6 +691,7 @@ export class LspClientManager {
level = "info";
}
const logFn = console[level] ?? console.info;
addLspLog(server.id, level === "log" ? "info" : level, message);
logFn(`[LSP:${server.id}] ${message}`);
return true;
},
Expand Down Expand Up @@ -716,6 +719,7 @@ export class LspClientManager {
icon: type === 1 ? "error" : "warningreport_problem",
type: type === 1 ? "error" : "warning",
});
addLspLog(server.id, type === 1 ? "error" : "warn", message);
logLspInfo(`[LSP:${server.id}] ${message}`);
return true;
}
Expand All @@ -728,6 +732,7 @@ export class LspClientManager {
icon: type === 4 ? "autorenew" : "info",
duration: 5000,
});
addLspLog(server.id, "info", message);
logLspInfo(`[LSP:${server.id}] ${message}`);
return true;
},
Expand Down Expand Up @@ -884,6 +889,7 @@ export class LspClientManager {
);
await transportHandle.ready;
client = new LSPClient(clientConfig) as ExtendedLSPClient;
client.__acodeServerId = server.id;
connectClient(client, transportHandle.transport, initializationOptions);
await client.initializing;
if (!client.__acodeLoggedInfo) {
Expand All @@ -906,6 +912,7 @@ export class LspClientManager {
);
}
logLspInfo(`[LSP:${server.id}] initialized`);
addLspLog(server.id, "info", "Initialized");
client.__acodeLoggedInfo = true;
}
} catch (error) {
Expand Down
10 changes: 10 additions & 0 deletions src/cm/lsp/codeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
WorkspaceEdit,
} from "vscode-languageserver-types";
import type { Position, Range } from "./types";
import { addLspLogFor } from "./logs";
import type AcodeWorkspace from "./workspace";

type CodeActionResponse = (CodeAction | Command)[] | null;
Expand Down Expand Up @@ -116,6 +117,7 @@ async function resolveCodeAction(
);
return resolved ?? action;
} catch (error) {
addLspLogFor(plugin, "warn", "Code action resolve failed", error);
console.warn("[LSP:CodeAction] Failed to resolve:", error);
return action;
}
Expand All @@ -138,6 +140,7 @@ async function executeCommand(
// -32601 = Method not implemented (expected for some LSP servers)
const lspError = error as { code?: number };
if (lspError?.code !== -32601) {
addLspLogFor(plugin, "warn", "Code action command execution failed", error);
console.warn("[LSP:CodeAction] Command execution failed:", error);
}
return false;
Expand Down Expand Up @@ -173,6 +176,11 @@ async function applyChangesToFile(

const displayedView = await workspace.displayFile(uri);
if (!displayedView?.state?.doc) {
addLspLogFor(
workspace.client,
"warn",
`Code action could not open file: ${uri}`,
);
console.warn(`[LSP:CodeAction] Could not open file: ${uri}`);
return false;
}
Expand Down Expand Up @@ -326,6 +334,7 @@ export async function fetchCodeActions(

return items;
} catch (error) {
addLspLogFor(plugin, "error", "Code action fetch failed", error);
console.error("[LSP:CodeAction] Failed to fetch:", error);
return [];
}
Expand All @@ -349,6 +358,7 @@ export async function executeCodeAction(
// Handle CodeAction
return applyCodeAction(view, item.action);
} catch (error) {
addLspLogFor(plugin, "error", "Code action execution failed", error);
console.error("[LSP:CodeAction] Failed to execute:", error);
return false;
}
Expand Down
32 changes: 32 additions & 0 deletions src/cm/lsp/connectionState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import serverRegistry from "./serverRegistry";

function getCurrentFileLanguage() {
try {
const file = window.editorManager?.activeFile;
if (!file || file.type !== "editor") return null;
return file.currentMode?.toLowerCase() || null;
} catch {
return null;
}
}

function getServersForCurrentFile() {
const language = getCurrentFileLanguage();
if (!language) return [];

try {
return serverRegistry.getServersForLanguage(language);
} catch {
return [];
}
}

function hasConnectedServers() {
return getServersForCurrentFile().length > 0;
}

export {
getCurrentFileLanguage,
getServersForCurrentFile,
hasConnectedServers,
};
8 changes: 8 additions & 0 deletions src/cm/lsp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export {
inlayHintsEditorExtension,
inlayHintsExtension,
} from "./inlayHints";
export {
addLspLog,
clearLspLogs,
getLspLogs,
onLspLog,
type LspLogEntry,
type LspLogLevel,
} from "./logs";
export {
closeReferencesPanel,
findAllReferences,
Expand Down
116 changes: 116 additions & 0 deletions src/cm/lsp/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
export type LspLogLevel = "debug" | "info" | "log" | "warn" | "error" | "stderr";

export interface LspLogEntry {
timestamp: Date;
level: LspLogLevel;
message: string;
details?: unknown;
}

const MAX_LOGS = 200;
const logsByServer = new Map<string, LspLogEntry[]>();
const listeners = new Set<(serverId: string, entry: LspLogEntry) => void>();
const IGNORED_LOG_PATTERNS = [
/\$\/progress\b/i,
/\bProgress:/i,
/\bwindow\/workDoneProgress\/create\b/i,
/\bAuto-responded to window\/workDoneProgress\/create\b/i,
];

function stripAnsi(value: string): string {
return value.replace(/\x1b\[[0-9;]*m/g, "");
}

function normalizeMessage(message: unknown): string {
let text: string;
if (typeof message === "string") {
text = message;
} else if (message instanceof Error) {
text = message.message;
} else {
try {
text = JSON.stringify(message);
} catch {
text = String(message);
}
}
return stripAnsi(String(text || ""))
.replace(/\[LSP:[^\]]+\]\s*/g, "")
.replace(/\[LSP-STDERR:[^\]]+\]\s*/g, "")
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\s*/g, "")
.replace(/\s*(INFO|WARN|ERROR|DEBUG|TRACE)\s+/gi, "")
.replace(/[a-z_]+::[a-z_]+:\s*/gi, "")
.trim();
}

function shouldIgnoreLog(message: string): boolean {
return IGNORED_LOG_PATTERNS.some((pattern) => pattern.test(message));
}

export function addLspLog(
serverId: string,
level: LspLogLevel,
message: unknown,
details?: unknown,
): void {
const id = String(serverId || "").trim();
if (!id) return;

const normalized = normalizeMessage(message);
if (!normalized || shouldIgnoreLog(normalized)) return;

const logs = logsByServer.get(id) || [];
const entry: LspLogEntry = {
timestamp: new Date(),
level,
message: normalized,
details,
};
logs.push(entry);
if (logs.length > MAX_LOGS) logs.splice(0, logs.length - MAX_LOGS);
logsByServer.set(id, logs);
listeners.forEach((listener) => listener(id, entry));
}

export function getLspLogServerId(source: unknown): string | null {
const client =
source &&
typeof source === "object" &&
Object.prototype.hasOwnProperty.call(source, "client")
? (source as { client?: unknown }).client
: source;
const metadata = client as
| { __acodeServerId?: unknown }
| null
| undefined;
const serverId = metadata?.__acodeServerId;
return typeof serverId === "string" && serverId.trim()
? serverId.trim()
: null;
}

export function addLspLogFor(
source: unknown,
level: LspLogLevel,
message: unknown,
details?: unknown,
): void {
const serverId = getLspLogServerId(source);
if (!serverId) return;
addLspLog(serverId, level, message, details);
}

export function getLspLogs(serverId: string): LspLogEntry[] {
return logsByServer.get(String(serverId || "").trim()) || [];
}

export function clearLspLogs(serverId: string): void {
logsByServer.delete(String(serverId || "").trim());
}

export function onLspLog(
listener: (serverId: string, entry: LspLogEntry) => void,
): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}
8 changes: 8 additions & 0 deletions src/cm/lsp/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@codemirror/view";
import prompt from "dialogs/prompt";
import type * as lsp from "vscode-languageserver-protocol";
import { addLspLogFor } from "./logs";
import type AcodeWorkspace from "./workspace";

interface RenameParams {
Expand Down Expand Up @@ -106,6 +107,7 @@ async function performRename(view: EditorView): Promise<boolean> {
}
}
} catch (error) {
addLspLogFor(plugin, "warn", "Rename prepare failed; using word", error);
console.warn("[LSP:Rename] prepareRename failed, using word:", error);
}
}
Expand Down Expand Up @@ -133,6 +135,7 @@ async function performRename(view: EditorView): Promise<boolean> {
try {
await doRename(view, String(newName), wordRange.from);
} catch (error) {
addLspLogFor(plugin, "error", "Rename failed", error);
console.error("[LSP:Rename] Rename failed:", error);
const errorMessage =
error instanceof Error ? error.message : "Failed to rename symbol";
Expand Down Expand Up @@ -176,6 +179,7 @@ async function applyChangesToFile(

const displayedView = await workspace.displayFile(uri);
if (!displayedView?.state?.doc) {
addLspLogFor(workspace.client, "warn", `Rename could not open file: ${uri}`);
console.warn(`[LSP:Rename] Could not open file: ${uri}`);
return false;
}
Expand Down Expand Up @@ -211,6 +215,7 @@ async function doRename(
);

if (!response) {
addLspLogFor(plugin, "info", "Rename returned no changes");
console.info("[LSP:Rename] No changes returned from server");
return;
}
Expand Down Expand Up @@ -255,10 +260,13 @@ async function doRename(
console.info(
`[LSP:Rename] Renamed to "${newName}" in ${filesChanged} file(s)`,
);
addLspLogFor(plugin, "info", `Renamed to "${newName}" in ${filesChanged} file(s)`);
}

export const renameSymbol: Command = (view) => {
performRename(view).catch((error) => {
const plugin = LSPPlugin.get(view);
addLspLogFor(plugin, "error", "Rename command failed", error);
console.error("[LSP:Rename] Rename command failed:", error);
});
return true;
Expand Down
Loading