agentby ramilito

lsp

LSP features specialist. ALWAYS use for ANY task involving completion, hover, diagnostics, or the in-process LSP server.

Installs: 0
Used in: 1 repos
Updated: 2w ago
$npx ai-builder add agent ramilito/lsp

Installs to .claude/agents/lsp.md

# LSP Features Guidance

**ALWAYS use this subagent** for ANY task involving:
- LSP server configuration or capabilities
- Completion sources for different filetypes
- Hover provider (resource info display)
- Diagnostics (errors/warnings for resources)
- Adding new completion sources or hover formatters

For general Lua/Neovim patterns, use the `lua` subagent.
For Rust async patterns and mlua FFI, use the `rust` subagent.

## Architecture Overview

The plugin runs an **in-process LSP server** (no external process). This enables:
- Native completion via `vim.lsp.completion` or external plugins (blink.cmp, nvim-cmp)
- Hover information via `vim.lsp.buf.hover()` (typically `K` key)
- Diagnostics for resource status issues

```
+-------------------------------------------------------------+
|                         Neovim                               |
|  +-----------------+      +----------------------------+    |
|  | Plugin Buffers  |<---->|  kubectl LSP Server        |    |
|  | (k8s_* types)   |      |  (in-process, lsp/init.lua)|    |
|  +-----------------+      +----------------------------+    |
|           |                          |                       |
|           v                          v                       |
|  +-----------------+      +----------------------------+    |
|  | Diagnostics     |      |  Handlers:                 |    |
|  | (virtual_lines) |      |  - completion -> sources/* |    |
|  +-----------------+      |  - hover -> Rust FFI       |    |
|                           +----------------------------+    |
+-------------------------------------------------------------+
```

## File Map

| Layer | File | Purpose |
|-------|------|---------|
| Lua | `lua/kubectl/lsp/init.lua` | LSP server, capability negotiation, client management |
| Lua | `lua/kubectl/lsp/hover/init.lua` | Hover handler, diagnostic integration |
| Lua | `lua/kubectl/lsp/diagnostics/init.lua` | Set/toggle diagnostics, quickfix export |
| Lua | `lua/kubectl/lsp/sources/aliases.lua` | Completion for `:Kubectl` (resource names, short names) |
| Lua | `lua/kubectl/lsp/sources/namespaces.lua` | Completion for `:Kubens` |
| Lua | `lua/kubectl/lsp/sources/contexts.lua` | Completion for `:Kubectx` |
| Lua | `lua/kubectl/lsp/sources/filter.lua` | Completion for filter prompt (history) |
| Rust | `kubectl-client/src/hover/mod.rs` | Async hover data fetch |
| Rust | `kubectl-client/src/hover/formatters.rs` | Resource-specific markdown formatters |

## Data Flow

### Completion Flow
```
User types in prompt buffer
        |
        v
LSP textDocument/completion request
        |
        v
lsp/init.lua:get_completion_items()
        |
        v
sources[filetype]() -> items[]
        |
        v
Return to LSP client (displayed by completion plugin)
```

### Hover Flow
```
User triggers hover (K key)
        |
        v
LSP textDocument/hover request
        |
        v
lsp/hover/init.lua:get_hover()
        |
        +--> resource_from_filetype() -> resource name
        +--> get_selection() -> name, namespace
        |
        v
commands.run_async("get_hover_async", {gvk, namespace, name})
        |
        v
Rust: hover/mod.rs -> store cache or API fetch
        |
        v
Rust: formatters.rs -> markdown output
        |
        v
Lua: append diagnostic section (if any)
        |
        v
Return markdown to Neovim hover window
```

### Diagnostics Flow
```
Resource view drawn (via resource_factory)
        |
        v
diagnostics.set_diagnostics(bufnr, resource)
        |
        v
Iterate builder.processedData rows
        |
        +--> Check status/phase/conditions for severity symbols
        +--> Build message from status, ready, restarts, hint
        |
        v
vim.diagnostic.set(ns, bufnr, diagnostics)
        |
        v
Displayed via virtual_lines (current line only by default)
```

## LSP Server Implementation

The server is created in `lsp/init.lua:8-49` as a Lua function (not an external process):

```lua
local function server(opts)
  return function(dispatchers)
    local srv = {}

    function srv.request(method, params, callback)
      local handler = handlers[method]
      if handler then
        handler(method, params, callback)
      elseif method == "initialize" then
        callback(nil, { capabilities = capabilities })
      end
      -- ...
    end

    return srv
  end
end
```

**Capabilities declared** (line 76-80):
- `completionProvider` with trigger characters `:`, `-`
- `hoverProvider = true`

**Client reuse:** Same client attached to all `k8s_*` buffers via `reuse_client` (line 69-71).

## Completion Sources

Each source follows the pattern:

```lua
local M = {}

function M.get_items()
  local items = {}
  -- Build completion items
  return items
end

function M.register()
  require("kubectl.lsp").register_source("k8s_<filetype>", M.get_items)
end

return M
```

**Completion item fields:**
- `label` - Display text
- `labelDetails.description` - Secondary info (kind name, etc.)
- `insertText` - Text to insert (if different from label)
- `documentation` - Hover docs for item
- `kind_name` / `kind_icon` - Custom kind display

**Registered sources:**

| Filetype | Source | Items |
|----------|--------|-------|
| `k8s_aliases` | `sources/aliases.lua` | API resources, short names, custom views |
| `k8s_namespaces` | `sources/namespaces.lua` | Namespace names |
| `k8s_contexts` | `sources/contexts.lua` | Kubernetes context names |
| `k8s_filter` | `sources/filter.lua` | Filter history entries |

## Hover Provider

`lsp/hover/init.lua` handles `textDocument/hover`:

1. **Filetype check** (`resource_from_filetype`) - Skips non-resource views (filter, namespaces, yaml, describe)
2. **Selection extraction** (`get_selection`) - Gets name/namespace using column positions from definition
3. **Async fetch** - Calls Rust `get_hover_async` with GVK and identifiers
4. **Diagnostic append** (`get_diagnostic_section`) - Adds diagnostics for current line if any

### Rust Hover Formatters

`hover/formatters.rs` has resource-specific formatters for 15 resource types:

| Resource | Formatter | Key Info Shown |
|----------|-----------|----------------|
| Pod | `format_pod` | Phase, node, IP, containers (state, ready, restarts), conditions |
| Deployment | `format_deployment` | Replicas, strategy, images, selector, conditions |
| StatefulSet | `format_statefulset` | Replicas, service name, conditions |
| DaemonSet | `format_daemonset` | Desired/current/ready counts, conditions |
| Job | `format_job` | Completions, active, failed, duration |
| Service | `format_service` | Type, ClusterIP, ports, selector |
| ConfigMap/Secret | `format_configmap/secret` | Key count, key names |
| PVC/PV | `format_pvc/pv` | Phase, capacity, access modes, storage class |
| Node | `format_node` | Addresses, conditions, schedulability |
| _other_ | `format_generic` | Labels, owner, age |

**Output format:** Markdown with `##` headers, `**bold**` labels, `` `code` `` for values.

## Diagnostics

`lsp/diagnostics/init.lua` provides Kubernetes-aware diagnostics:

**Severity detection** (line 10-11):
```lua
local error_symbols = { KubectlError = vim.diagnostic.severity.ERROR }
local warning_symbols = { KubectlWarning = vim.diagnostic.severity.WARN }
```

These symbols come from Rust processors (`status.symbol` field).

**Message building** (`get_message`, line 49-99):
- Status value with hint from Rust (if unhealthy)
- Ready count (if not all ready)
- Restart count with context
- Age (for errors only)

**Display config** (line 176-184):
```lua
vim.diagnostic.config({
  virtual_text = false,
  virtual_lines = { current_line = true },  -- Only show on current line
  signs = true,
  underline = false,
})
```

**Public API:**
- `M.set_diagnostics(bufnr, resource)` - Called after view draw
- `M.toggle()` - Enable/disable virtual_lines display
- `M.to_quickfix()` - Export to quickfix list

## Common Tasks

### Adding a New Completion Source

1. Create `lua/kubectl/lsp/sources/<name>.lua`:
```lua
local M = {}

function M.get_items()
  local items = {}
  -- Fetch data and build items
  table.insert(items, {
    label = "item-name",
    kind_name = "KindName",
    kind_icon = "...",
  })
  return items
end

function M.register()
  require("kubectl.lsp").register_source("k8s_<filetype>", M.get_items)
end

return M
```

2. Register in plugin initialization (typically in `init.lua` setup)

### Adding Hover for New Resource Type

1. Add case in `hover/formatters.rs:format_resource()`:
```rust
"myresource" | "myresources" => format_myresource(obj),
```

2. Implement formatter function following existing patterns:
```rust
fn format_myresource(obj: &DynamicObject) -> String {
    let Ok(res) = from_value::<MyResource>(to_value(obj).unwrap_or_default()) else {
        return format_generic("MyResource", obj);
    };
    // Build markdown lines...
}
```

### Adding Diagnostic Severity

1. Add symbol mapping in `diagnostics/init.lua`:
```lua
local error_symbols = { KubectlError = ..., MyNewErrorSymbol = ... }
```

2. Ensure Rust processor sets `status.symbol` to match

### Extending Diagnostic Message

Modify `get_message()` in `diagnostics/init.lua` to include new fields:
```lua
if row.my_field then
  local val = get_value(row.my_field)
  if val ~= "" then
    table.insert(parts, string.format("My Field: %s", val))
  end
end
```

## Testing

Manual testing via:
```bash
nvim -u repro.lua
```

Then:
- Open `:Kubectl pods` - verify diagnostics appear on unhealthy pods
- Press `K` on a pod row - verify hover shows resource info
- Open `:Kubectl` and type - verify completion shows resources
- Open `:Kubens` and type - verify namespace completion

## Key Patterns

1. **In-process LSP** - No subprocess, server is a Lua table with request/notify methods
2. **Filetype-based sources** - `sources[filetype]()` pattern for completion dispatch
3. **Async hover** - Rust fetch with `vim.schedule` callback for UI safety
4. **Symbol-based severity** - Rust processors set `symbol` field, Lua maps to severity
5. **Virtual lines** - Diagnostics shown inline only on current line to reduce noise

Quick Install

$npx ai-builder add agent ramilito/lsp

Details

Type
agent
Author
ramilito
Slug
ramilito/lsp
Created
2w ago

More by ramilito