Skip to content

Commit

Permalink
Completions: package name suggested from any path component (#869)
Browse files Browse the repository at this point in the history
And fix bug that would sometimes suggest "" as the package name.

Oh, and the provider is now written in Rego :)

Fixes #846
Fixes #856

Signed-off-by: Anders Eknert <[email protected]>
  • Loading branch information
anderseknert committed Jun 24, 2024
1 parent 3b5b83e commit 2fa61d7
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 233 deletions.
13 changes: 13 additions & 0 deletions bundle/regal/lsp/completion/location/location.rego
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ to_position(location) := {
"character": location.col - 1,
}

# METADATA
# description: returns a range from start of line to position
from_start_of_line_to_position(position) := {
"start": {
"line": position.line,
"character": 0,
},
"end": {
"line": position.line,
"character": position.character,
},
}

# METADATA
# description: |
# estimate where the location "ends" based on its text attribute,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@ items contains item if {
"value": sprintf("%q is a common rule name", [label]),
},
"textEdit": {
"range": {
"start": {
"line": position.line,
"character": 0,
},
"end": position,
},
"range": location.from_start_of_line_to_position(position),
"newText": sprintf("%s ", [label]),
},
}
Expand Down
16 changes: 2 additions & 14 deletions bundle/regal/lsp/completion/providers/default/default.rego
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ items contains item if {
"kind": kind.keyword,
"detail": "default <rule-name> := <value>",
"textEdit": {
"range": {
"start": {
"line": position.line,
"character": 0,
},
"end": position,
},
"range": location.from_start_of_line_to_position(position),
"newText": "default ",
},
}
Expand All @@ -43,13 +37,7 @@ items contains item if {
"kind": kind.keyword,
"detail": sprintf("add default assignment for %s rule", [name]),
"textEdit": {
"range": {
"start": {
"line": position.line,
"character": 0,
},
"end": position,
},
"range": location.from_start_of_line_to_position(position),
"newText": sprintf("default %s := ", [name]),
},
}
Expand Down
11 changes: 1 addition & 10 deletions bundle/regal/lsp/completion/providers/package/package.rego
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,7 @@ items contains item if {
"kind": kind.keyword,
"detail": "package <package-name>",
"textEdit": {
"range": {
"start": {
"line": position.line,
"character": 0,
},
"end": {
"line": position.line,
"character": position.character,
},
},
"range": location.from_start_of_line_to_position(position),
"newText": "package ",
},
}
Expand Down
51 changes: 51 additions & 0 deletions bundle/regal/lsp/completion/providers/packagename/packagename.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package regal.lsp.completion.providers.packagename

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]

invoke_suggestion(line, position)

ps := input.regal.context.path_separator

abs_dir := base(input.regal.file.name)
rel_dir := trim_prefix(abs_dir, input.regal.context.workspace_root)
fix_dir := replace(replace(trim_prefix(rel_dir, ps), ".", "_"), ps, ".")

word := location.ref_at(line, input.regal.context.location.col)

some suggestion in suggestions(fix_dir, word)

item := {
"label": suggestion,
"kind": kind.folder,
"detail": "suggested package name based on directory structure",
"textEdit": {
"range": location.word_range(word, position),
"newText": sprintf("%s\n\n", [suggestion]),
},
}
}

invoke_suggestion(line, position) if {
startswith(line, "package ")
position.character > 7
}

base(path) := substring(path, 0, regal.last(indexof_n(path, "/")))

suggestions(dir, word) := [path |
parts := split(dir, ".")
len_p := count(parts)
some n in numbers.range(0, len_p)

path := concat(".", array.slice(parts, n, len_p))
path != ""

startswith(path, word.text)
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package regal.lsp.completion.providers.packagename_test

import rego.v1

import data.regal.lsp.completion.providers.packagename as provider

test_package_name_completion_on_typing if {
policy := `package f`
provider_input := {"regal": {
"file": {
"name": "/Users/joe/policy/foo/bar/baz/p.rego",
"lines": split(policy, "\n"),
},
"context": {
"path_separator": "/",
"workspace_root": "/Users/joe/policy",
"location": {
"row": 1,
"col": 10,
},
},
}}
items := provider.items with input as provider_input
items == {{
"detail": "suggested package name based on directory structure",
"kind": 19,
"label": "foo.bar.baz",
"textEdit": {
"newText": "foo.bar.baz\n\n",
"range": {
"end": {"character": 9, "line": 0},
"start": {"character": 8, "line": 0},
},
},
}}
}

# regal ignore:rule-length
test_package_name_completion_on_typing_multiple_suggestions if {
policy := `package b`
provider_input := {"regal": {
"file": {
"name": "/Users/joe/policy/foo/bar/baz/p.rego",
"lines": split(policy, "\n"),
},
"context": {
"path_separator": "/",
"workspace_root": "/Users/joe/policy",
"location": {
"row": 1,
"col": 10,
},
},
}}
items := provider.items with input as provider_input
items == {
{
"detail": "suggested package name based on directory structure",
"kind": 19,
"label": "bar.baz",
"textEdit": {
"newText": "bar.baz\n\n",
"range": {
"end": {"character": 9, "line": 0},
"start": {"character": 8, "line": 0},
},
},
},
{
"detail": "suggested package name based on directory structure",
"kind": 19,
"label": "baz",
"textEdit": {
"newText": "baz\n\n",
"range": {
"end": {"character": 9, "line": 0},
"start": {"character": 8, "line": 0},
},
},
},
}
}

# regal ignore:rule-length
test_package_name_completion_on_typing_multiple_suggestions_when_invoked if {
policy := `package `
provider_input := {"regal": {
"file": {
"name": "/Users/joe/policy/foo/bar/baz/p.rego",
"lines": split(policy, "\n"),
},
"context": {
"path_separator": "/",
"workspace_root": "/Users/joe/policy",
"location": {
"row": 1,
"col": 9,
},
},
}}
items := provider.items with input as provider_input
items == {
{
"detail": "suggested package name based on directory structure",
"kind": 19,
"label": "foo.bar.baz",
"textEdit": {
"newText": "foo.bar.baz\n\n",
"range": {
"end": {"character": 8, "line": 0},
"start": {"character": 8, "line": 0},
},
},
},
{
"detail": "suggested package name based on directory structure",
"kind": 19,
"label": "bar.baz",
"textEdit": {
"newText": "bar.baz\n\n",
"range": {
"end": {"character": 8, "line": 0},
"start": {"character": 8, "line": 0},
},
},
},
{
"detail": "suggested package name based on directory structure",
"kind": 19,
"label": "baz",
"textEdit": {
"newText": "baz\n\n",
"range": {
"end": {"character": 8, "line": 0},
"start": {"character": 8, "line": 0},
},
},
},
}
}

test_build_suggestions if {
provider.suggestions("foo.bar.baz", {"text": "foo"}) == ["foo.bar.baz"]
provider.suggestions("foo.bar.baz", {"text": "bar"}) == ["bar.baz"]
provider.suggestions("foo.bar.baz", {"text": "ba"}) == ["bar.baz", "baz"]
}

test_build_suggestions_invoked if {
provider.suggestions("foo.bar.baz", {"text": ""}) == [
"foo.bar.baz",
"bar.baz",
"baz",
]
}
6 changes: 6 additions & 0 deletions internal/embeds/schemas/regal-ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@
},
"location": {
"type": "object"
},
"path_separator": {
"type": "string"
},
"workspace_root": {
"type": "string"
}
}
}
Expand Down
1 change: 0 additions & 1 deletion internal/lsp/completions/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func NewManager(c *cache.Cache, opts *ManagerOptions) *Manager {
func NewDefaultManager(c *cache.Cache, store storage.Store) *Manager {
m := NewManager(c, &ManagerOptions{})

m.RegisterProvider(&providers.PackageName{})
m.RegisterProvider(&providers.BuiltIns{})
m.RegisterProvider(&providers.PackageRefs{})
m.RegisterProvider(&providers.RuleHead{})
Expand Down
78 changes: 0 additions & 78 deletions internal/lsp/completions/providers/packagename.go

This file was deleted.

Loading

0 comments on commit 2fa61d7

Please sign in to comment.