Skip to content

Commit

Permalink
Add snippets provider (#870)
Browse files Browse the repository at this point in the history
Snippets are special inserts that allow users to enter values and press tab
to move to the next location.

https://code.visualstudio.com/docs/editor/userdefinedsnippets

Add snippets for:
- `some v in coll`,
- `some k, v in coll`
- `every v in coll`
- `every k, v in coll`

Got interested in trying this after learniung that Zed supports snippets in
the latest release. I've tried and confirmed this works in both VS Code and
in Zed.

Signed-off-by: Anders Eknert <[email protected]>
  • Loading branch information
anderseknert committed Jun 25, 2024
1 parent 2fa61d7 commit 6cebb1c
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 5 deletions.
50 changes: 50 additions & 0 deletions bundle/regal/lsp/completion/providers/snippet/snippet.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package regal.lsp.completion.providers.snippet

import rego.v1

import data.regal.lsp.completion.kind
import data.regal.lsp.completion.location

items contains item if {
position := location.to_position(input.regal.context.location)
line := input.regal.file.lines[position.line]
word := location.word_at(line, input.regal.context.location.col)

location.in_rule_body(line)

some label, snippet in _snippets

strings.any_prefix_match(snippet.prefix, word.text)
not contains(line, snippet.prefix[0])
not endswith(trim_space(line), "=")

item := {
"label": sprintf("%s (snippet)", [label]),
"kind": kind.snippet,
"detail": label,
"textEdit": {
"range": location.word_range(word, position),
"newText": snippet.body,
},
"insertTextFormat": 2, # snippet
}
}

_snippets := {
"some value iteration": {
"body": "some ${1:var} in ${2:collection}\n$0",
"prefix": ["some"],
},
"some key-value iteration": {
"body": "some ${1:key}, ${2:value} in ${3:collection}\n$0",
"prefix": ["some", "some-kv"],
},
"every value iteration": {
"body": "every ${1:var} in ${2:collection} {\n\t$0\n}",
"prefix": ["every"],
},
"every key-value iteration": {
"body": "every ${1:key}, ${2:value} in ${3:collection} {\n\t$0\n}",
"prefix": ["every", "every-kv"],
},
}
110 changes: 110 additions & 0 deletions bundle/regal/lsp/completion/providers/snippet/snippet_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package regal.lsp.completion.providers.snippet_test

import rego.v1

import data.regal.lsp.completion.providers.snippet as provider
import data.regal.lsp.completion.providers.utils_test as util

# regal ignore:rule-length
test_snippet_completion_on_typing if {
policy := `package policy
import rego.v1
allow if {
e
}`
items := provider.items with input as util.input_with_location(policy, {"row": 6, "col": 2})
items == {
{
"detail": "every key-value iteration",
"insertTextFormat": 2,
"kind": 15,
"label": "every key-value iteration (snippet)",
"textEdit": {
"newText": "every ${1:key}, ${2:value} in ${3:collection} {\n\t$0\n}",
"range": {
"end": {"character": 2, "line": 5},
"start": {"character": 1, "line": 5},
},
},
},
{
"detail": "every value iteration",
"insertTextFormat": 2,
"kind": 15,
"label": "every value iteration (snippet)",
"textEdit": {
"newText": "every ${1:var} in ${2:collection} {\n\t$0\n}",
"range": {
"end": {"character": 2, "line": 5},
"start": {"character": 1, "line": 5},
},
},
},
}
}

# regal ignore:rule-length
test_snippet_completion_on_invoked if {
policy := `package policy
import rego.v1
allow if `
items := provider.items with input as util.input_with_location(policy, {"row": 5, "col": 10})
items == {
{
"detail": "every key-value iteration",
"insertTextFormat": 2,
"kind": 15,
"label": "every key-value iteration (snippet)",
"textEdit": {
"newText": "every ${1:key}, ${2:value} in ${3:collection} {\n\t$0\n}",
"range": {
"end": {"character": 9, "line": 4},
"start": {"character": 9, "line": 4},
},
},
},
{
"detail": "every value iteration",
"insertTextFormat": 2,
"kind": 15,
"label": "every value iteration (snippet)",
"textEdit": {
"newText": "every ${1:var} in ${2:collection} {\n\t$0\n}",
"range": {
"end": {"character": 9, "line": 4},
"start": {"character": 9, "line": 4},
},
},
},
{
"detail": "some key-value iteration",
"insertTextFormat": 2,
"kind": 15,
"label": "some key-value iteration (snippet)",
"textEdit": {
"newText": "some ${1:key}, ${2:value} in ${3:collection}\n$0",
"range": {
"end": {"character": 9, "line": 4},
"start": {"character": 9, "line": 4},
},
},
},
{
"detail": "some value iteration",
"insertTextFormat": 2,
"kind": 15,
"label": "some value iteration (snippet)",
"textEdit": {
"newText": "some ${1:var} in ${2:collection}\n$0",
"range": {
"end": {"character": 9, "line": 4},
"start": {"character": 9, "line": 4},
},
},
},
}
}
11 changes: 6 additions & 5 deletions internal/lsp/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,12 @@ type CompletionItem struct {
Label string `json:"label"`
LabelDetails *CompletionItemLabelDetails `json:"labelDetails,omitempty"`
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind
Kind completion.ItemKind `json:"kind"`
Detail string `json:"detail"`
Documentation *MarkupContent `json:"documentation,omitempty"`
Preselect bool `json:"preselect"`
TextEdit *TextEdit `json:"textEdit,omitempty"`
Kind completion.ItemKind `json:"kind"`
Detail string `json:"detail"`
Documentation *MarkupContent `json:"documentation,omitempty"`
Preselect bool `json:"preselect"`
TextEdit *TextEdit `json:"textEdit,omitempty"`
InserTextFormat *uint `json:"insertTextFormat,omitempty"`

// Mandatory is used to indicate that the completion item is mandatory and should be offered
// as an exclusive completion. This is not part of the LSP spec, but used in regal providers
Expand Down

0 comments on commit 6cebb1c

Please sign in to comment.