@ian-pascoe/pi-lsp

Configured language-server tools and post-edit diagnostics for Pi

Packages

Package details

extensionskill

Install @ian-pascoe/pi-lsp from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@ian-pascoe/pi-lsp
Package
@ian-pascoe/pi-lsp
Version
0.4.3
Published
Sep 13, 2026
Downloads
1,277/mo · 257/wk
Author
ianpascoe_
License
MIT
Types
extension, skill
Size
258.6 KB
Dependencies
3 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ],
  "skills": [
    "./skills"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@ian-pascoe/pi-lsp

Configured language-server tools and post-edit diagnostics for Pi.

Install

pi install npm:@ian-pascoe/pi-lsp
# or
pi install git:github.com/ian-pascoe/pi-extensions

For a local checkout, run pi -e ./packages/pi-lsp/src/index.ts.

Install every language-server executable separately. Pi LSP contains no server catalog or installer. It launches configured commands over stdio with the Pi process's environment and permissions.

Settings

Pi LSP reads only the lsp key from Pi's global settings.json and trusted project .pi/settings.json:

{
  "lsp": {
    "timeouts": {
      "initializeMs": 45000,
      "requestMs": 3000,
      "diagnosticsMs": 3000,
      "shutdownMs": 5000
    },
    "servers": {
      "typescript": {
        "command": "pnpm",
        "args": ["exec", "tsc", "--lsp", "--stdio"],
        "languages": [
          {
            "extensions": [".ts", ".mts", ".cts"],
            "languageId": "typescript"
          },
          {
            "extensions": [".tsx"],
            "languageId": "typescriptreact"
          }
        ],
        "requireRootMarker": true,
        "rootMarkers": ["tsconfig.json", "package.json", ".git"],
        "initializationOptions": {},
        "settings": {},
        "environment": {}
      }
    }
  }
}

Every field is optional except a Server Definition's non-empty command and languages, even when that definition is disabled. Each language needs a non-empty languageId and at least one extension or exact filename. Extensions include their leading period. rootMarkers are basename glob patterns; the nearest matching ancestor becomes the server root and Pi's working directory is the fallback. Set requireRootMarker to true to exclude the server for files without any matching ancestor; it defaults to false. A required empty rootMarkers list is invalid. Explicit requests naming an otherwise compatible excluded server report that its required root marker was not found.

Global and project timeouts merge by field. A project server replaces the complete global server with the same ID; set a project server to null to remove it. initializationOptions is sent only during initialization. settings is used for workspace/didChangeConfiguration and workspace/configuration. Environment strings override process.env; null removes a variable. Invalid server definitions and timeout fields are quarantined individually and remain visible through status; unrelated valid settings continue to work. An invalid project server replacement still shadows the global definition. Untrusted project settings are ignored.

Pi's /reload reloads configuration. Servers start lazily on first use and live for one Pi session. After a process or protocol failure, recovery requires stopping the affected Instance, disabling then enabling its Definition, using the existing restart tool operation, or /reload.

/lsp command

Use /lsp to inspect server status and choose an action. The picker displays known workspace roots and the effective enablement scope; opening it never starts a server. Text shortcuts are:

/lsp stop typescript
/lsp stop typescript "packages/my app"
/lsp disable typescript
/lsp enable typescript
/lsp disable typescript --project
/lsp enable typescript --global
  • Stop ends one known Server Instance and clears its failure state. The next matching request can start it again. Omit the root when only one is known; otherwise choose a root in the picker or supply its path, relative to Pi's working directory or absolute.
  • Disable prevents automatic and explicit startup of the entire Server Definition, stops all its Instances in this session, and clears their failed runtime state.
  • Enable permits lazy startup without launching a process.

Unflagged enable/disable choices are custom session entries. They survive /reload and saved-session resume, but follow the selected branch: navigating before a choice rolls it back, and forks inherit choices on their selected ancestry. A newly created session starts without session overrides. Pi only flushes entries in a brand-new session after its first assistant message; ephemeral sessions are not persisted.

--project and --global write only the chosen settings document's lsp.enablement map, preserving Server Definitions and unrelated settings. Project writes require a trusted project. For example, this project setting disables an inherited global Definition without copying it:

{ "lsp": { "enablement": { "typescript": false } } }

Enablement resolves independently of Server Definition replacement, by server ID: session override → project setting → global setting → enabled by default. Explicit true can override a lower scope's false. Scoped commands do not change session overrides and report when a higher-priority choice masks their effect. They update eligibility immediately in the current session; other sessions read the settings on startup or /reload. Changes to commands, languages, and other definition fields still require /reload.

There are no /lsp start, /lsp restart, or /lsp inherit subcommands. Use explicit enable/disable choices to manage overrides. The existing agent-facing lsp tool remains available, but cannot start a disabled server.

lsp tool

The extension registers one strict lsp tool with these operations:

status                       capabilities                  restart
diagnostics                  workspace_diagnostics         completion
hover                        signature_help                declaration
goto_definition              goto_type_definition          goto_implementation
find_references              document_highlights           document_symbols
workspace_symbols            document_links                call_hierarchy
incoming_calls               outgoing_calls                type_hierarchy
supertypes                   subtypes                      selection_ranges
folding_ranges               code_lenses                   inlay_hints
document_colors              format_document               format_range
format_on_type               prepare_rename                rename
code_actions                 apply

File operations use file_path. Position operations also use one-based line and character; characters count Unicode code points, regardless of the server's negotiated UTF-8, UTF-16, or UTF-32 encoding. selection_ranges accepts a positions array. find_references accepts include_declaration.

workspace_symbols requires query and a root-anchor file_path. workspace_diagnostics, capabilities, and restart require server_id and a root-anchor file_path. Other reads query every matching capable server unless narrowed by server_id; successful responses remain visible when another server fails. Automatic reads omit matching incapable servers and fail once if none are capable; explicitly selecting an incapable server reports that the operation is unsupported. A mutation may omit server_id only when exactly one matching capable server exists.

Formatting requires tab_size and insert_spaces. It optionally accepts trim_trailing_whitespace, insert_final_newline, and trim_final_newlines. Range formatting also requires start and end; on-type formatting requires a position and trigger_character. rename requires new_name. code_actions accepts a range and optional only_kinds filters.

Workspace diagnostics use protocol workspace pull when available and cached push diagnostics otherwise. They never crawl the project to open files. Non-file result URIs such as jar: remain readable.

In Pi's interactive transcript, each LSP call stays compact until tool output is expanded. The collapsed row shows the operation, target, and outcome; the expanded row adds structured server or mutation details followed by the exact tool output. Rendering uses Pi's active theme and native tool-output expansion controls.

Workspace Edit Preview and apply

rename, formatting, and edit-bearing code actions return a persisted Workspace Edit Preview. They never write immediately. Command-bearing code actions remain visible but cannot be applied. Server-initiated workspace/applyEdit requests are rejected with applied: false and exposed as a preview in the active tool result.

Apply a preview with { "operation": "apply", "preview_id": "..." }. Before Pi's tool_call hooks run, prepareArguments() replaces any supplied mutation_manifest with canonical absolute entries shaped as:

{
  "mutation_manifest": [
    {
      "operation": "modify",
      "path": "/absolute/canonical/content/target"
    },
    {
      "operation": "rename",
      "path": "/absolute/old/name",
      "destination_path": "/absolute/new/name"
    }
  ]
}

operation is create, modify, delete, or rename; only rename also has destination_path. A permission extension may inspect or block this manifest. Pi LSP validates it again after all hooks, then rechecks preview state, existence, hashes, modes, and destinations inside Pi's sorted per-file mutation queues. Paths are not restricted to the workspace.

Application uses temporary-file replacement, preserves modes and UTF-8 BOMs, and keeps originals for reverse rollback. Content edits follow existing symlinks and expose the canonical target; rename and delete address the named directory entry. The guarded batch is rollback-capable, not crash-atomic. A recovery failure reports every unrecovered path.

Post-edit Diagnostics

Pi LSP appends fresh diagnostics to results from:

  • any edit or write tool whose input has a string path;
  • apply_patch results using the current pi-codex-conversion success/partial-result details;
  • successful or partially applied LSP previews.

Changed, created, and renamed destination files are diagnosed; deleted files are not. Every recognized result gets an explicit outcome, including no diagnostics, no configured server, timeout, unavailable server, or an apply_patch adapter-version warning. Diagnostics preserve duplicates from independent servers and never change the original tool's success or error state. Only servers that advertise document diagnostics participate; formatting-only servers remain available for explicit LSP formatting operations without appearing in Post-edit Diagnostics. Files excluded by every matching server's Activation Gate or disable state are skipped silently.

Findings, matched-server failures, timeouts, and adapter warnings also appear in one expandable Post-edit Diagnostics Entry after the current tool batch. Clean results and files without a configured server stay silent in the transcript. This entry is excluded from model context; the model sees diagnostics only in the original mutation result.

Limits and lifecycle

Every operation uses Pi's 2,000-line/50-KB output limit. Complete truncated text is saved as a Result Spill and the result names its path. Each server's latest 1 MB of stderr is saved in the session temp directory and failure messages name that path. With --no-session, Pi provides no session directory, so these files use a private directory under the OS temporary directory instead. Normal session teardown removes it; forced termination may leave temporary files behind.

Documents must be valid UTF-8. Each server keeps at most 100 synchronized documents and closes least-recently-used documents. Session shutdown requests a graceful LSP shutdown, then terminates the process within the configured timeout.

Security

Trusted project settings can launch arbitrary local executables with all permissions of the Pi process. Review settings and server binaries before trusting a project. Mutation manifests can be blocked by another extension, but Pi LSP itself intentionally imposes no workspace path boundary.