Skip to content

Commit

Permalink
Merge pull request #51 from lighttiger2505/add-rename
Browse files Browse the repository at this point in the history
feat: Add text document rename
  • Loading branch information
lighttiger2505 authored Mar 19, 2021
2 parents 0546a76 + a1b45a3 commit c81e67e
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 0 deletions.
3 changes: 3 additions & 0 deletions internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ func (s *Server) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.
return s.handleTextDocumentRangeFormatting(ctx, conn, req)
case "textDocument/signatureHelp":
return s.handleTextDocumentSignatureHelp(ctx, conn, req)
case "textDocument/rename":
return s.handleTextDocumentRename(ctx, conn, req)
}
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: fmt.Sprintf("method not supported: %s", req.Method)}
}
Expand Down Expand Up @@ -150,6 +152,7 @@ func (s *Server) handleInitialize(ctx context.Context, conn *jsonrpc2.Conn, req
DefinitionProvider: false,
DocumentFormattingProvider: true,
DocumentRangeFormattingProvider: true,
RenameProvider: true,
},
}

Expand Down
1 change: 1 addition & 0 deletions internal/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func TestInitialized(t *testing.T) {
DefinitionProvider: false,
DocumentFormattingProvider: true,
DocumentRangeFormattingProvider: true,
RenameProvider: true,
},
}
var got lsp.InitializeResult
Expand Down
110 changes: 110 additions & 0 deletions internal/handler/rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package handler

import (
"context"
"encoding/json"
"fmt"

"github.com/lighttiger2505/sqls/ast"
"github.com/lighttiger2505/sqls/ast/astutil"
"github.com/lighttiger2505/sqls/internal/lsp"
"github.com/lighttiger2505/sqls/parser"
"github.com/lighttiger2505/sqls/parser/parseutil"
"github.com/lighttiger2505/sqls/token"
"github.com/sourcegraph/jsonrpc2"
)

func (s *Server) handleTextDocumentRename(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
if req.Params == nil {
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
}

var params lsp.RenameParams
if err := json.Unmarshal(*req.Params, &params); err != nil {
return nil, err
}

f, ok := s.files[params.TextDocument.URI]
if !ok {
return nil, fmt.Errorf("document not found: %s", params.TextDocument.URI)
}

res, err := rename(f.Text, params)
if err != nil {
return nil, err
}
return res, nil
}

func rename(text string, params lsp.RenameParams) (*lsp.WorkspaceEdit, error) {
parsed, err := parser.Parse(text)
if err != nil {
return nil, err
}

pos := token.Pos{
Line: params.Position.Line,
Col: params.Position.Character,
}

// Get the identifer on focus
nodeWalker := parseutil.NewNodeWalker(parsed, pos)
m := astutil.NodeMatcher{
NodeTypes: []ast.NodeType{ast.TypeIdentifer},
}
currentVariable := nodeWalker.CurNodeButtomMatched(m)
if currentVariable == nil {
return nil, nil
}

// Get all identifiers in the statement
idents, err := parseutil.ExtractIdenfiers(parsed, pos)
if err != nil {
return nil, err
}

// Extract only those with matching names
renameTarget := []ast.Node{}
for _, ident := range idents {
if ident.String() == currentVariable.String() {
renameTarget = append(renameTarget, ident)
}
}
if len(renameTarget) == 0 {
return nil, nil
}

edits := make([]lsp.TextEdit, len(renameTarget))
for i, target := range renameTarget {
edit := lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{
Line: target.Pos().Line,
Character: target.Pos().Col,
},
End: lsp.Position{
Line: target.End().Line,
Character: target.End().Col,
},
},
NewText: params.NewName,
}
edits[i] = edit
}

res := &lsp.WorkspaceEdit{
DocumentChanges: []lsp.TextDocumentEdit{
{
TextDocument: lsp.OptionalVersionedTextDocumentIdentifier{
Version: 0,
TextDocumentIdentifier: lsp.TextDocumentIdentifier{
URI: params.TextDocument.URI,
},
},
Edits: edits,
},
},
}

return res, nil
}
118 changes: 118 additions & 0 deletions internal/handler/rename_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package handler

import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/lighttiger2505/sqls/internal/config"
"github.com/lighttiger2505/sqls/internal/database"
"github.com/lighttiger2505/sqls/internal/lsp"
)

var renameTestCases = []struct {
name string
input string
newName string
output lsp.WorkspaceEdit
pos lsp.Position
}{
{
name: "ok",
input: "SELECT ci.ID, ci.Name FROM city as ci",
newName: "ct",
output: lsp.WorkspaceEdit{
DocumentChanges: []lsp.TextDocumentEdit{
{
TextDocument: lsp.OptionalVersionedTextDocumentIdentifier{
Version: 0,
TextDocumentIdentifier: lsp.TextDocumentIdentifier{
URI: "file:///Users/octref/Code/css-test/test.sql",
},
},
Edits: []lsp.TextEdit{
{
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Character: 7,
},
End: lsp.Position{
Line: 0,
Character: 9,
},
},
NewText: "ct",
},
{
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Character: 14,
},
End: lsp.Position{
Line: 0,
Character: 16,
},
},
NewText: "ct",
},
{
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Character: 35,
},
End: lsp.Position{
Line: 0,
Character: 37,
},
},
NewText: "ct",
},
},
},
},
},
pos: lsp.Position{
Line: 0,
Character: 8,
},
},
}

func TestRenameMain(t *testing.T) {
tx := newTestContext()
tx.setup(t)
defer tx.tearDown()

cfg := &config.Config{
Connections: []*database.DBConfig{
{Driver: "mock"},
},
}
tx.addWorkspaceConfig(t, cfg)

for _, tt := range renameTestCases {
t.Run(tt.name, func(t *testing.T) {
tx.textDocumentDidOpen(t, testFileURI, tt.input)

params := lsp.RenameParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: testFileURI,
},
Position: tt.pos,
NewName: tt.newName,
}
var got lsp.WorkspaceEdit
err := tx.conn.Call(tx.ctx, "textDocument/rename", params, &got)
if err != nil {
t.Errorf("conn.Call textDocument/rename: %+v", err)
return
}

if diff := cmp.Diff(tt.output, got); diff != "" {
t.Errorf("unmatch rename edits (- want, + got):\n%s", diff)
}
})
}
}
67 changes: 67 additions & 0 deletions internal/lsp/lsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,70 @@ var (
Info MessageType = 3
Log MessageType = 4
)

type RenameParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
Position Position `json:"position"`
NewName string `json:"newName"`
WorkDoneProgressParams
}

type RenameFile struct {
Kind string `json:"kind"`
OldURI DocumentURI `json:"oldUri"`
NewURI DocumentURI `json:"newUri"`
Options RenameFileOptions `json:"options,omitempty"`
ResourceOperation
}

type RenameClientCapabilities struct {
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
PrepareSupport bool `json:"prepareSupport,omitempty"`
PrepareSupportDefaultBehavior PrepareSupportDefaultBehavior `json:"prepareSupportDefaultBehavior,omitempty"`
HonorsChangeAnnotations bool `json:"honorsChangeAnnotations,omitempty"`
}

type RenameFileOptions struct {
Overwrite bool `json:"overwrite,omitempty"`
IgnoreIfExists bool `json:"ignoreIfExists,omitempty"`
}

type RenameFilesParams struct {
Files []FileRename `json:"files"`
}

type RenameOptions struct {
PrepareProvider bool `json:"prepareProvider,omitempty"`
WorkDoneProgressOptions
}

type FileRename struct {
OldURI string `json:"oldUri"`
NewURI string `json:"newUri"`
}

type DocumentURI string
type PrepareSupportDefaultBehavior = interface{}

type ResourceOperation struct {
Kind string `json:"kind"`
AnnotationID ChangeAnnotationIdentifier `json:"annotationId,omitempty"`
}

type ChangeAnnotationIdentifier = string

type WorkspaceEdit struct {
Changes map[string][]TextEdit `json:"changes,omitempty"`
DocumentChanges []TextDocumentEdit `json:"documentChanges,omitempty"`
ChangeAnnotations map[string]ChangeAnnotationIdentifier `json:"changeAnnotations,omitempty"`
}

type TextDocumentEdit struct {
TextDocument OptionalVersionedTextDocumentIdentifier `json:"textDocument"`
Edits []TextEdit `json:"edits"`
}

type OptionalVersionedTextDocumentIdentifier struct {
Version int32 `json:"version"`
TextDocumentIdentifier
}
25 changes: 25 additions & 0 deletions parser/parseutil/idenfier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package parseutil

import (
"github.com/lighttiger2505/sqls/ast"
"github.com/lighttiger2505/sqls/ast/astutil"
"github.com/lighttiger2505/sqls/token"
)

func ExtractIdenfiers(parsed ast.TokenList, pos token.Pos) ([]ast.Node, error) {
stmt, err := extractFocusedStatement(parsed, pos)
if err != nil {
return nil, err
}

identiferMatcher := astutil.NodeMatcher{
NodeTypes: []ast.NodeType{
ast.TypeIdentifer,
},
}
return parsePrefix(astutil.NewNodeReader(stmt), identiferMatcher, parseIdentifer), nil
}

func parseIdentifer(reader *astutil.NodeReader) []ast.Node {
return []ast.Node{reader.CurNode}
}

0 comments on commit c81e67e

Please sign in to comment.