Skip to content

Feat/lsp improvements - #2632

Open
gat0sy wants to merge 16 commits into
Acode-Foundation:mainfrom
gat0sy:feat/lsp-improvements
Open

Feat/lsp improvements#2632
gat0sy wants to merge 16 commits into
Acode-Foundation:mainfrom
gat0sy:feat/lsp-improvements

Conversation

@gat0sy

@gat0sy gat0sy commented Aug 4, 2026

Copy link
Copy Markdown

Summary

Improves Acode's built-in CM6 LSP client with fixes for URI handling, workspace edits, client capabilities, session management, and custom server configuration. All changes are client-side; no external tooling or server scripts are included.

Changes

Position & text edit handling

  • Fixed "applyTextEdits" discarding formatter/code-action results when an LSP server returns "end.line == doc.lines" (the standard end-of-document convention).
  • Added "safeLspPositionToOffset" to safely clamp document positions without throwing.

URI resolution

  • Fixed forward URI translation for Acode's internal storage provider ("foxdebug.acode" / "foxdebug.acodefree") so internal-storage projects are correctly mapped to "file://" URIs.
  • Added reverse URI translation (LSP "file://" → Acode URI) for internal storage, allowing features such as Go to Definition and tooltip file links to work correctly.
  • Fixed SFTP URI translation in both directions so remote workspaces resolve consistently.

Client capabilities

  • Declared missing client capabilities ("workspace.applyEdit", "workspace.workspaceFolders", "textDocument.codeAction.resolveSupport") so servers such as jdtls and Dart can advertise and execute workspace edits and code actions.

Workspace edits

  • Generalized workspace/applyEdit handling in transport.ts so server-initiated workspace edits can be applied across multiple files through AcodeWorkspace, with per-file success/failure reporting.

LSP actions

  • Added a dedicated LSP actions menu exposing existing and supported LSP features such as Go to Definition, Show References, Rename Symbol, and Code Actions.
  • Automatically executes code actions when only a single action is available.

Session lifecycle

  • Added a 45-second idle grace period before disposing inactive LSP clients, avoiding unnecessary restarts while switching files.

Custom server configuration

  • Replaced the multi-step "add_custom_server" wizard with pre-filled prompts.
  • Added an Edit action for custom servers so transport, launcher settings, and language IDs can be modified without recreating the server.

Tooltip file links

  • Fixed an Android "FileUriExposedException" when opening "file://" links from hover or signature-help tooltips. Links now open directly inside Acode at the correct location if accessible by the latter.

Testing

  • "biome check --write" ✓
  • Tested with jdtls, Dart Analysis Server, phpactor, pylsp, and jedi-language-server over WebSocket transport.
  • Verified on shared storage, internal storage, and SFTP workspaces.

@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR expands the built-in CodeMirror LSP client with URI translation, server-driven workspace edits, definition/reference actions, richer capabilities, delayed client disposal, tooltip navigation, and editable custom-server settings. The workspace-edit and SFTP paths contain correctness defects, and the new code currently introduces a TypeScript build error.

  • Adds multi-file workspace/applyEdit handling and shared safe text-edit conversion.
  • Adds LSP navigation/actions and diagnostic-aware code-action requests.
  • Adds SAF/SFTP URI translation and in-app tooltip link handling.
  • Extends client capabilities, workspace initialization, and idle-session lifecycle.
  • Reworks custom LSP server creation and editing.

Confidence Score: 4/5

The PR should not merge until the TypeScript error and the workspace-edit and SFTP correctness failures are fixed.

The changed code references a missing type, can silently drop or falsely acknowledge portions of server-requested workspace edits, and removes authentication parameters while resolving child files in SFTP workspaces.

Files Needing Attention: src/cm/lsp/codeActions.ts, src/cm/lsp/transport.ts, src/components/referencesPanel/utils.js, src/cm/lsp/clientManager.ts

Important Files Changed

Filename Overview
src/cm/lsp/clientManager.ts Adds capabilities, initialization data, URI normalization, and idle disposal, but also logs complete document contents during formatting.
src/cm/lsp/codeActions.ts Adds diagnostic-aware code actions but references an undeclared type that breaks TypeScript checking.
src/cm/lsp/transport.ts Adds server-driven workspace edits, but duplicate same-file edit batches are lost and partial application is incorrectly reported as success.
src/components/referencesPanel/utils.js Adds reverse SAF/SFTP URI mapping, but child SFTP targets lose query-based authentication parameters.
src/cm/lsp/textEditUtils.ts Centralizes position clamping and edit mapping, including end-of-document positions.
src/cm/lsp/tooltipExtensions.ts Intercepts tooltip file links and opens accessible files inside Acode.
src/settings/lspSettings.js Reworks custom-server creation and adds editing of saved server definitions.

Sequence Diagram

sequenceDiagram
  participant Server as Language Server
  participant Transport as transport.ts
  participant Workspace as AcodeWorkspace
  participant Editor as Target EditorView
  Server->>Transport: workspace/applyEdit
  Transport->>Transport: normalize changes by URI
  loop Each target URI
    Transport->>Workspace: getFile / displayFile
    Workspace-->>Transport: EditorView
    Transport->>Editor: applyTextEdits
  end
  Transport-->>Server: ApplyWorkspaceEditResponse
Loading

Reviews (1): Last reviewed commit: "fix: Removing testing scripts not belong..." | Re-trigger Greptile

Comment thread src/cm/lsp/codeActions.ts Outdated
Comment on lines +62 to +65
function comparePositions(a: LspPosition, b: LspPosition): number {
if (a.line !== b.line) return a.line - b.line;
return a.character - b.character;
}

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.

P1 Undefined position type breaks build

TypeScript checks this exported LSP module with no declaration or import for LspPosition, causing Cannot find name 'LspPosition' and preventing the project from building.

Suggested change
function comparePositions(a: LspPosition, b: LspPosition): number {
if (a.line !== b.line) return a.line - b.line;
return a.character - b.character;
}
function comparePositions(a: Position, b: Position): number {
if (a.line !== b.line) return a.line - b.line;
return a.character - b.character;
}

Knowledge Base Used: LSP Integration

Comment thread src/cm/lsp/transport.ts
Comment on lines +178 to +181
Object.fromEntries(
(edit.documentChanges ?? [])
.filter((c): c is { textDocument: { uri: string }; edits: TextEdit[] } => "edits" in c)
.map((c) => [c.textDocument.uri, c.edits]),

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.

P1 Duplicate edit batches are discarded

When documentChanges contains multiple ordered entries for the same URI, Object.fromEntries retains only the final entry, causing earlier portions of a rename or refactor to be silently omitted while the request is reported as applied.

Knowledge Base Used: LSP Integration

Comment thread src/cm/lsp/transport.ts
Comment on lines +235 to +240
if (failures.length) {
return {
applied: true,
failureReason: `Applied to ${appliedCount} file(s); failed: ${failures.join(", ")}`,
};
}

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.

P1 Partial workspace edits report success

When at least one target file is edited but another cannot be opened or updated, this branch returns applied: true, causing the server to proceed as though the entire rename or refactor completed while source files remain inconsistent.

Suggested change
if (failures.length) {
return {
applied: true,
failureReason: `Applied to ${appliedCount} file(s); failed: ${failures.join(", ")}`,
};
}
if (failures.length) {
return {
applied: false,
failureReason: `Applied to ${appliedCount} file(s); failed: ${failures.join(", ")}`,
};
}

Knowledge Base Used: LSP Integration

Comment thread src/components/referencesPanel/utils.js Outdated
Comment on lines +268 to +272
const base = rootUrl.slice(
0,
rootUrl.indexOf(rootPath) + rootPath.length,
);
return base + suffix;

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.

P1 SFTP child links lose authentication

When a definition or reference targets a child of an SFTP workspace configured with keyFile or passPhrase query parameters, slicing the root URL here drops those parameters, causing the reconstructed file URI to lose the credentials needed to open the remote target.

Knowledge Base Used: LSP Integration

Comment on lines +535 to +543
console.log(
"Current doc\n",
view.state.doc.toString(),
);

console.log(
"Synced doc\n",
plugin.syncedDoc.toString(),
);

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!

gat0sy added 13 commits August 4, 2026 20:13
Add  workspace/didChangeConfiguration notification after the LSP initialization handshake but was missing from theCodeMirror-based LSP client.

Send the notification through Acode's existing LSP transport afterclient.initializing resolves, ensuring the server has completed theinitialization handshake before receiving the configuration.

This restores the previous ALC behavior and ensures external languageservers receive their initial workspace configuration immediately afterconnecting.
Disclaimer, these fixes focus more on the custom server configation rather than the built LSP, so that people can use it through termux or even port forward from a server running an lsp ( As I used to do on my phone with Acode-lanuage-client before the CM6 migration)
There was a mismatch with the LSP internal line count and the editor lines count, it may have been caused by how codemirror itself impleent it. The fix was to reimplement a safeOffsetLsp method to clamp to a line count both the lsp and editor can agree on.
This allowed pylsp through autopep8 to do the formatting successfully.

But then there was another issue, codeAction also needed that workedit, and the latter were in the transport.ts which is imported by clientManager.
To avoid the cycling situation, applywork edit, and the newly added safeoffset were extarcted into a new textUtils.ts file so both can import it on their own.

It's my first actual commit, so I apologize for the format... :(
DISCLAIMER: this work is mostly to add compatibility for custom lsp used via termux or any other terminal, through acode-ls, which was used with Acode-language-client
added a menu for go-to functions and remapped the code-action button to that menu.
codeactions are now included in that submenu. a lot of items have no code actions,considering then to make the "show code action" buttton grayed ( in other words if list of action = 0).
go-to works following acode current file system design.
next and final commit, fixing uri translation so lsp can work with termux, ftp and sftp ( like I used to work with Acode Language client).
But under one condition, indexing lsp must be running on the same device as where the workdir

example, you use sftp, portforward. I don't think it is worth having 1 cross device lsp, that's mind bending and even complicated to use anyway.
I have been using Acode for a while now and it helped me go through a full semester of college without a laptop when I just had a phone and a homeserver.

We are using a patched version of Acode-ls by Thraize to as a websocket pipe.
That way people can use it on termux or even portforward it via ssh from their server where the lsp runs.

- Implemented an LSP menu:
before the toolbox menu was going directly to code action.
with the redesign the buttins opens a dialog allowing to use go-to like functions,
or then press 'show code actions' to then access the code action menu,
which btw auto execute if there is only one action.

- session and hanging lsp handling:
if all the files of a project are closed, there is a 45 sec timer after what the lsp closes.
It is made so to leave some time for the user if they are just swithing files while also avoiding keeping an lsp active
for an uncessary amount of time.

- uri translation fix:
this one is my favorite, previously it was a heachache to to use lsp with anything that isn’t in local shared storage.
it is now possible to use the lsp even if the workdir is in termux, no setup required, it has been implemented at the client level, fully exploiting Acode internal file system features.
SFTP support has also been added. Now lsp should work if a the work dir is an sftp repo.
note that it has not been implemented for ftp, it should be trivial now but sticking to sftp might be better for serious work anyway.
The consequences of that translation fix is also that it allows go-to actions to be if the work dir is in termux or sftp along local shared storage.
Sdcard may or may not be supported, i haven’t taken that in account honestly.

- codeActions & formatting support:
this was one of the first fixes, code action now works accross the different workspaces type mentionned ("local","termux" and "sftp")
however code action in currently active and opened has been prioritize. Need to investigate if we can do code-actions that work accros multiple files.
In theory it should be a feature as well, I may have a look at it later, this the reason why I haven’t exposed "rename symbol" in the lsp menu.

I think that's about it. I really hope that you will consider these fixes as it now allows the usage of eithet a local lsp on termux or a remote one, as long as the lsp and the project are on the same device with no reachabilitied issue.

Hopefully you will consider my contribution to this amazing mobile editor that Acode is.

I never wrote a commit message this long, so I apologizes to whoever finds any incoveniences to read it.

Thanks the Thraize as well, if he had never done Acode-Language-Client and the acode-ls companion, all of this would have never been possible.
It contributed a lot at teaching me how lsp works and how to steer claude for decisives patches
@gat0sy
gat0sy force-pushed the feat/lsp-improvements branch from cd5e211 to 5a10e5a Compare August 4, 2026 20:21
@gat0sy

gat0sy commented Aug 4, 2026

Copy link
Copy Markdown
Author

@deadlyjack Separately from this PR, I wanted to share an idea I have been experimenting with for SFTP + LSP support.

The way I got SFTP projects working reliably was by running the LSP on the same machine as the files, then forwarding the WebSocket connection back to Acode through SSH. This avoids a lot of URI/path translation issues because the language server sees the real filesystem it is indexing.

The "acode-ls" script I shared earlier was not actually Termux-specific. It only had a Termux shebang because I used Termux as my testing environment when the built-in terminal was unavailable.

Failed to create terminal:
Executor.setProotDebug is not a function

The same concept works with Acode's own WebSocket LSP setup.

The idea would be to make this more seamless:

  • Detect when the opened workspace is an SFTP workspace.
  • Use the existing SSH credentials to open a port-forwarded session.
  • Check if the remote machine already has the WebSocket bridge/LSP runtime available.
  • If not, optionally offer to install it.
  • Start the remote LSP bridge automatically and connect Acode to it.

Then when the SFTP workspace is closed, the session can be stopped.

The goal is not to replace local LSPs, but to make heavy servers usable remotely. For example, jdtls is often too heavy for a phone, but running it on the same machine as the project worked perfectly for me: indexing, completion, code actions, imports, generation of getters/setters, rename, etc.

The general idea is: the LSP should run where the files are. Local projects use local servers, SFTP projects use remote servers through SSH forwarding.

I'm not sure if this fits Acode's roadmap, but I thought I would share the approach since SFTP support already exists and this could make large projects much more practical on mobile devices. Happy to discuss it or drop it if it is out of scope.

gat0sy added 3 commits August 4, 2026 22:30
note: Thanks to whoever fixed the access to the built in terminal, it is now usable!

i# Please enter the commit message for your changes. Lines starting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant