Skip to content

Commit 3c8a006

Browse files
authored
Merge pull request #11 from Codestz/fix/go-receiver-method-support
fix: support Go receiver method notation for read/find commands
2 parents 2be325d + ec87c38 commit 3c8a006

12 files changed

Lines changed: 546 additions & 36 deletions

File tree

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export default defineConfig({
4141
{ label: 'How It Works', link: '/how-it-works/' },
4242
],
4343
},
44+
{ label: 'Troubleshooting', link: '/troubleshooting/' },
4445
{ label: 'Contributing', link: '/contributing/' },
4546
],
4647
head: [

docs/src/content/docs/languages/go.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,19 @@ description: Using krait with Go projects.
1111
go install golang.org/x/tools/gopls@latest
1212
```
1313

14+
On macOS without Go installed, krait can install gopls via Homebrew:
15+
16+
```bash
17+
krait server install go # uses brew if go is not in PATH
18+
```
19+
20+
Note: installing the gopls binary alone is not sufficient — the Go toolchain must also be in PATH at runtime (see [Troubleshooting](#troubleshooting) below).
21+
1422
## Requirements
1523

1624
- `go.mod` at project root
17-
- Go toolchain in PATH
25+
- Go toolchain in PATH (`go` command available)
26+
- gopls binary installed
1827

1928
## Zero Config
2029

@@ -46,3 +55,39 @@ krait find symbol Server.Handle
4655
## Performance
4756

4857
gopls is fast even on large projects. `find symbol` and `hover` typically respond in 30-60ms on warm daemon.
58+
59+
## Troubleshooting
60+
61+
### `warn go: gopls is installed but requires go in PATH`
62+
63+
gopls is a standalone binary, but it calls the `go` command internally to load module graphs and type-check packages. Installing the gopls binary alone (e.g. via `brew install gopls`) is not enough.
64+
65+
**Fix:** Install the Go toolchain from [https://go.dev/dl/](https://go.dev/dl/), then re-index:
66+
67+
```bash
68+
go version # verify Go is available
69+
krait daemon stop
70+
krait init --force
71+
```
72+
73+
### `indexed 0 files, 0 symbols` with no warning
74+
75+
gopls is likely not installed.
76+
77+
```bash
78+
krait server list # check gopls status
79+
krait server install go # install gopls (requires go in PATH)
80+
```
81+
82+
### `krait status` shows `go (gopls) — pending` indefinitely
83+
84+
gopls is still loading the workspace, or failed to start silently. Restart the daemon:
85+
86+
```bash
87+
krait daemon stop
88+
krait status # daemon auto-restarts on next command
89+
```
90+
91+
### First `krait init` is slow
92+
93+
gopls downloads and caches module dependencies on first use. This is a one-time cost. Large modules (e.g. with many external dependencies) can take 30–60 seconds the first time.
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
title: Troubleshooting
3+
description: Common issues and fixes when using krait.
4+
---
5+
6+
## Index Issues
7+
8+
### `krait init` shows `indexed 0 files, 0 symbols`
9+
10+
**With a warning line:** the warning tells you exactly what's wrong. Common causes:
11+
12+
| Warning | Cause | Fix |
13+
|---------|-------|-----|
14+
| `gopls is installed but requires go in PATH` | Go toolchain missing | Install Go from [go.dev/dl](https://go.dev/dl/) |
15+
| `failed to install gopls: Go is required` | gopls not installed, no Go in PATH | Install Go, then `krait server install go` |
16+
| `no LSP server configured for <lang>` | Language not supported | Check [Language Support](/languages/) |
17+
18+
**With no warning:** the LSP server may not be installed.
19+
20+
```bash
21+
krait server list # see what's installed
22+
krait server install <lang>
23+
krait init --force
24+
```
25+
26+
### `krait init` re-indexes everything every time
27+
28+
The index is content-addressed (BLAKE3 hashes). Files only re-index when their content changes. If everything re-indexes on each run, the `.krait/index.db` may have been deleted or is being regenerated.
29+
30+
Check that `.krait/` is not in your `.gitignore` or being cleaned by another tool.
31+
32+
### Symbols are stale after editing a file
33+
34+
The file watcher marks edited files dirty within ~500ms. If symbols appear stale:
35+
36+
```bash
37+
krait status # check "dirty files" count
38+
krait init --force # full re-index if watcher has fallen behind
39+
```
40+
41+
---
42+
43+
## Daemon Issues
44+
45+
### `krait status` hangs or returns no output
46+
47+
The daemon may have crashed. Remove the stale socket and restart:
48+
49+
```bash
50+
krait daemon stop # cleans up PID + socket files
51+
krait status # daemon auto-restarts
52+
```
53+
54+
### `krait daemon start` fails with "already running"
55+
56+
A stale PID file exists from a previous crash. Stop the daemon to clean it up:
57+
58+
```bash
59+
krait daemon stop
60+
krait daemon start
61+
```
62+
63+
### Daemon shuts down unexpectedly
64+
65+
The daemon idles out after 30 minutes of inactivity by default. It auto-restarts on the next command — no manual action needed.
66+
67+
To keep it alive longer, set `KRAIT_IDLE_TIMEOUT` (in seconds) before starting:
68+
69+
```bash
70+
KRAIT_IDLE_TIMEOUT=3600 krait daemon start # 1 hour
71+
```
72+
73+
---
74+
75+
## LSP Server Issues
76+
77+
### `krait server install <lang>` fails
78+
79+
| Error | Fix |
80+
|-------|-----|
81+
| `Go is required but not found in PATH` | Install Go from [go.dev/dl](https://go.dev/dl/) |
82+
| `Node.js is required but not found in PATH` | Install Node.js from [nodejs.org](https://nodejs.org/) |
83+
| `Homebrew is required but not found in PATH` | Install Homebrew or use the language's native install method |
84+
| `npm install failed` | Check npm registry access; try `npm install` manually |
85+
86+
### `krait server list` shows a server as `not installed`
87+
88+
Run the install command shown in the output:
89+
90+
```bash
91+
krait server install rust # installs rust-analyzer
92+
krait server install go # installs gopls
93+
```
94+
95+
### A language server keeps crashing
96+
97+
```bash
98+
krait server status # see running LSP processes
99+
krait daemon stop # full restart
100+
krait status # re-boot all servers
101+
```
102+
103+
If crashes persist, the language server binary may be corrupted. Remove managed servers and reinstall:
104+
105+
```bash
106+
krait server clean # removes ~/.krait/servers/
107+
krait server install <lang>
108+
```
109+
110+
---
111+
112+
## macOS-Specific Issues
113+
114+
### `krait` binary crashes immediately (exit 137)
115+
116+
After installing or updating the krait binary, macOS may cache the old binary's security state. Overwriting a binary in-place with `cp` can trigger this.
117+
118+
**Fix:** Remove the old binary before copying:
119+
120+
```bash
121+
rm /usr/local/bin/krait
122+
cp ./target/release/krait /usr/local/bin/krait
123+
```
124+
125+
### `spctl --assess` reports `rejected`
126+
127+
`spctl` checks against the App Store or Developer ID policy. Locally built binaries are always rejected by this check, but they still run fine. This is not an error.
128+
129+
---
130+
131+
## Command Errors
132+
133+
### `error: LSP servers still indexing`
134+
135+
The daemon just started and language servers are still loading. Wait a few seconds:
136+
137+
```bash
138+
krait status # check "pending" count
139+
```
140+
141+
Retry the command once `krait status` shows no pending servers.
142+
143+
### `no results` from `krait find symbol`
144+
145+
- The symbol name may be misspelled or use wrong casing (symbol lookup is exact)
146+
- The file may not be indexed yet — run `krait init`
147+
- The language server may still be loading — check `krait status`
148+
149+
Try a text search as a fallback:
150+
151+
```bash
152+
krait search MySymbol src/
153+
```
154+
155+
### `krait check` returns no diagnostics on a file with errors
156+
157+
The language server may not have finished loading the file. Wait for `krait status` to show no pending servers, then retry. If the issue persists, the file may be outside the indexed workspace — verify with `krait status`.
158+
159+
---
160+
161+
## Getting More Information
162+
163+
Run any command with `RUST_LOG=krait=debug` to see detailed logs from the CLI:
164+
165+
```bash
166+
RUST_LOG=krait=debug krait find symbol MyStruct
167+
```
168+
169+
For daemon-level logs, run the daemon in the foreground:
170+
171+
```bash
172+
krait daemon stop
173+
RUST_LOG=krait=debug krait daemon start # keep terminal open
174+
```
175+
176+
Then run your command in a second terminal.

src/commands/find.rs

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::path::Path;
22

33
use anyhow::Context;
44
use serde_json::{json, Value};
5+
use tracing::debug;
56

67
use crate::lang::go as lang_go;
78
use crate::lsp::client::{self, LspClient};
@@ -192,6 +193,69 @@ fn classify_definition<'a>(line: &str, name: &str) -> Option<&'a str> {
192193
Some(kind)
193194
}
194195

196+
/// Find a Go receiver method by `"Receiver.Method"` dotted notation.
197+
///
198+
/// gopls workspace/symbol returns methods as flat `"(*Receiver).Method"` entries.
199+
/// This searches for the method name and filters results using `receiver_method_matches`.
200+
///
201+
/// Returns `(symbol, token)` where `token` is the bare method name to use for
202+
/// position lookup in the source file.
203+
async fn find_go_receiver_method(
204+
name: &str,
205+
client: &mut LspClient,
206+
project_root: &Path,
207+
) -> anyhow::Result<Option<(SymbolMatch, String)>> {
208+
let Some(dot) = name.find('.') else {
209+
return Ok(None);
210+
};
211+
let receiver = &name[..dot];
212+
let method = &name[dot + 1..];
213+
214+
let params = json!({ "query": method });
215+
let request_id = client
216+
.transport_mut()
217+
.send_request("workspace/symbol", params)
218+
.await?;
219+
let response = client
220+
.wait_for_response_public(request_id)
221+
.await
222+
.context("workspace/symbol request failed")?;
223+
224+
let Some(items) = response.as_array() else {
225+
return Ok(None);
226+
};
227+
228+
debug!(
229+
"find_go_receiver_method: got {} items for query '{method}'",
230+
items.len()
231+
);
232+
for item in items {
233+
let sym_name = item.get("name").and_then(Value::as_str).unwrap_or_default();
234+
let matches = crate::lang::go::receiver_method_matches(sym_name, receiver, method);
235+
debug!(" sym_name={sym_name:?} receiver_method_matches({receiver},{method})={matches}");
236+
if !matches {
237+
continue;
238+
}
239+
let (path, line) = extract_location(item, project_root);
240+
let preview = read_line_preview(&project_root.join(&path), line);
241+
let kind =
242+
symbol_kind_name(item.get("kind").and_then(Value::as_u64).unwrap_or(0)).to_string();
243+
return Ok(Some((
244+
SymbolMatch {
245+
path,
246+
line,
247+
kind,
248+
preview,
249+
body: None,
250+
},
251+
method.to_string(),
252+
)));
253+
}
254+
255+
debug!("find_go_receiver_method: no match found for '{name}'");
256+
Ok(None)
257+
}
258+
195259
/// Find all references to a symbol using `textDocument/references`.
196260
///
197261
/// First resolves the symbol's location via `workspace/symbol`, then
@@ -205,11 +269,38 @@ pub async fn find_refs(
205269
file_tracker: &mut FileTracker,
206270
project_root: &Path,
207271
) -> anyhow::Result<Vec<ReferenceMatch>> {
208-
// Step 1: Find the symbol definition
272+
// Step 1: Find the symbol definition.
273+
// For dotted Go receiver methods ("Handler.CreateSession"), workspace/symbol
274+
// won't match the full dotted name — fall back to receiver method search.
209275
let symbols = find_symbol(name, client, project_root).await?;
210-
let symbol = symbols
211-
.first()
212-
.with_context(|| format!("symbol '{name}' not found"))?;
276+
debug!(
277+
"find_refs: find_symbol('{name}') returned {} results",
278+
symbols.len()
279+
);
280+
let (symbol, token) = if let Some(sym) = symbols.into_iter().next() {
281+
debug!("find_refs: direct match at {}:{}", sym.path, sym.line);
282+
// For Go receiver method notation "Handler.CreateSession", Go source files
283+
// contain "CreateSession" as the method token, not "Handler.CreateSession".
284+
// Use only the method part for position lookup to find the right character offset.
285+
let token = if Path::new(&sym.path).extension().and_then(|e| e.to_str()) == Some("go")
286+
&& name.contains('.')
287+
{
288+
name.rsplit('.').next().unwrap_or(name).to_string()
289+
} else {
290+
name.to_string()
291+
};
292+
(sym, token)
293+
} else if name.contains('.') {
294+
debug!("find_refs: no direct match, trying go receiver method path");
295+
let result = find_go_receiver_method(name, client, project_root).await?;
296+
debug!(
297+
"find_refs: find_go_receiver_method returned: {}",
298+
result.is_some()
299+
);
300+
result.with_context(|| format!("symbol '{name}' not found"))?
301+
} else {
302+
anyhow::bail!("symbol '{name}' not found");
303+
};
213304

214305
// Step 2: Open the file containing the definition and let the server process it
215306
let abs_path = project_root.join(&symbol.path);
@@ -224,7 +315,7 @@ pub async fn find_refs(
224315

225316
// Step 3: Send references request at the symbol position (single attempt)
226317
let uri = crate::lsp::client::path_to_uri(&abs_path)?;
227-
let (ref_line, ref_char) = find_name_position(&abs_path, symbol.line, name);
318+
let (ref_line, ref_char) = find_name_position(&abs_path, symbol.line, &token);
228319

229320
let params = json!({
230321
"textDocument": { "uri": uri.as_str() },

0 commit comments

Comments
 (0)