Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
acf2a16
fix(lsp): restore initial workspace configuration notification
gat0sy Jul 29, 2026
568163a
dev: Reimplementing applyWorkEdit so formatter and some codeAction work
gat0sy Jul 31, 2026
d5c8a34
dev: implemented go-to lsp menu to allow go-to actions to be used
gat0sy Aug 1, 2026
d3c542a
dev: checkpoint sftp & termux forward and backward uri translation fo…
gat0sy Aug 1, 2026
e7ff47a
dev: rooturi resolved extended and fixed for sftp type of storage - lsp
gat0sy Aug 2, 2026
4e92ce1
dev: custom lsp support patches implemententation
gat0sy Aug 2, 2026
cb2e42f
ressources: adding a for acode-ls installing and patch application fo…
gat0sy Aug 2, 2026
9474b59
fix: implenting symnbol rename accros workspace - LSP
gat0sy Aug 3, 2026
50095e2
fix: implementation of a handler for FileUriExposedException type of …
gat0sy Aug 3, 2026
f89d570
dev:changed custom setup wizard into a form PART 1
gat0sy Aug 3, 2026
64a57db
fix: removal of termux supports from the core lsp feature TODO:implem…
gat0sy Aug 3, 2026
6f689c2
CI-Fix: refactoring transport.ts and tooltipextension to satisfy some…
gat0sy Aug 4, 2026
5a10e5a
fix: Removing testing scripts not belonging in the repo anyway
gat0sy Aug 4, 2026
93a26d1
safety-commit after conflict appearance
gat0sy Aug 4, 2026
3eb6c02
Merge remote-tracking branch 'upstream/main' into feat/lsp-improvements
gat0sy Aug 5, 2026
a599f14
merge-fix: fixing conflicts while mainting the fork's core contribution
gat0sy Aug 5, 2026
8ac7dbb
fix(lsp): restore safe position mapping and fix merge regressions fro…
gat0sy Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12,003 changes: 2,865 additions & 9,138 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
"com.foxdebug.acode.rk.customtabs": {},
"cordova-plugin-system": {},
"cordova-plugin-crashhandler": {},
"cordova-plugin-advanced-http": {},
"cordova-plugin-advanced-http": {
"ANDROIDBLACKLISTSECURESOCKETPROTOCOLS": "SSLv3,TLSv1"
},
"cordova-plugin-acode-webview": {}
},
"platforms": [
Expand Down
210 changes: 138 additions & 72 deletions src/cm/lsp/clientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
serverCompletion,
serverDiagnostics,
} from "@codemirror/lsp-client";
import { EditorState, Extension, Facet, MapMode } from "@codemirror/state";
//import { EditorState, Extension, Facet, MapMode, Text } from "@codemirror/state";
import { EditorState, Extension, Facet } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import lspStatusBar from "components/lspStatusBar";
import notificationManager from "lib/notificationManager";
Expand Down Expand Up @@ -52,6 +53,10 @@ import type {
Transport,
} from "./types";
import AcodeWorkspace from "./workspace";
//new
import { applyTextEdits, safeLspPositionToOffset } from "./textEditUtils";

const LSP_IDLE_GRACE_MS = 45_000; // grace period before a client with no open files is actually disposed

export const lspCompletionEnabled = Facet.define<boolean, boolean>({
// File-level marker used by the autocomplete override path. If any attached
Expand Down Expand Up @@ -131,11 +136,17 @@ function isSettingsOrKeybindingsFile(
);
}

function isVerboseLspLoggingEnabled(): boolean {
return true; // TEMP: force verbose LSP logging for debugging
}

/*
function isVerboseLspLoggingEnabled(): boolean {
const buildInfo = (globalThis as { BuildInfo?: { debug?: boolean } })
.BuildInfo;
return !!buildInfo?.debug;
}
*/

function logLspInfo(...args: unknown[]): void {
if (!isVerboseLspLoggingEnabled()) return;
Expand Down Expand Up @@ -183,9 +194,14 @@ function connectClient(
initializationOptions?: Record<string, unknown>,
rootUri?: string | null,
): void {
const hasInitializationOptions =
!!initializationOptions && Object.keys(initializationOptions).length > 0;
if (!hasInitializationOptions && !rootUri) {
const workspaceFolders = rootUri
? [{ uri: rootUri, name: deriveFolderName(rootUri) }]
: undefined;

if (
(!initializationOptions || !Object.keys(initializationOptions).length) &&
!workspaceFolders
) {
client.connect(transport);
return;
}
Expand All @@ -205,14 +221,8 @@ function connectClient(
if (method === "initialize" && isPlainObject(params)) {
params = {
...params,
...(hasInitializationOptions ? { initializationOptions } : {}),
...(rootUri
? {
workspaceFolders: [
{ uri: rootUri, name: workspaceName(rootUri) },
],
}
: {}),
...(initializationOptions ? { initializationOptions } : {}),
...(workspaceFolders ? { workspaceFolders } : {}),
} as Params;
}
return originalRequestInner<Params, Result>(method, params, mapped);
Expand All @@ -225,13 +235,14 @@ function connectClient(
}
}

function workspaceName(rootUri: string): string {
const trimmed = rootUri.replace(/\/+$/, "");
const encodedName = trimmed.slice(trimmed.lastIndexOf("/") + 1);
function deriveFolderName(uri: string): string {
try {
return decodeURIComponent(encodedName) || "workspace";
const decoded = decodeURIComponent(uri);
const trimmed = decoded.replace(/\/+$/, "");
const segments = trimmed.split("/").filter(Boolean);
return segments[segments.length - 1] || decoded;
} catch {
return encodedName || "workspace";
return uri;
}
}

Expand Down Expand Up @@ -524,6 +535,25 @@ export class LspClientManager {
const plugin = LSPPlugin.get(view, state.client);
if (!plugin) continue;
plugin.client.sync();
console.log(
"Current length",
view.state.doc.length,
);

console.log(
"Synced length",
plugin.syncedDoc.length,
);

console.log(
"Current doc\n",
view.state.doc.toString(),
);

console.log(
"Synced doc\n",
plugin.syncedDoc.toString(),
);
Comment on lines +548 to +556

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Formatting logs entire documents

Every LSP formatting request now writes both the current and synchronized document text to the console, exposing source code or credentials in collected WebView logs and adding avoidable overhead for large files.

Suggested change
console.log(
"Current doc\n",
view.state.doc.toString(),
);
console.log(
"Synced doc\n",
plugin.syncedDoc.toString(),
);

Knowledge Base Used: LSP Integration

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

const edits = await state.client.request<
{ textDocument: { uri: string }; options: FormattingOptions },
TextEdit[] | null
Expand Down Expand Up @@ -797,8 +827,17 @@ export class LspClientManager {
},
workspace: {
configuration: true,
applyEdit: true,
workspaceFolders: true,
},
textDocument: {
codeAction: {
dataSupport: true,
resolveSupport: {
properties: ["edit"],
},
},
},
},
};

Expand Down Expand Up @@ -1019,6 +1058,7 @@ export class LspClientManager {
serverId: server.id,
allowNonTerminalWorkspace:
this.options.allowNonTerminalWorkspace === true,
//resolveViewForUri: (uri: string) => clientState.getAttachedView?.(uri) ?? null,
};
const connection = await runtimeProvider.start(server, runtimeContext);
const connectionDispose = connection.dispose;
Expand All @@ -1045,14 +1085,22 @@ export class LspClientManager {
client = new LSPClient(clientConfig) as ExtendedLSPClient;
client.__acodeServerId = server.id;
connectClient(
client,
transportHandle.transport,
initializationOptions,
scope === "workspace" && server.useWorkspaceFolders
? null
: normalizedRootUri,
);
client,
transportHandle.transport,
initializationOptions,
normalizedRootUri,
);
await waitForInitialization(client.initializing, signal, server.id);
// New: push config the same way ALC always did
console.log("### CONFIG PUSH ATTEMPT ###");
// Fire after "initialized" — reuse initializationOptions as the config payload,
// since that's the field you actually populate via the wizard
transportHandle.transport.send(JSON.stringify({
jsonrpc: "2.0",
method: "workspace/didChangeConfiguration",
params: { settings: server.initializationOptions ?? {} },
}));
console.log("### CONFIG PUSH SENT ###");
if (!client.__acodeLoggedInfo) {
// Log root URI info to console
if (normalizedRootUri) {
Expand Down Expand Up @@ -1135,8 +1183,9 @@ export class LspClientManager {
const uriAliases = new Map<string, string>();
const effectiveRoot = normalizedRootUri ?? originalRootUri ?? null;
let disposed = false;
let idleTimer: ReturnType<typeof setTimeout> | undefined;

const attach = (
/* const attach = (
uri: string,
view: EditorView,
aliases: string[] = [],
Expand All @@ -1151,7 +1200,27 @@ export class LspClientManager {
}
const suffix = effectiveRoot ? ` (root ${effectiveRoot})` : "";
logLspInfo(`[LSP:${server.id}] attached to ${uri}${suffix}`);
};
}; */
const attach = (
uri: string,
view: EditorView,
aliases: string[] = [],
): void => {
if (idleTimer) {
clearTimeout(idleTimer);
idleTimer = undefined;
}
const existing = fileRefs.get(uri) ?? new Set();
existing.add(view);
fileRefs.set(uri, existing);
uriAliases.set(uri, uri);
for (const alias of aliases) {
if (!alias || alias === uri) continue;
uriAliases.set(alias, uri);
}
const suffix = effectiveRoot ? ` (root ${effectiveRoot})` : "";
logLspInfo(`[LSP:${server.id}] attached to ${uri}${suffix}`);
};

const clearClientDiagnostics = (view: EditorView): void => {
try {
Expand All @@ -1164,6 +1233,10 @@ export class LspClientManager {
const dispose = async (): Promise<void> => {
if (disposed) return;
disposed = true;
if (idleTimer) {
clearTimeout(idleTimer);
idleTimer = undefined;
}
disposePullDiagnostics(client);
this.#clients.delete(key);
for (const views of fileRefs.values()) {
Expand Down Expand Up @@ -1206,13 +1279,18 @@ export class LspClientManager {
}

if (!fileRefs.size) {
this.options.onClientIdle?.({
server,
client,
rootUri: effectiveRoot,
dispose,
});
}
if (idleTimer) clearTimeout(idleTimer);
idleTimer = setTimeout(() => {
idleTimer = undefined;
if (fileRefs.size) return; // a file reattached during the grace window
this.options.onClientIdle?.({
server,
client,
rootUri: effectiveRoot,
dispose,
});
}, LSP_IDLE_GRACE_MS);
}
};

return {
Expand Down Expand Up @@ -1404,45 +1482,6 @@ interface Change {
insert: string;
}

function applyTextEdits(
plugin: LSPPlugin,
view: EditorView,
edits: TextEdit[],
): boolean {
const changes: Change[] = [];
for (const edit of edits) {
if (!edit?.range) continue;
let fromBase: number;
let toBase: number;
try {
fromBase = plugin.fromPosition(edit.range.start, plugin.syncedDoc);
toBase = plugin.fromPosition(edit.range.end, plugin.syncedDoc);
} catch (_) {
continue;
}
const fromResult = plugin.unsyncedChanges.mapPos(
fromBase,
1,
MapMode.TrackDel,
);
const toResult = plugin.unsyncedChanges.mapPos(
toBase,
-1,
MapMode.TrackDel,
);
if (fromResult == null || toResult == null) continue;
const insert =
typeof edit.newText === "string"
? edit.newText.replace(/\r\n/g, "\n")
: "";
changes.push({ from: fromResult, to: toResult, insert });
}
if (!changes.length) return false;
changes.sort((a, b) => a.from - b.from || a.to - b.to);
view.dispatch({ changes });
return true;
}

function buildFormattingOptions(
view: EditorView,
overrides: FormattingOptions = {},
Expand Down Expand Up @@ -1482,6 +1521,7 @@ function resolveIndentWidth(unit: string): number {
return width || 4;
}


const defaultManager = new LspClientManager();

export default defaultManager;
Expand All @@ -1500,6 +1540,15 @@ function normalizeRootUriForServer(
if (scheme === "file") {
return { normalizedRootUri: rootUri, originalRootUri: rootUri };
}

// sftp roots: strip to the bare remote path
if (scheme === "sftp") {
const fileUri = sftpUriToFileUri(rootUri);
if (fileUri) {
return { normalizedRootUri: fileUri, originalRootUri: rootUri };
}
return { normalizedRootUri: null, originalRootUri: rootUri };
}

// Try to convert content:// URIs to file:// URIs
if (scheme === "content") {
Expand All @@ -1525,6 +1574,12 @@ function normalizeDocumentUri(uri: string | null | undefined): string | null {
if (scheme === "file" || scheme === "untitled") {
return uri;
}

// sftp documents: strip to the bare remote path
if (scheme === "sftp") {
return sftpUriToFileUri(uri);
}


// Convert content:// URIs to file:// URIs
if (scheme === "content") {
Expand Down Expand Up @@ -1611,6 +1666,17 @@ function contentUriToFileUri(uri: string): string | null {
}
}

function sftpUriToFileUri(uri: string): string | null {
// acode-ls and the LSP process it spawns run on the same remote host
// reached via this SFTP connection, so the server needs only the bare
// remote path — no scheme, host, port, or credentials.
const match = /^sftp:\/\/[^/]*(\/.*)$/.exec(uri);
if (!match) return null;
const path = match[1].split("?")[0];
if (!path) return null;
return buildFileUri(path);
}

function buildFileUri(pathname: string): string | null {
if (!pathname) return null;
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
Expand Down
Loading