@@ -4,8 +4,13 @@ import (
4
4
"context"
5
5
"strings"
6
6
7
+ "github.com/google/go-jsonnet/ast"
8
+ "github.com/grafana/jsonnet-language-server/pkg/ast/processing"
9
+ "github.com/grafana/jsonnet-language-server/pkg/nodestack"
10
+ position "github.com/grafana/jsonnet-language-server/pkg/position_conversion"
7
11
"github.com/grafana/jsonnet-language-server/pkg/utils"
8
12
"github.com/jdbaldry/go-language-server-protocol/lsp/protocol"
13
+ log "github.com/sirupsen/logrus"
9
14
)
10
15
11
16
func (s * Server ) Completion (ctx context.Context , params * protocol.CompletionParams ) (* protocol.CompletionList , error ) {
@@ -14,14 +19,98 @@ func (s *Server) Completion(ctx context.Context, params *protocol.CompletionPara
14
19
return nil , utils .LogErrorf ("Completion: %s: %w" , errorRetrievingDocument , err )
15
20
}
16
21
17
- items := []protocol.CompletionItem {}
22
+ line := getCompletionLine (doc .item .Text , params .Position )
23
+
24
+ // Short-circuit if it's a stdlib completion
25
+ if items := s .completionStdLib (line ); len (items ) > 0 {
26
+ return & protocol.CompletionList {IsIncomplete : false , Items : items }, nil
27
+ }
28
+
29
+ // Otherwise, parse the AST and search for completions
30
+ if doc .ast == nil {
31
+ log .Errorf ("Completion: document was never successfully parsed, can't autocomplete" )
32
+ return nil , nil
33
+ }
34
+
35
+ searchStack , err := processing .FindNodeByPosition (doc .ast , position .ProtocolToAST (params .Position ))
36
+ if err != nil {
37
+ log .Errorf ("Completion: error computing node: %v" , err )
38
+ return nil , nil
39
+ }
40
+
41
+ items := s .completionFromStack (line , searchStack )
42
+ return & protocol.CompletionList {IsIncomplete : false , Items : items }, nil
43
+ }
18
44
19
- line := strings .Split (doc .item .Text , "\n " )[params .Position .Line ]
20
- charIndex := int (params .Position .Character )
45
+ func getCompletionLine (fileContent string , position protocol.Position ) string {
46
+ line := strings .Split (fileContent , "\n " )[position .Line ]
47
+ charIndex := int (position .Character )
21
48
if charIndex > len (line ) {
22
49
charIndex = len (line )
23
50
}
24
51
line = line [:charIndex ]
52
+ return line
53
+ }
54
+
55
+ func (s * Server ) completionFromStack (line string , stack * nodestack.NodeStack ) []protocol.CompletionItem {
56
+ var items []protocol.CompletionItem
57
+
58
+ lineWords := strings .Split (line , " " )
59
+ lastWord := lineWords [len (lineWords )- 1 ]
60
+
61
+ indexes := strings .Split (lastWord , "." )
62
+ firstIndex , indexes := indexes [0 ], indexes [1 :]
63
+
64
+ if firstIndex == "self" && len (indexes ) > 0 {
65
+ fieldPrefix := indexes [0 ]
66
+
67
+ for ! stack .IsEmpty () {
68
+ curr := stack .Pop ()
69
+
70
+ switch curr := curr .(type ) {
71
+ case * ast.Binary :
72
+ stack .Push (curr .Left )
73
+ stack .Push (curr .Right )
74
+ case * ast.DesugaredObject :
75
+ for _ , field := range curr .Fields {
76
+ label := processing .FieldNameToString (field .Name )
77
+ // Ignore fields that don't match the prefix
78
+ if ! strings .HasPrefix (label , fieldPrefix ) {
79
+ continue
80
+ }
81
+
82
+ // Ignore the current field
83
+ if strings .Contains (line , label + ":" ) {
84
+ continue
85
+ }
86
+
87
+ items = append (items , createCompletionItem (label , "self." + label , protocol .FieldCompletion , field .Body ))
88
+ }
89
+ }
90
+ }
91
+ } else if len (indexes ) == 0 {
92
+ // firstIndex is a variable (local) completion
93
+ for ! stack .IsEmpty () {
94
+ if curr , ok := stack .Pop ().(* ast.Local ); ok {
95
+ for _ , bind := range curr .Binds {
96
+ label := string (bind .Variable )
97
+
98
+ if ! strings .HasPrefix (label , firstIndex ) {
99
+ continue
100
+ }
101
+
102
+ items = append (items , createCompletionItem (label , label , protocol .VariableCompletion , bind .Body ))
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ return items
109
+ }
110
+
111
+ func (s * Server ) completionStdLib (line string ) []protocol.CompletionItem {
112
+ items := []protocol.CompletionItem {}
113
+
25
114
stdIndex := strings .LastIndex (line , "std." )
26
115
if stdIndex != - 1 {
27
116
userInput := line [stdIndex + 4 :]
@@ -55,5 +144,26 @@ func (s *Server) Completion(ctx context.Context, params *protocol.CompletionPara
55
144
items = append (items , funcContains ... )
56
145
}
57
146
58
- return & protocol.CompletionList {IsIncomplete : false , Items : items }, nil
147
+ return items
148
+ }
149
+
150
+ func createCompletionItem (label , detail string , kind protocol.CompletionItemKind , body ast.Node ) protocol.CompletionItem {
151
+ insertText := label
152
+ if asFunc , ok := body .(* ast.Function ); ok {
153
+ kind = protocol .FunctionCompletion
154
+ params := []string {}
155
+ for _ , param := range asFunc .Parameters {
156
+ params = append (params , string (param .Name ))
157
+ }
158
+ paramsString := "(" + strings .Join (params , ", " ) + ")"
159
+ detail += paramsString
160
+ insertText += paramsString
161
+ }
162
+
163
+ return protocol.CompletionItem {
164
+ Label : label ,
165
+ Detail : detail ,
166
+ Kind : kind ,
167
+ InsertText : insertText ,
168
+ }
59
169
}
0 commit comments