diff --git a/editor.go b/editor.go index bb28aebb10baaad2c99911d4f8f5a10e0370b825..473136a5b417e5b4e6216cef9d39daf2f5fed3ea 100644 --- a/editor.go +++ b/editor.go @@ -2066,6 +2066,70 @@ } e.centerCursor() } +func (e *Editor) gotoImplementation() { + b := e.activeBuffer() + if b == nil || b.lspClient == nil { + return + } + + e.pushJump() + + // Sync buffer content with LSP server before requesting implementation. + b.lspClient.SendDidChange(b.toString()) + + locs, err := b.lspClient.Implementation(b.PrimaryCursor().Y, b.PrimaryCursor().X) + if err != nil { + e.addLog("Editor", fmt.Sprintf("gotoImplementation error: %v", err)) + return + } + + if len(locs) == 0 { + e.addLog("Editor", "gotoImplementation: No implementation found") + return + } + + loc := locs[0] + targetPath := strings.TrimPrefix(loc.URI, "file://") + + // Find if buffer is already open + found := false + for i, buf := range e.buffers { + absT, _ := filepath.Abs(targetPath) + absB, _ := filepath.Abs(buf.filename) + if absT == absB { + e.activeBufferIndex = i + found = true + break + } + } + + if !found { + if err := e.LoadFile(targetPath); err != nil { + e.addLog("Editor", fmt.Sprintf("gotoImplementation: Failed to load %s: %v", targetPath, err)) + return + } + } + + b = e.activeBuffer() + b.PrimaryCursor().Y = loc.Range.Start.Line + b.PrimaryCursor().X = loc.Range.Start.Character + + // Ensure cursor is within bounds + if b.PrimaryCursor().Y < 0 { + b.PrimaryCursor().Y = 0 + } + if b.PrimaryCursor().Y >= len(b.buffer) { + b.PrimaryCursor().Y = len(b.buffer) - 1 + } + if b.PrimaryCursor().X < 0 { + b.PrimaryCursor().X = 0 + } + if b.PrimaryCursor().X > len(b.buffer[b.PrimaryCursor().Y]) { + b.PrimaryCursor().X = len(b.buffer[b.PrimaryCursor().Y]) + } + e.centerCursor() +} + func (e *Editor) pushJump() { b := e.activeBuffer() if b == nil { diff --git a/kevent.go b/kevent.go index c3847c11a30ddc4206728d25ddcaa54884375eda..a2ee560469dd46ed19d8b8112634b7643e592c50 100644 --- a/kevent.go +++ b/kevent.go @@ -291,9 +291,14 @@ } switch ev.Ch { case 'i': - e.saveState() - e.mode = ModeInsert - e.introDismissed = true + if e.pendingKey == 'g' { + e.gotoImplementation() + e.pendingKey = 0 + } else { + e.saveState() + e.mode = ModeInsert + e.introDismissed = true + } case 'a': e.saveState() e.moveCursor(1, 0) diff --git a/lsp.go b/lsp.go index d56edd38ef96720784e2db471cf2ba95f5bcc0d0..b8fa5b368f0aa12ab234baca78bbe3b4ef25b182 100644 --- a/lsp.go +++ b/lsp.go @@ -481,6 +481,10 @@ "definition": map[string]interface{}{ "dynamicRegistration": false, "linkSupport": false, }, + "implementation": map[string]interface{}{ + "dynamicRegistration": false, + "linkSupport": false, + }, }, "workspace": map[string]interface{}{ "configuration": true, @@ -572,6 +576,44 @@ resJSON, _ := json.Marshal(result) // Definition can return a single Location or an array of them. + var loc Location + if err := json.Unmarshal(resJSON, &loc); err == nil && loc.URI != "" { + return []Location{loc}, nil + } + + var locs []Location + if err := json.Unmarshal(resJSON, &locs); err == nil { + return locs, nil + } + + return nil, nil +} + +// Implementation requests the location of the implementation of the symbol at cursor. +func (c *LSPClient) Implementation(line, character int) ([]Location, error) { + params := map[string]interface{}{ + "textDocument": map[string]interface{}{ + "uri": c.uri, + }, + "position": map[string]interface{}{ + "line": line, + "character": character, + }, + } + + resp, err := c.Request("textDocument/implementation", params) + if err != nil { + return nil, err + } + + result := resp["result"] + if result == nil { + return nil, nil + } + + resJSON, _ := json.Marshal(result) + + // Implementation can return a single Location or an array of them. var loc Location if err := json.Unmarshal(resJSON, &loc); err == nil && loc.URI != "" { return []Location{loc}, nil