Skip to content

Commit c81e67e

Browse files
Merge pull request #51 from lighttiger2505/add-rename
feat: Add text document rename
2 parents 0546a76 + a1b45a3 commit c81e67e

File tree

6 files changed

+324
-0
lines changed

6 files changed

+324
-0
lines changed

internal/handler/handler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ func (s *Server) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.
118118
return s.handleTextDocumentRangeFormatting(ctx, conn, req)
119119
case "textDocument/signatureHelp":
120120
return s.handleTextDocumentSignatureHelp(ctx, conn, req)
121+
case "textDocument/rename":
122+
return s.handleTextDocumentRename(ctx, conn, req)
121123
}
122124
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: fmt.Sprintf("method not supported: %s", req.Method)}
123125
}
@@ -150,6 +152,7 @@ func (s *Server) handleInitialize(ctx context.Context, conn *jsonrpc2.Conn, req
150152
DefinitionProvider: false,
151153
DocumentFormattingProvider: true,
152154
DocumentRangeFormattingProvider: true,
155+
RenameProvider: true,
153156
},
154157
}
155158

internal/handler/handler_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func TestInitialized(t *testing.T) {
121121
DefinitionProvider: false,
122122
DocumentFormattingProvider: true,
123123
DocumentRangeFormattingProvider: true,
124+
RenameProvider: true,
124125
},
125126
}
126127
var got lsp.InitializeResult

internal/handler/rename.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/lighttiger2505/sqls/ast"
9+
"github.com/lighttiger2505/sqls/ast/astutil"
10+
"github.com/lighttiger2505/sqls/internal/lsp"
11+
"github.com/lighttiger2505/sqls/parser"
12+
"github.com/lighttiger2505/sqls/parser/parseutil"
13+
"github.com/lighttiger2505/sqls/token"
14+
"github.com/sourcegraph/jsonrpc2"
15+
)
16+
17+
func (s *Server) handleTextDocumentRename(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
18+
if req.Params == nil {
19+
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
20+
}
21+
22+
var params lsp.RenameParams
23+
if err := json.Unmarshal(*req.Params, &params); err != nil {
24+
return nil, err
25+
}
26+
27+
f, ok := s.files[params.TextDocument.URI]
28+
if !ok {
29+
return nil, fmt.Errorf("document not found: %s", params.TextDocument.URI)
30+
}
31+
32+
res, err := rename(f.Text, params)
33+
if err != nil {
34+
return nil, err
35+
}
36+
return res, nil
37+
}
38+
39+
func rename(text string, params lsp.RenameParams) (*lsp.WorkspaceEdit, error) {
40+
parsed, err := parser.Parse(text)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
pos := token.Pos{
46+
Line: params.Position.Line,
47+
Col: params.Position.Character,
48+
}
49+
50+
// Get the identifer on focus
51+
nodeWalker := parseutil.NewNodeWalker(parsed, pos)
52+
m := astutil.NodeMatcher{
53+
NodeTypes: []ast.NodeType{ast.TypeIdentifer},
54+
}
55+
currentVariable := nodeWalker.CurNodeButtomMatched(m)
56+
if currentVariable == nil {
57+
return nil, nil
58+
}
59+
60+
// Get all identifiers in the statement
61+
idents, err := parseutil.ExtractIdenfiers(parsed, pos)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
// Extract only those with matching names
67+
renameTarget := []ast.Node{}
68+
for _, ident := range idents {
69+
if ident.String() == currentVariable.String() {
70+
renameTarget = append(renameTarget, ident)
71+
}
72+
}
73+
if len(renameTarget) == 0 {
74+
return nil, nil
75+
}
76+
77+
edits := make([]lsp.TextEdit, len(renameTarget))
78+
for i, target := range renameTarget {
79+
edit := lsp.TextEdit{
80+
Range: lsp.Range{
81+
Start: lsp.Position{
82+
Line: target.Pos().Line,
83+
Character: target.Pos().Col,
84+
},
85+
End: lsp.Position{
86+
Line: target.End().Line,
87+
Character: target.End().Col,
88+
},
89+
},
90+
NewText: params.NewName,
91+
}
92+
edits[i] = edit
93+
}
94+
95+
res := &lsp.WorkspaceEdit{
96+
DocumentChanges: []lsp.TextDocumentEdit{
97+
{
98+
TextDocument: lsp.OptionalVersionedTextDocumentIdentifier{
99+
Version: 0,
100+
TextDocumentIdentifier: lsp.TextDocumentIdentifier{
101+
URI: params.TextDocument.URI,
102+
},
103+
},
104+
Edits: edits,
105+
},
106+
},
107+
}
108+
109+
return res, nil
110+
}

internal/handler/rename_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package handler
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
"github.com/lighttiger2505/sqls/internal/config"
8+
"github.com/lighttiger2505/sqls/internal/database"
9+
"github.com/lighttiger2505/sqls/internal/lsp"
10+
)
11+
12+
var renameTestCases = []struct {
13+
name string
14+
input string
15+
newName string
16+
output lsp.WorkspaceEdit
17+
pos lsp.Position
18+
}{
19+
{
20+
name: "ok",
21+
input: "SELECT ci.ID, ci.Name FROM city as ci",
22+
newName: "ct",
23+
output: lsp.WorkspaceEdit{
24+
DocumentChanges: []lsp.TextDocumentEdit{
25+
{
26+
TextDocument: lsp.OptionalVersionedTextDocumentIdentifier{
27+
Version: 0,
28+
TextDocumentIdentifier: lsp.TextDocumentIdentifier{
29+
URI: "file:///Users/octref/Code/css-test/test.sql",
30+
},
31+
},
32+
Edits: []lsp.TextEdit{
33+
{
34+
Range: lsp.Range{
35+
Start: lsp.Position{
36+
Line: 0,
37+
Character: 7,
38+
},
39+
End: lsp.Position{
40+
Line: 0,
41+
Character: 9,
42+
},
43+
},
44+
NewText: "ct",
45+
},
46+
{
47+
Range: lsp.Range{
48+
Start: lsp.Position{
49+
Line: 0,
50+
Character: 14,
51+
},
52+
End: lsp.Position{
53+
Line: 0,
54+
Character: 16,
55+
},
56+
},
57+
NewText: "ct",
58+
},
59+
{
60+
Range: lsp.Range{
61+
Start: lsp.Position{
62+
Line: 0,
63+
Character: 35,
64+
},
65+
End: lsp.Position{
66+
Line: 0,
67+
Character: 37,
68+
},
69+
},
70+
NewText: "ct",
71+
},
72+
},
73+
},
74+
},
75+
},
76+
pos: lsp.Position{
77+
Line: 0,
78+
Character: 8,
79+
},
80+
},
81+
}
82+
83+
func TestRenameMain(t *testing.T) {
84+
tx := newTestContext()
85+
tx.setup(t)
86+
defer tx.tearDown()
87+
88+
cfg := &config.Config{
89+
Connections: []*database.DBConfig{
90+
{Driver: "mock"},
91+
},
92+
}
93+
tx.addWorkspaceConfig(t, cfg)
94+
95+
for _, tt := range renameTestCases {
96+
t.Run(tt.name, func(t *testing.T) {
97+
tx.textDocumentDidOpen(t, testFileURI, tt.input)
98+
99+
params := lsp.RenameParams{
100+
TextDocument: lsp.TextDocumentIdentifier{
101+
URI: testFileURI,
102+
},
103+
Position: tt.pos,
104+
NewName: tt.newName,
105+
}
106+
var got lsp.WorkspaceEdit
107+
err := tx.conn.Call(tx.ctx, "textDocument/rename", params, &got)
108+
if err != nil {
109+
t.Errorf("conn.Call textDocument/rename: %+v", err)
110+
return
111+
}
112+
113+
if diff := cmp.Diff(tt.output, got); diff != "" {
114+
t.Errorf("unmatch rename edits (- want, + got):\n%s", diff)
115+
}
116+
})
117+
}
118+
}

internal/lsp/lsp.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,3 +419,70 @@ var (
419419
Info MessageType = 3
420420
Log MessageType = 4
421421
)
422+
423+
type RenameParams struct {
424+
TextDocument TextDocumentIdentifier `json:"textDocument"`
425+
Position Position `json:"position"`
426+
NewName string `json:"newName"`
427+
WorkDoneProgressParams
428+
}
429+
430+
type RenameFile struct {
431+
Kind string `json:"kind"`
432+
OldURI DocumentURI `json:"oldUri"`
433+
NewURI DocumentURI `json:"newUri"`
434+
Options RenameFileOptions `json:"options,omitempty"`
435+
ResourceOperation
436+
}
437+
438+
type RenameClientCapabilities struct {
439+
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
440+
PrepareSupport bool `json:"prepareSupport,omitempty"`
441+
PrepareSupportDefaultBehavior PrepareSupportDefaultBehavior `json:"prepareSupportDefaultBehavior,omitempty"`
442+
HonorsChangeAnnotations bool `json:"honorsChangeAnnotations,omitempty"`
443+
}
444+
445+
type RenameFileOptions struct {
446+
Overwrite bool `json:"overwrite,omitempty"`
447+
IgnoreIfExists bool `json:"ignoreIfExists,omitempty"`
448+
}
449+
450+
type RenameFilesParams struct {
451+
Files []FileRename `json:"files"`
452+
}
453+
454+
type RenameOptions struct {
455+
PrepareProvider bool `json:"prepareProvider,omitempty"`
456+
WorkDoneProgressOptions
457+
}
458+
459+
type FileRename struct {
460+
OldURI string `json:"oldUri"`
461+
NewURI string `json:"newUri"`
462+
}
463+
464+
type DocumentURI string
465+
type PrepareSupportDefaultBehavior = interface{}
466+
467+
type ResourceOperation struct {
468+
Kind string `json:"kind"`
469+
AnnotationID ChangeAnnotationIdentifier `json:"annotationId,omitempty"`
470+
}
471+
472+
type ChangeAnnotationIdentifier = string
473+
474+
type WorkspaceEdit struct {
475+
Changes map[string][]TextEdit `json:"changes,omitempty"`
476+
DocumentChanges []TextDocumentEdit `json:"documentChanges,omitempty"`
477+
ChangeAnnotations map[string]ChangeAnnotationIdentifier `json:"changeAnnotations,omitempty"`
478+
}
479+
480+
type TextDocumentEdit struct {
481+
TextDocument OptionalVersionedTextDocumentIdentifier `json:"textDocument"`
482+
Edits []TextEdit `json:"edits"`
483+
}
484+
485+
type OptionalVersionedTextDocumentIdentifier struct {
486+
Version int32 `json:"version"`
487+
TextDocumentIdentifier
488+
}

parser/parseutil/idenfier.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package parseutil
2+
3+
import (
4+
"github.com/lighttiger2505/sqls/ast"
5+
"github.com/lighttiger2505/sqls/ast/astutil"
6+
"github.com/lighttiger2505/sqls/token"
7+
)
8+
9+
func ExtractIdenfiers(parsed ast.TokenList, pos token.Pos) ([]ast.Node, error) {
10+
stmt, err := extractFocusedStatement(parsed, pos)
11+
if err != nil {
12+
return nil, err
13+
}
14+
15+
identiferMatcher := astutil.NodeMatcher{
16+
NodeTypes: []ast.NodeType{
17+
ast.TypeIdentifer,
18+
},
19+
}
20+
return parsePrefix(astutil.NewNodeReader(stmt), identiferMatcher, parseIdentifer), nil
21+
}
22+
23+
func parseIdentifer(reader *astutil.NodeReader) []ast.Node {
24+
return []ast.Node{reader.CurNode}
25+
}

0 commit comments

Comments
 (0)