diff --git a/lsp/handlers.go b/lsp/handlers.go index ae86933..54fa735 100644 --- a/lsp/handlers.go +++ b/lsp/handlers.go @@ -452,6 +452,50 @@ func (s *Server) documentHighlights(p documentHighlightParams) []DocumentHighlig return out } +// workspaceSymbols searches all open documents for symbols matching the query. +func (s *Server) workspaceSymbols(p workspaceSymbolParams) []WorkspaceSymbol { + if p.Query == "" { + return nil + } + query := strings.ToLower(p.Query) + var out []WorkspaceSymbol + for uri, text := range s.docs { + f, _ := parser.Parse(uriPath(uri), text) + if f == nil { + continue + } + for _, d := range f.Decls { + switch dd := d.(type) { + case *ast.Text: + if strings.Contains(strings.ToLower(dd.Name.Name), query) { + out = append(out, WorkspaceSymbol{ + Name: dd.Name.Name, + Kind: symFunction, + Location: Location{URI: uri, Range: symRange(dd.Name)}, + }) + } + case *ast.Globl: + if dd.Name != nil && strings.Contains(strings.ToLower(dd.Name.Name), query) { + out = append(out, WorkspaceSymbol{ + Name: dd.Name.Name, + Kind: symConstant, + Location: Location{URI: uri, Range: symRange(dd.Name)}, + }) + } + case *ast.Data: + if dd.Name != nil && strings.Contains(strings.ToLower(dd.Name.Name), query) { + out = append(out, WorkspaceSymbol{ + Name: dd.Name.Name, + Kind: symConstant, + Location: Location{URI: uri, Range: symRange(dd.Name)}, + }) + } + } + } + } + return out +} + // documentSymbols returns functions and their labels, plus global symbols. func (s *Server) documentSymbols(p documentSymbolParams) []DocumentSymbol { text := s.docs[p.TextDocument.URI] diff --git a/lsp/protocol.go b/lsp/protocol.go index 0e087a6..279a4c7 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -312,6 +312,7 @@ type ServerCapabilities struct { DocumentHighlightProvider bool `json:"documentHighlightProvider,omitempty"` CodeActionProvider bool `json:"codeActionProvider,omitempty"` SignatureHelpProvider map[string]any `json:"signatureHelpProvider,omitempty"` + WorkspaceSymbolProvider bool `json:"workspaceSymbolProvider,omitempty"` SemanticTokensProvider map[string]any `json:"semanticTokensProvider,omitempty"` InlayHintProvider bool `json:"inlayHintProvider,omitempty"` DiagnosticProvider map[string]any `json:"diagnosticProvider,omitempty"` @@ -378,6 +379,19 @@ const ( highlightWrite = 2 ) +// --- workspace symbols ------------------------------------------------------- + +type workspaceSymbolParams struct { + Query string `json:"query"` +} + +// WorkspaceSymbol represents a symbol found across the workspace. +type WorkspaceSymbol struct { + Name string `json:"name"` + Kind int `json:"kind"` + Location Location `json:"location"` +} + type initializeResult struct { Capabilities ServerCapabilities `json:"capabilities"` ServerInfo map[string]string `json:"serverInfo,omitempty"` diff --git a/lsp/server.go b/lsp/server.go index 1b02e02..31e5e86 100644 --- a/lsp/server.go +++ b/lsp/server.go @@ -135,6 +135,7 @@ func (s *Server) dispatch(msg *rpcMessage) (exit bool) { DocumentHighlightProvider: true, CodeActionProvider: true, SignatureHelpProvider: map[string]any{"triggerCharacters": []string{"("}}, + WorkspaceSymbolProvider: true, }, ServerInfo: map[string]string{"name": "gasm", "version": "0.31.1"}, }) @@ -228,6 +229,11 @@ func (s *Server) dispatch(msg *rpcMessage) (exit bool) { json.Unmarshal(msg.Params, &p) s.respond(msg.ID, s.semanticTokens(p)) + case "workspace/symbol": + var p workspaceSymbolParams + json.Unmarshal(msg.Params, &p) + s.respond(msg.ID, s.workspaceSymbols(p)) + case "shutdown": s.respond(msg.ID, nil) diff --git a/lsp/server_test.go b/lsp/server_test.go index eca0af7..07d70c4 100644 --- a/lsp/server_test.go +++ b/lsp/server_test.go @@ -450,6 +450,31 @@ func TestDocumentHighlights(t *testing.T) { } } +func TestWorkspaceSymbols(t *testing.T) { + in := session("file:///f_amd64.s", cleanDoc) + + frame(13, "workspace/symbol", map[string]any{ + "query": "foo", + }) + frame(nil, "exit", nil) + msgs := run(t, in) + resp := findByID(msgs, 13) + if resp == nil { + t.Fatal("no workspace/symbol response") + } + var syms []WorkspaceSymbol + if err := json.Unmarshal(mustResult(t, resp), &syms); err != nil { + t.Fatal(err) + } + found := false + for _, s := range syms { + if s.Name == "foo" { + found = true + } + } + if !found { + t.Fatalf("want symbol foo in results, got %+v", syms) + } +} + func TestMethodNotFound(t *testing.T) { in := frame(9, "bogus/method", map[string]any{}) + frame(nil, "exit", nil) msgs := run(t, in)