Merge: LSP Go to implementation

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-20 03:40:38 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-20 03:40:38 +0200
Commit 7b7f866d2f14e04e95291af5d62e2ea4110646fd (patch)
-rw-r--r-- editor.go 64
-rw-r--r-- kevent.go 11
-rw-r--r-- lsp.go 42
3 files changed, 114 insertions, 3 deletions
diff --git a/editor.go b/editor.go
...
2066
	e.centerCursor()
2066
	e.centerCursor()
2067
}
2067
}
2068
  
2068
  
  
2069
func (e *Editor) gotoImplementation() {
  
2070
	b := e.activeBuffer()
  
2071
	if b == nil || b.lspClient == nil {
  
2072
		return
  
2073
	}
  
2074
  
  
2075
	e.pushJump()
  
2076
  
  
2077
	// Sync buffer content with LSP server before requesting implementation.
  
2078
	b.lspClient.SendDidChange(b.toString())
  
2079
  
  
2080
	locs, err := b.lspClient.Implementation(b.PrimaryCursor().Y, b.PrimaryCursor().X)
  
2081
	if err != nil {
  
2082
		e.addLog("Editor", fmt.Sprintf("gotoImplementation error: %v", err))
  
2083
		return
  
2084
	}
  
2085
  
  
2086
	if len(locs) == 0 {
  
2087
		e.addLog("Editor", "gotoImplementation: No implementation found")
  
2088
		return
  
2089
	}
  
2090
  
  
2091
	loc := locs[0]
  
2092
	targetPath := strings.TrimPrefix(loc.URI, "file://")
  
2093
  
  
2094
	// Find if buffer is already open
  
2095
	found := false
  
2096
	for i, buf := range e.buffers {
  
2097
		absT, _ := filepath.Abs(targetPath)
  
2098
		absB, _ := filepath.Abs(buf.filename)
  
2099
		if absT == absB {
  
2100
			e.activeBufferIndex = i
  
2101
			found = true
  
2102
			break
  
2103
		}
  
2104
	}
  
2105
  
  
2106
	if !found {
  
2107
		if err := e.LoadFile(targetPath); err != nil {
  
2108
			e.addLog("Editor", fmt.Sprintf("gotoImplementation: Failed to load %s: %v", targetPath, err))
  
2109
			return
  
2110
		}
  
2111
	}
  
2112
  
  
2113
	b = e.activeBuffer()
  
2114
	b.PrimaryCursor().Y = loc.Range.Start.Line
  
2115
	b.PrimaryCursor().X = loc.Range.Start.Character
  
2116
  
  
2117
	// Ensure cursor is within bounds
  
2118
	if b.PrimaryCursor().Y < 0 {
  
2119
		b.PrimaryCursor().Y = 0
  
2120
	}
  
2121
	if b.PrimaryCursor().Y >= len(b.buffer) {
  
2122
		b.PrimaryCursor().Y = len(b.buffer) - 1
  
2123
	}
  
2124
	if b.PrimaryCursor().X < 0 {
  
2125
		b.PrimaryCursor().X = 0
  
2126
	}
  
2127
	if b.PrimaryCursor().X > len(b.buffer[b.PrimaryCursor().Y]) {
  
2128
		b.PrimaryCursor().X = len(b.buffer[b.PrimaryCursor().Y])
  
2129
	}
  
2130
	e.centerCursor()
  
2131
}
  
2132
  
2069
func (e *Editor) pushJump() {
2133
func (e *Editor) pushJump() {
2070
	b := e.activeBuffer()
2134
	b := e.activeBuffer()
2071
	if b == nil {
2135
	if b == nil {
...
diff --git a/kevent.go b/kevent.go
...
291
  
291
  
292
	switch ev.Ch {
292
	switch ev.Ch {
293
	case 'i':
293
	case 'i':
294
		e.saveState()
294
		if e.pendingKey == 'g' {
295
		e.mode = ModeInsert
295
			e.gotoImplementation()
296
		e.introDismissed = true
296
			e.pendingKey = 0
  
297
		} else {
  
298
			e.saveState()
  
299
			e.mode = ModeInsert
  
300
			e.introDismissed = true
  
301
		}
297
	case 'a':
302
	case 'a':
298
		e.saveState()
303
		e.saveState()
299
		e.moveCursor(1, 0)
304
		e.moveCursor(1, 0)
...
diff --git a/lsp.go b/lsp.go
...
481
					"dynamicRegistration": false,
481
					"dynamicRegistration": false,
482
					"linkSupport":         false,
482
					"linkSupport":         false,
483
				},
483
				},
  
484
				"implementation": map[string]interface{}{
  
485
					"dynamicRegistration": false,
  
486
					"linkSupport":         false,
  
487
				},
484
			},
488
			},
485
			"workspace": map[string]interface{}{
489
			"workspace": map[string]interface{}{
486
				"configuration":    true,
490
				"configuration":    true,
...
572
	resJSON, _ := json.Marshal(result)
576
	resJSON, _ := json.Marshal(result)
573
  
577
  
574
	// Definition can return a single Location or an array of them.
578
	// Definition can return a single Location or an array of them.
  
579
	var loc Location
  
580
	if err := json.Unmarshal(resJSON, &loc); err == nil && loc.URI != "" {
  
581
		return []Location{loc}, nil
  
582
	}
  
583
  
  
584
	var locs []Location
  
585
	if err := json.Unmarshal(resJSON, &locs); err == nil {
  
586
		return locs, nil
  
587
	}
  
588
  
  
589
	return nil, nil
  
590
}
  
591
  
  
592
// Implementation requests the location of the implementation of the symbol at cursor.
  
593
func (c *LSPClient) Implementation(line, character int) ([]Location, error) {
  
594
	params := map[string]interface{}{
  
595
		"textDocument": map[string]interface{}{
  
596
			"uri": c.uri,
  
597
		},
  
598
		"position": map[string]interface{}{
  
599
			"line":      line,
  
600
			"character": character,
  
601
		},
  
602
	}
  
603
  
  
604
	resp, err := c.Request("textDocument/implementation", params)
  
605
	if err != nil {
  
606
		return nil, err
  
607
	}
  
608
  
  
609
	result := resp["result"]
  
610
	if result == nil {
  
611
		return nil, nil
  
612
	}
  
613
  
  
614
	resJSON, _ := json.Marshal(result)
  
615
  
  
616
	// Implementation can return a single Location or an array of them.
575
	var loc Location
617
	var loc Location
576
	if err := json.Unmarshal(resJSON, &loc); err == nil && loc.URI != "" {
618
	if err := json.Unmarshal(resJSON, &loc); err == nil && loc.URI != "" {
577
		return []Location{loc}, nil
619
		return []Location{loc}, nil
...