Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hover): POC for inline hover with rust #150

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ require (
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smacker/go-tree-sitter v0.0.0-20240402012804-99ab967cf9b9 // indirect
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smacker/go-tree-sitter v0.0.0-20240402012804-99ab967cf9b9 h1:5HSGLeLdHwoLEEr794DtHfFD67aF4rPLLQFfbVvEF2w=
github.com/smacker/go-tree-sitter v0.0.0-20240402012804-99ab967cf9b9/go.mod h1:q99oHDsbP0xRwmn7Vmob8gbSMNyvJ83OauXPSuHQuKE=
github.com/sourcegraph/jsonrpc2 v0.2.0 h1:KjN/dC4fP6aN9030MZCJs9WQbTOjWHhrtKVpzzSrr/U=
github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -233,6 +235,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
Expand Down
4 changes: 4 additions & 0 deletions internal/handler/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func (s *Server) handleTextDocumentFormatting(ctx context.Context, conn *jsonrpc
return nil, fmt.Errorf("document not found: %s", params.TextDocument.URI)
}

if f.LanguageID != `sql` {
return nil, nil
}

textEdits, err := formatter.Format(f.Text, params, s.getConfig())
if err != nil {
return nil, err
Expand Down
3 changes: 2 additions & 1 deletion internal/handler/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ func (s *Server) handleTextDocumentHover(ctx context.Context, conn *jsonrpc2.Con
return nil, fmt.Errorf("document not found: %s", params.TextDocument.URI)
}

res, err := hover(f.Text, params, s.worker.Cache())
text := cleanInjections(f, params.Position)
res, err := hover(text, params, s.worker.Cache())
if err != nil {
if errors.Is(ErrNoHover, err) {
return nil, nil
Expand Down
21 changes: 21 additions & 0 deletions internal/handler/injection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package handler

import (
"testing"

"github.com/sqls-server/sqls/internal/lsp"
)

func TestCleanRustInjections(t *testing.T) {
file := &File{
LanguageID: `rust`,
Text: `fn main() { sqlx::query("select * from users").fetch_all(&mut conn).await; }`,
}
result := cleanRustInjections(file, lsp.Position{Line: 0, Character: 0})

expected := ` select * from users `

if result != expected {
t.Errorf("got %s, want %s", result, expected)
}
}
77 changes: 77 additions & 0 deletions internal/handler/injections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package handler

import (
"context"
"fmt"

sitter "github.com/smacker/go-tree-sitter"
"github.com/smacker/go-tree-sitter/rust"
"github.com/sqls-server/sqls/internal/lsp"
)

func cleanInjections(file *File, position lsp.Position) string {
switch file.LanguageID {
case `sql`:
return file.Text
case `rust`:
return cleanRustInjections(file, position)

default:
return file.Text
}
}

func cleanRustInjections(file *File, position lsp.Position) string {
result := make([]byte, len(file.Text))
for i := range result {
if file.Text[i] == '\n' || file.Text[i] == '\r' {
result[i] = file.Text[i]
continue
}
result[i] = byte(' ')
}

lang := rust.GetLanguage()
tree, err := sitter.ParseCtx(context.Background(), []byte(file.Text), lang)
if err != nil {
return file.Text
}

// TODO: this would need to be configurable
query := `((call_expression
function: (scoped_identifier
path: ((identifier) @_sqlx
(#eq? @_sqlx "sqlx"))
name: (identifier) @_query
(#eq? @_query "query"))
arguments: (arguments
((string_literal)
@injection.content))))`

q, err := sitter.NewQuery([]byte(query), lang)
if err != nil {
return file.Text
}
qc := sitter.NewQueryCursor()
qc.Exec(q, tree)
// Iterate over query results
for {
m, ok := qc.NextMatch()
if !ok {
break
}
// Apply predicates filtering
m = qc.FilterPredicates(m, []byte(file.Text))
for _, c := range m.Captures {
fmt.Println(c.Node.String())
fmt.Println(c.Node.Content([]byte(file.Text)))
captureName := q.CaptureNameForId(c.Index)
if captureName == "injection.content" {
// TODO: add a check if the position is inside the injection
copy(result[c.Node.StartByte()+1:c.Node.EndByte()-1],
file.Text[c.Node.StartByte()+1:c.Node.EndByte()-1])
}
}
}
return string(result)
}