How LSP Workspace Operations Function in oh-my-pi: Rename, References, and Code Actions
The oh-my-pi coding agent implements LSP workspace operations through a centralized dispatcher in packages/coding-agent/src/lsp/index.ts, enabling file rename detection, symbol reference queries, and batched code actions with optional preview modes.
The oh-my-pi (omp) coding agent provides comprehensive Language Server Protocol (LSP) integration to handle complex workspace-wide transformations. Located in packages/coding-agent/src/lsp/, the LSP module manages everything from file renames to code actions by communicating with configured language servers. Understanding how omp handles these LSP workspace operations reveals sophisticated batching and error handling mechanisms that ensure consistent, safe code transformations.
Handling File and Directory Renames via workspace/willRenameFiles
When you invoke the rename_file tool, omp validates source and destination paths, then calls enumerateRenamePairs() to construct the list of {oldUri, newUri} pairs that language servers expect for a workspace/willRenameFiles request. This function, found at [line 48 of index.ts](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/lsp/index.ts#L48), handles both single files and directories.
For directory renames, omp walks every regular file within the source tree and builds parallel URIs under the new root. To prevent performance degradation, the enumeration caps at MAX_RENAME_PAIRS = 1000 files. For each configured LSP server, the agent obtains (or creates) a client via getOrCreateClient() and dispatches the workspace/willRenameFiles request. The resulting WorkspaceEdit objects from all servers are merged, de-duplicated, and stored in the tool-call metadata.
Preview Mode vs. Direct Application
The rename operation supports a configurable apply parameter. When set to false, the rename enters preview mode: the merge step builds a human-readable description (Rename preview: … → …) and the file system remains untouched. When apply is omitted or truthy, omp executes fs.promises.rename() on the source path(s) only after the LSP edits have been applied, ensuring symbol references remain synchronized with the new file structure.
// Rename a single file (preview only)
await runtime.tool("rename_file", {
file: "src/old.ts",
new_name: "src/new.ts",
apply: false,
});
// Rename a directory and apply LSP edits
await runtime.tool("rename_file", {
file: "src/components",
new_name: "src/ui",
apply: true,
});
Querying Symbol References and Definitions
The core LSP dispatcher lives in LspTool.handleAction. For actions such as "references", "definition", "type_definition", and "implementation", the agent builds a request payload using the document URI and cursor position, then invokes the appropriate LSP method (textDocument/references, textDocument/definition, etc.).
Results are transformed into user-friendly text blocks via formatLocationWithContext() (lines 23-34 of the same file). If no results are returned, the tool emits "No references found" rather than empty output.
// Query for all references of a symbol
await runtime.tool("references", {
file: "src/util.ts",
line: 12,
col: 1,
});
Executing and Resolving Code Actions
When requesting a code action (action: "code_action"), the dispatcher sends a textDocument/codeAction request (see [line 2107 of index.ts](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/lsp/index.ts#L2107)). The raw list of CodeAction objects may contain unresolved actions containing only a title and kind.
The agent resolves these on-demand by calling textDocument/codeAction/resolve (line 2142) for each unresolved action. Resolved actions convert into WorkspaceEdit objects, merge with any pending LSP edits, and apply to the workspace when apply is true.
// Request a code-action at a cursor
await runtime.tool("code_action", {
file: "src/app.ts",
line: 42,
col: 5,
kind: "quickfix",
});
Batching Strategies for Atomic Workflows
To maintain workspace consistency, omp places LSP-related tools (edit, write, rename_file, code_action) in the LSP_BATCH_TOOLS set defined in [render-utils.ts](https://github.com/can1357/oh-my-pi/blob/main/packages/coding-agent/src/tools/render-utils.ts#L752). When streaming tool calls, the agent delays execution and keeps earlier LSP-tool calls in the batch until the last LSP-tool is observed.
This batching guarantees that inter-dependent edits—such as a rename that triggers a code action—see a consistent view of the workspace. All edits accumulate and merge into a single atomic operation.
Error Handling and Abort Signals
If a language server does not implement a particular method, isMethodNotFoundError() (lines 78-87 of index.ts) filters the error and records a user-facing note rather than aborting the entire operation. All LSP calls wrap their promises with throwIfAborted(signal), ensuring that user-initiated aborts (e.g., Ctrl-C) stop the batch safely without leaving the workspace in a partial state.
Summary
- Rename enumeration uses
enumerateRenamePairs()to build URI mappings, capped at 1000 pairs to prevent performance issues during directory renames. - LSP workspace operations are coordinated through
LspTool.handleAction(), supporting references, definitions, and implementation queries with contextual formatting. - Code actions are resolved via
textDocument/codeAction/resolvebefore converting to workspace edits, ensuring complete transformation data. - Request batching via
LSP_BATCH_TOOLSensures atomic execution of inter-dependent LSP operations. - Graceful degradation occurs when servers lack method support, with
isMethodNotFoundError()preventing cascading failures.
Frequently Asked Questions
How does oh-my-pi handle renaming directories containing thousands of files?
The enumerateRenamePairs() function imposes a hard limit of MAX_RENAME_PAIRS = 1000 files when walking directory trees. If a directory exceeds this limit, omp processes only the first 1000 regular files to prevent memory exhaustion and LSP request timeout issues, ensuring stable performance even on large codebases.
Can I preview LSP workspace edits before applying them to disk?
Yes. The rename_file and code_action tools accept an apply parameter. Setting apply: false enters preview mode, where omp generates a human-readable description of the proposed changes without executing fs.promises.rename() or writing file modifications. This allows verification of symbol renames and refactorings before committing changes.
How does oh-my-pi manage multiple simultaneous LSP tool calls?
omp utilizes the LSP_BATCH_TOOLS set defined in packages/coding-agent/src/tools/render-utils.ts to identify LSP-related operations. The system buffers these calls until all LSP tools in the current batch are collected, then dispatches them together. This ensures that dependent operations—such as a file rename followed by a code action—operate on a consistent workspace state.
What happens when a language server doesn't support workspace/willRenameFiles?
If a server returns a method-not-found error, isMethodNotFoundError() catches the exception (lines 78-87 of index.ts) and records a note in the tool output rather than failing the entire rename operation. The file system rename proceeds normally, though without automatic import updates from that specific server.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →