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]
|
||||
|
||||
+66
-6
@@ -157,6 +157,61 @@ type definitionParams struct {
|
||||
Position Position `json:"position"`
|
||||
}
|
||||
|
||||
type referenceParams struct {
|
||||
TextDocument textDocumentIdentifier `json:"textDocument"`
|
||||
Position Position `json:"position"`
|
||||
Context referenceContext `json:"context"`
|
||||
}
|
||||
|
||||
type referenceContext struct {
|
||||
IncludeDeclaration bool `json:"includeDeclaration"`
|
||||
}
|
||||
|
||||
type renameParams struct {
|
||||
TextDocument textDocumentIdentifier `json:"textDocument"`
|
||||
Position Position `json:"position"`
|
||||
NewName string `json:"newName"`
|
||||
}
|
||||
|
||||
// WorkspaceEdit holds changes across multiple documents.
|
||||
type WorkspaceEdit struct {
|
||||
Changes map[string][]TextEdit `json:"changes"`
|
||||
}
|
||||
|
||||
// TextEdit replaces a range with new text.
|
||||
type TextEdit struct {
|
||||
Range Range `json:"range"`
|
||||
NewText string `json:"newText"`
|
||||
}
|
||||
|
||||
type documentFormattingParams struct {
|
||||
TextDocument textDocumentIdentifier `json:"textDocument"`
|
||||
Options formattingOptions `json:"options"`
|
||||
}
|
||||
|
||||
type formattingOptions struct {
|
||||
TabSize int `json:"tabSize"`
|
||||
InsertSpaces bool `json:"insertSpaces"`
|
||||
}
|
||||
|
||||
type inlayHintParams struct {
|
||||
TextDocument textDocumentIdentifier `json:"textDocument"`
|
||||
Range Range `json:"range"`
|
||||
}
|
||||
|
||||
// InlayHint is a hint displayed inline in the editor.
|
||||
type InlayHint struct {
|
||||
Position Position `json:"position"`
|
||||
Label string `json:"label"`
|
||||
Kind int `json:"kind,omitempty"`
|
||||
Padding bool `json:"paddingLeft,omitempty"`
|
||||
Tooltip string `json:"tooltip,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
inlayHintTypeParameter = 2
|
||||
)
|
||||
|
||||
// Hover is the hover response.
|
||||
type Hover struct {
|
||||
Contents markupContent `json:"contents"`
|
||||
@@ -246,12 +301,17 @@ type initializeParams struct {
|
||||
|
||||
// ServerCapabilities advertises what this server provides.
|
||||
type ServerCapabilities struct {
|
||||
TextDocumentSync int `json:"textDocumentSync"`
|
||||
CompletionProvider map[string]any `json:"completionProvider,omitempty"`
|
||||
HoverProvider bool `json:"hoverProvider,omitempty"`
|
||||
DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"`
|
||||
SemanticTokensProvider map[string]any `json:"semanticTokensProvider,omitempty"`
|
||||
DiagnosticProvider map[string]any `json:"diagnosticProvider,omitempty"`
|
||||
TextDocumentSync int `json:"textDocumentSync"`
|
||||
CompletionProvider map[string]any `json:"completionProvider,omitempty"`
|
||||
HoverProvider bool `json:"hoverProvider,omitempty"`
|
||||
DefinitionProvider bool `json:"definitionProvider,omitempty"`
|
||||
ReferencesProvider bool `json:"referencesProvider,omitempty"`
|
||||
RenameProvider bool `json:"renameProvider,omitempty"`
|
||||
DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"`
|
||||
DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"`
|
||||
SemanticTokensProvider map[string]any `json:"semanticTokensProvider,omitempty"`
|
||||
InlayHintProvider bool `json:"inlayHintProvider,omitempty"`
|
||||
DiagnosticProvider map[string]any `json:"diagnosticProvider,omitempty"`
|
||||
}
|
||||
|
||||
type initializeResult struct {
|
||||
|
||||
+29
-4
@@ -116,10 +116,14 @@ func (s *Server) dispatch(msg *rpcMessage) (exit bool) {
|
||||
case "initialize":
|
||||
s.respond(msg.ID, initializeResult{
|
||||
Capabilities: ServerCapabilities{
|
||||
TextDocumentSync: 1, // full sync
|
||||
CompletionProvider: map[string]any{},
|
||||
HoverProvider: true,
|
||||
DocumentSymbolProvider: true,
|
||||
TextDocumentSync: 1, // full sync
|
||||
CompletionProvider: map[string]any{},
|
||||
HoverProvider: true,
|
||||
DefinitionProvider: true,
|
||||
ReferencesProvider: true,
|
||||
RenameProvider: true,
|
||||
DocumentSymbolProvider: true,
|
||||
DocumentFormattingProvider: true,
|
||||
SemanticTokensProvider: map[string]any{
|
||||
"legend": SemanticTokensLegend{
|
||||
TokenTypes: semanticTokenTypes,
|
||||
@@ -127,6 +131,7 @@ func (s *Server) dispatch(msg *rpcMessage) (exit bool) {
|
||||
},
|
||||
"full": true,
|
||||
},
|
||||
InlayHintProvider: true,
|
||||
},
|
||||
ServerInfo: map[string]string{"name": "gasm", "version": "0.1.0"},
|
||||
})
|
||||
@@ -180,6 +185,26 @@ func (s *Server) dispatch(msg *rpcMessage) (exit bool) {
|
||||
json.Unmarshal(msg.Params, &p)
|
||||
s.respond(msg.ID, s.documentSymbols(p))
|
||||
|
||||
case "textDocument/references":
|
||||
var p referenceParams
|
||||
json.Unmarshal(msg.Params, &p)
|
||||
s.respond(msg.ID, s.references(p))
|
||||
|
||||
case "textDocument/rename":
|
||||
var p renameParams
|
||||
json.Unmarshal(msg.Params, &p)
|
||||
s.respond(msg.ID, s.rename(p))
|
||||
|
||||
case "textDocument/formatting":
|
||||
var p documentFormattingParams
|
||||
json.Unmarshal(msg.Params, &p)
|
||||
s.respond(msg.ID, s.documentFormatting(p))
|
||||
|
||||
case "textDocument/inlayHint":
|
||||
var p inlayHintParams
|
||||
json.Unmarshal(msg.Params, &p)
|
||||
s.respond(msg.ID, s.inlayHints(p))
|
||||
|
||||
case "textDocument/semanticTokens/full":
|
||||
var p semanticTokensParams
|
||||
json.Unmarshal(msg.Params, &p)
|
||||
|
||||
Reference in New Issue
Block a user