turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 20h ago
symbols.go · 111 lines · 3.5 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
// Finding a symbol by name: in the file in front, or anywhere in the project.

package app

import (
	"context"
	"errors"
	"fmt"
	"path/filepath"
	"strings"

	"codeberg.org/turbo-editors/turbo-core/lsp"
	"codeberg.org/turbo-editors/turbo-core/ui"
)

// SymbolInFile lists what the file in front declares, and goes to the one
// chosen. It is the outline of a file, offered as a way of moving around it.
func (a *App) SymbolInFile() {
	view := a.activeView()
	if view == nil {
		return
	}

	symbols, err := a.language.DocumentSymbols(context.Background(), view.Buffer().Path())
	a.showSymbols("Symbols in file", symbols, err, outlineLabel)
}

// SymbolInProject asks for a name and lists the symbols matching it anywhere
// in the project.
//
// The query goes to the server, which decides what "matching" means — gopls
// matches fuzzily, others by prefix. Deciding that here would mean filtering
// an answer the server has already filtered, by a different rule.
func (a *App) SymbolInProject() {
	dialog, field := NewPromptDialog("Symbol in project", "Name:", "", a.screenRect())
	a.pushModal(dialog, func(result ui.Result) {
		if result != ui.ResultOK {
			return
		}
		query := strings.TrimSpace(field.Text())
		if query == "" {
			// An empty query is answered by some servers with the whole
			// project and by others with nothing. Neither is what was meant.
			a.Message("Nothing to look for")
			return
		}

		symbols, err := a.language.WorkspaceSymbols(context.Background(), query)
		a.showSymbols("Symbols matching "+query, symbols, err, projectLabel)
	})
}

// showSymbols offers a list of symbols and goes to the one chosen.
//
// Unlike a list of locations, a single symbol still gets the list. Finding one
// match for a name is an answer worth reading — it says *which* thing has that
// name and where — where a single definition is somewhere you simply wanted to
// be taken.
func (a *App) showSymbols(title string, symbols []lsp.Symbol, err error, label func(lsp.Symbol) string) {
	switch {
	case errors.Is(err, lsp.ErrNotReady):
		a.Message(a.language.Status())
		return
	case err != nil:
		a.Message("Symbols: " + err.Error())
		return
	case len(symbols) == 0:
		a.Message("No symbols found")
		return
	}

	labels := make([]string, 0, len(symbols))
	for _, symbol := range symbols {
		labels = append(labels, label(symbol))
	}

	dialog, list := NewChoiceDialog(fmt.Sprintf("%s (%d)", title, len(symbols)), labels, 0, a.screenRect())
	a.pushModal(dialog, func(result ui.Result) {
		if result == ui.ResultOK && list.Selected() >= 0 {
			a.jumpTo(symbols[list.Selected()].Location)
		}
	})
}

// outlineLabel is how a symbol reads in a file's own outline: indented by how
// deeply it is nested, so a type's methods sit under it, with the kind after
// the name rather than before it so the names line up and can be read down.
func outlineLabel(symbol lsp.Symbol) string {
	label := strings.Repeat("  ", symbol.Depth) + symbol.Name
	if kind := symbol.Kind.String(); kind != "" {
		label += "  " + kind
	}
	return label
}

// projectLabel is how a symbol reads in a search across the project: the name,
// what contains it, and which file — because the file is what tells two
// identically-named symbols apart, and there are usually several.
func projectLabel(symbol lsp.Symbol) string {
	label := symbol.Name
	if kind := symbol.Kind.String(); kind != "" {
		label += "  " + kind
	}
	if symbol.Container != "" {
		label += "  in " + symbol.Container
	}
	if path := lsp.URIToPath(symbol.Location.URI); path != "" {
		label += "  (" + filepath.Base(path) + ")"
	}
	return label
}