feat(lsp): add references, rename, formatting, and inlay hints
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
+217
@@ -4,6 +4,7 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -125,6 +126,222 @@ func (s *Server) definition(p definitionParams) []Location {
|
||||
return nil
|
||||
}
|
||||
|
||||
// references returns all locations where the symbol under the cursor appears.
|
||||
func (s *Server) references(p referenceParams) []Location {
|
||||
text := s.docs[p.TextDocument.URI]
|
||||
word, _ := wordAt(text, p.Position)
|
||||
if word == "" {
|
||||
return nil
|
||||
}
|
||||
f, errs := parser.Parse(uriPath(p.TextDocument.URI), text)
|
||||
if f == nil || len(errs) > 0 {
|
||||
return nil
|
||||
}
|
||||
uri := p.TextDocument.URI
|
||||
var out []Location
|
||||
for _, d := range f.Decls {
|
||||
t, ok := d.(*ast.Text)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Include the definition if requested.
|
||||
if p.Context.IncludeDeclaration {
|
||||
if t.Name.Name == word {
|
||||
out = append(out, Location{
|
||||
URI: uri,
|
||||
Range: symRange(t.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, stmt := range t.Body {
|
||||
switch st := stmt.(type) {
|
||||
case *ast.Label:
|
||||
if st.Name.Text == word {
|
||||
out = append(out, Location{
|
||||
URI: uri,
|
||||
Range: tokenRange(st.Name),
|
||||
})
|
||||
}
|
||||
case *ast.Instr:
|
||||
for _, op := range st.Operands {
|
||||
if op.Addr.Sym != nil && op.Addr.Sym.Name == word {
|
||||
out = append(out, Location{
|
||||
URI: uri,
|
||||
Range: Range{
|
||||
Start: Position{Line: op.Pos.Line - 1, Character: op.Pos.Column - 1},
|
||||
End: Position{Line: op.Pos.Line - 1, Character: op.Pos.Column - 1 + runeLen(word)},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// rename renames a label across the document.
|
||||
func (s *Server) rename(p renameParams) *WorkspaceEdit {
|
||||
text := s.docs[p.TextDocument.URI]
|
||||
word, _ := wordAt(text, p.Position)
|
||||
if word == "" || word == p.NewName {
|
||||
return nil
|
||||
}
|
||||
refs := s.references(referenceParams{
|
||||
TextDocument: p.TextDocument,
|
||||
Position: p.Position,
|
||||
Context: referenceContext{IncludeDeclaration: true},
|
||||
})
|
||||
if len(refs) == 0 {
|
||||
return nil
|
||||
}
|
||||
edits := make([]TextEdit, 0, len(refs))
|
||||
for _, loc := range refs {
|
||||
edits = append(edits, TextEdit{Range: loc.Range, NewText: p.NewName})
|
||||
}
|
||||
return &WorkspaceEdit{
|
||||
Changes: map[string][]TextEdit{p.TextDocument.URI: edits},
|
||||
}
|
||||
}
|
||||
|
||||
// documentFormatting returns a single edit that replaces the whole document
|
||||
// with its formatted version (canonical indentation and spacing).
|
||||
func (s *Server) documentFormatting(p documentFormattingParams) []TextEdit {
|
||||
text := s.docs[p.TextDocument.URI]
|
||||
if text == "" {
|
||||
return nil
|
||||
}
|
||||
f, _ := parser.Parse(uriPath(p.TextDocument.URI), text)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
formatted := formatFile(f)
|
||||
if formatted == text {
|
||||
return nil
|
||||
}
|
||||
lines := strings.Split(text, "\n")
|
||||
endLine := len(lines) - 1
|
||||
endChar := 0
|
||||
if endLine >= 0 {
|
||||
endChar = len([]rune(lines[endLine]))
|
||||
}
|
||||
return []TextEdit{{
|
||||
Range: Range{Start: Position{Line: 0, Character: 0}, End: Position{Line: endLine, Character: endChar}},
|
||||
NewText: formatted,
|
||||
}}
|
||||
}
|
||||
|
||||
// inlayHints returns hints for frame sizes and argument areas.
|
||||
func (s *Server) inlayHints(p inlayHintParams) []InlayHint {
|
||||
text := s.docs[p.TextDocument.URI]
|
||||
f, _ := parser.Parse(uriPath(p.TextDocument.URI), text)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
var out []InlayHint
|
||||
for _, d := range f.Decls {
|
||||
t, ok := d.(*ast.Text)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Hint after the args size: show frame size.
|
||||
if t.Frame != nil && t.Frame.Imm.HasVal && t.Args != nil && t.Args.Imm.HasVal {
|
||||
// Place hint after the args operand using its raw text length.
|
||||
col := t.Args.Pos.Column - 1 + len(t.Args.Raw)
|
||||
out = append(out, InlayHint{
|
||||
Position: Position{Line: t.Args.Pos.Line - 1, Character: col},
|
||||
Label: fmt.Sprintf(" frame=%d", t.Frame.Imm.Val),
|
||||
Kind: inlayHintTypeParameter,
|
||||
Tooltip: fmt.Sprintf("local frame size: %d bytes", t.Frame.Imm.Val),
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// formatFile produces a canonical formatting of a parsed file.
|
||||
func formatFile(f *ast.File) string {
|
||||
var b strings.Builder
|
||||
for i, d := range f.Decls {
|
||||
if i > 0 {
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
switch dd := d.(type) {
|
||||
case *ast.Include:
|
||||
fmt.Fprintf(&b, "#include %s\n", dd.Header.Text)
|
||||
case *ast.Preproc:
|
||||
b.WriteString("#")
|
||||
b.WriteString(dd.Raw)
|
||||
b.WriteByte('\n')
|
||||
case *ast.Text:
|
||||
fmt.Fprintf(&b, "TEXT %s(SB), %s, $%d-%d\n",
|
||||
symString(dd.Name), strings.Join(dd.Flags, "|"), frameVal(dd.Frame), argsVal(dd.Args))
|
||||
for _, stmt := range dd.Body {
|
||||
switch st := stmt.(type) {
|
||||
case *ast.Label:
|
||||
fmt.Fprintf(&b, "%s:\n", st.Name.Text)
|
||||
case *ast.Instr:
|
||||
b.WriteByte('\t')
|
||||
b.WriteString(st.Mnemonic.Text)
|
||||
for j, op := range st.Operands {
|
||||
if j > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(op.Raw)
|
||||
}
|
||||
if st.Comment != "" {
|
||||
b.WriteString(st.Comment)
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
case *ast.Globl:
|
||||
fmt.Fprintf(&b, "GLOBL %s(SB), %s, $%d\n",
|
||||
symString(dd.Name), strings.Join(dd.Flags, "|"), globlSize(dd.Size))
|
||||
case *ast.Data:
|
||||
fmt.Fprintf(&b, "DATA %s+%d(SB)/%d, %s\n",
|
||||
symString(dd.Name), dd.Name.Offset, dd.Width, dd.Value.Raw)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func symString(sym *ast.Symbol) string {
|
||||
if sym == nil {
|
||||
return "<?>"
|
||||
}
|
||||
s := ""
|
||||
if sym.Pkg != "" {
|
||||
s = sym.Pkg + "·"
|
||||
}
|
||||
s += sym.Name
|
||||
if sym.Static {
|
||||
s += "<>"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func frameVal(op *ast.Operand) int64 {
|
||||
if op != nil && op.Imm.HasVal {
|
||||
return op.Imm.Val
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func argsVal(op *ast.Operand) int64 {
|
||||
if op != nil && op.Imm.HasVal {
|
||||
return op.Imm.Val
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func globlSize(op *ast.Operand) int64 {
|
||||
if op != nil && op.Imm.HasVal {
|
||||
return op.Imm.Val
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// documentSymbols returns functions and their labels, plus global symbols.
|
||||
func (s *Server) documentSymbols(p documentSymbolParams) []DocumentSymbol {
|
||||
text := s.docs[p.TextDocument.URI]
|
||||
|
||||
Reference in New Issue
Block a user