diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 5eda12b..b7d9bbf 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -21,8 +21,10 @@ jobs: # test tries to retrieve non-existent node # TestParser_NodeFieldsConsistency: # Vars.Content only includes name, not whole body + # TestCollector_CollectByTreeSitter_Java + # JDTLS download was rate-limited on github actions SKIPPED_TESTS: >- - TestPatcher|TestCases|Test_goParser_ParseNode|TestParser_NodeFieldsConsistency|TestRustLSP + TestPatcher|TestCases|Test_goParser_ParseNode|TestParser_NodeFieldsConsistency|TestRustLSP|TestCollector_CollectByTreeSitter_Java steps: - name: Checkout code diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 5709528..9d80631 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -18,6 +18,7 @@ jobs: DIFFJSON_IGNORE: > ['id'] ['Path'] + ['ToolVersion'] ['Modules']['a.b/c']['Dependencies']['a.b/c'] ['Modules']['a.b/c/cmdx']['Dependencies']['a.b/c/cmdx'] steps: diff --git a/lang/lsp/client.go b/lang/lsp/client.go index 4afe4ad..f4e3767 100644 --- a/lang/lsp/client.go +++ b/lang/lsp/client.go @@ -56,7 +56,7 @@ func NewLSPClient(repo string, openfile string, wait time.Duration, opts ClientO return nil, err } - cli, err := initLSPClient(context.Background(), svr, NewURI(repo), opts.Verbose, opts.InitializationOptions) + cli, err := initLSPClient(context.Background(), svr, NewURI(repo), opts.Verbose, opts.Language, opts.InitializationOptions) if err != nil { return nil, err } @@ -122,7 +122,7 @@ func (c *LSPClient) InitFiles() { } } -func initLSPClient(ctx context.Context, svr io.ReadWriteCloser, dir DocumentURI, verbose bool, InitializationOptions interface{}) (*LSPClient, error) { +func initLSPClient(ctx context.Context, svr io.ReadWriteCloser, dir DocumentURI, verbose bool, language uniast.Language, InitializationOptions interface{}) (*LSPClient, error) { h := newLSPHandler() stream := jsonrpc2.NewBufferedStream(svr, jsonrpc2.VSCodeObjectCodec{}) conn := jsonrpc2.NewConn(ctx, stream, h) @@ -141,8 +141,12 @@ func initLSPClient(ctx context.Context, svr io.ReadWriteCloser, dir DocumentURI, "dynamicRegistration": true, }, }, - "documentSymbol": map[string]interface{}{ - "hierarchicalDocumentSymbolSupport": true, + "textDocument": map[string]interface{}{ + "documentSymbol": map[string]interface{}{ + // Java uses tree-sitter instead of hierarchical symbols + // Golang stays the same as older versions. ABCoder do not use gopls, so don't play with it. + "hierarchicalDocumentSymbolSupport": (language != uniast.Java && language != uniast.Golang), + }, }, } diff --git a/lang/lsp/lsp.go b/lang/lsp/lsp.go index 39c6286..92c7b62 100644 --- a/lang/lsp/lsp.go +++ b/lang/lsp/lsp.go @@ -184,12 +184,18 @@ type DocumentSymbol struct { Name string `json:"name"` Kind SymbolKind `json:"kind"` Tags []json.RawMessage `json:"tags"` - Location Location `json:"location"` Children []*DocumentSymbol `json:"children"` Text string `json:"text"` Tokens []Token `json:"tokens"` Node *sitter.Node `json:"-"` Role SymbolRole `json:"-"` + + // Older LSPs might return SymbolInformation[] which have `Location`. + // Newer LSPs return DocumentSymbol[] which have `Range` and `SelectionRange`. + // ABCoder uses `Location`, and converts `Range` to `Location` when needed. + Location Location `json:"location"` + Range *Range `json:"range"` + SelectionRange *Range `json:"selectionRange"` } type TextDocumentPositionParams struct { diff --git a/lang/lsp/lsp_methods.go b/lang/lsp/lsp_methods.go index 99b3e0a..e2d9479 100644 --- a/lang/lsp/lsp_methods.go +++ b/lang/lsp/lsp_methods.go @@ -63,6 +63,44 @@ func (cli *LSPClient) DidOpen(ctx context.Context, file DocumentURI) (*TextDocum return f, nil } +func flattenDocumentSymbols(symbols []*DocumentSymbol, uri DocumentURI) []*DocumentSymbol { + var result []*DocumentSymbol + for _, sym := range symbols { + var location Location + if sym.Range != nil { + location = Location{ + URI: uri, + Range: *sym.Range, + } + } else { + location = sym.Location + } + flatSymbol := DocumentSymbol{ + // copy + Name: sym.Name, + Kind: sym.Kind, + Tags: sym.Tags, + Text: sym.Text, + Tokens: sym.Tokens, + Node: sym.Node, + Children: sym.Children, + // new + Location: location, + // empty + Role: 0, + Range: nil, + SelectionRange: nil, + } + result = append(result, &flatSymbol) + + if len(sym.Children) > 0 { + childSymbols := flattenDocumentSymbols(sym.Children, uri) + result = append(result, childSymbols...) + } + } + return result +} + func (cli *LSPClient) DocumentSymbols(ctx context.Context, file DocumentURI) (map[Range]*DocumentSymbol, error) { // open file first f, err := cli.DidOpen(ctx, file) @@ -78,14 +116,15 @@ func (cli *LSPClient) DocumentSymbols(ctx context.Context, file DocumentURI) (ma URI: uri, }, } - var resp []DocumentSymbol + var resp []*DocumentSymbol if err := cli.Call(ctx, "textDocument/documentSymbol", req, &resp); err != nil { return nil, err } + respFlatten := flattenDocumentSymbols(resp, file) // cache symbols - f.Symbols = make(map[Range]*DocumentSymbol, len(resp)) - for i := range resp { - s := &resp[i] + f.Symbols = make(map[Range]*DocumentSymbol, len(respFlatten)) + for i := range respFlatten { + s := respFlatten[i] f.Symbols[s.Location.Range] = s } return f.Symbols, nil diff --git a/lang/python/lib.go b/lang/python/lib.go index 5dbe5db..3441556 100644 --- a/lang/python/lib.go +++ b/lang/python/lib.go @@ -59,7 +59,7 @@ func CheckPythonVersion() error { func InstallLanguageServer() (string, error) { if out, err := exec.Command("pylsp", "--version").CombinedOutput(); err == nil { - log.Info("pylsp already installed: %v", out) + log.Info("pylsp already installed: %v", string(out)) return lspName, nil } if err := CheckPythonVersion(); err != nil {