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

textDocument/complete: Pass TextEdit instead of static text #133

Merged
merged 1 commit into from
Jun 4, 2020
Merged
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
31 changes: 17 additions & 14 deletions internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,7 @@ func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupp
InsertTextFormat: lsp.ITFSnippet,
Detail: candidate.Detail(),
Documentation: doc,
TextEdit: &lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
End: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
},
NewText: candidate.Snippet(),
},
TextEdit: textEdit(candidate.Snippet(), pos),
}
}

Expand All @@ -57,12 +51,21 @@ func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupp
InsertTextFormat: lsp.ITFPlainText,
Detail: candidate.Detail(),
Documentation: doc,
TextEdit: &lsp.TextEdit{
Range: lsp.Range{
Start: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
End: lsp.Position{Line: pos.Line - 1, Character: pos.Column - 1},
},
NewText: candidate.PlainText(),
},
TextEdit: textEdit(candidate.PlainText(), pos),
}
}

func textEdit(te lang.TextEdit, pos hcl.Pos) *lsp.TextEdit {
rng := te.Range()
if rng == nil {
rng = &hcl.Range{
Start: pos,
End: pos,
}
}

return &lsp.TextEdit{
NewText: te.NewText(),
Range: hclRangeToLSP(*rng),
}
}
98 changes: 56 additions & 42 deletions internal/terraform/lang/config_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ type configBlockFactory interface {
type labelCandidates map[string][]*labelCandidate

type completableLabels struct {
logger *log.Logger
logger *log.Logger
maxCandidates int
parsedLabels []*ParsedLabel
tBlock ihcl.TokenizedBlock
labels labelCandidates
parsedLabels []*ParsedLabel
tBlock ihcl.TokenizedBlock
labels labelCandidates
}

func (cl *completableLabels) maxCompletionCandidates() int {
Expand All @@ -51,7 +51,7 @@ func (cl *completableLabels) completionCandidatesAtPos(pos hcl.Pos) (CompletionC

cl.logger.Printf("completing label %q ...", l.Name)

prefix := prefixAtPos(cl.tBlock, pos)
prefix, prefixRng := prefixAtPos(cl.tBlock, pos)

for _, c := range candidates {
if len(list.candidates) >= cl.maxCompletionCandidates() {
Expand All @@ -61,7 +61,7 @@ func (cl *completableLabels) completionCandidatesAtPos(pos hcl.Pos) (CompletionC
if !strings.HasPrefix(c.Label(), prefix) {
continue
}
c.prefix = prefix
c.prefixRng = prefixRng
list.candidates = append(list.candidates, c)
}
list.Sort()
Expand All @@ -72,11 +72,11 @@ func (cl *completableLabels) completionCandidatesAtPos(pos hcl.Pos) (CompletionC
// completableBlock provides common completion functionality
// for any Block implementation
type completableBlock struct {
logger *log.Logger
logger *log.Logger
maxCandidates int
parsedLabels []*ParsedLabel
tBlock ihcl.TokenizedBlock
schema *tfjson.SchemaBlock
parsedLabels []*ParsedLabel
tBlock ihcl.TokenizedBlock
schema *tfjson.SchemaBlock
}

func (cl *completableBlock) maxCompletionCandidates() int {
Expand All @@ -94,15 +94,11 @@ func (cb *completableBlock) completionCandidatesAtPos(pos hcl.Pos) (CompletionCa
block := ParseBlock(cb.tBlock, cb.schema)

if !block.PosInBody(pos) {
// TODO: Allow this (requires access to the parser/all block types here)
cb.logger.Println("avoiding completion outside of block body")
return nil, nil
}

if block.PosInAttribute(pos) {
cb.logger.Println("avoiding completion in the middle of existing attribute")
return nil, nil
}

// Completing the body (attributes and nested blocks)
b, ok := block.BlockAtPos(pos)
if !ok {
Expand All @@ -112,7 +108,8 @@ func (cb *completableBlock) completionCandidatesAtPos(pos hcl.Pos) (CompletionCa
return nil, nil
}

prefix := prefixAtPos(cb.tBlock, pos)
prefix, prefixRng := prefixAtPos(cb.tBlock, pos)
cb.logger.Printf("completing block: %#v, %#v", prefix, prefixRng)

for name, attr := range b.Attributes() {
if len(list.candidates) >= cb.maxCompletionCandidates() {
Expand All @@ -126,9 +123,9 @@ func (cb *completableBlock) completionCandidatesAtPos(pos hcl.Pos) (CompletionCa
continue
}
list.candidates = append(list.candidates, &attributeCandidate{
Name: name,
Attr: attr,
Prefix: prefix,
Name: name,
Attr: attr,
PrefixRange: prefixRng,
})
}

Expand All @@ -143,11 +140,15 @@ func (cb *completableBlock) completionCandidatesAtPos(pos hcl.Pos) (CompletionCa
if block.ReachedMaxItems() {
continue
}
list.candidates = append(list.candidates, &nestedBlockCandidate{

nbc := &nestedBlockCandidate{
Name: name,
BlockType: block,
Prefix: prefix,
})
}
if prefixRng != nil {
nbc.PrefixRange = prefixRng
}
list.candidates = append(list.candidates, nbc)
}

list.Sort()
Expand Down Expand Up @@ -183,7 +184,7 @@ type labelCandidate struct {
label string
detail string
documentation MarkupContent
prefix string
prefixRng *hcl.Range
}

func (c *labelCandidate) Label() string {
Expand All @@ -198,18 +199,21 @@ func (c *labelCandidate) Documentation() MarkupContent {
return c.documentation
}

func (c *labelCandidate) Snippet() string {
func (c *labelCandidate) Snippet() TextEdit {
return c.PlainText()
}

func (c *labelCandidate) PlainText() string {
return strings.TrimPrefix(c.label, c.prefix)
func (c *labelCandidate) PlainText() TextEdit {
return &textEdit{
newText: c.label,
rng: c.prefixRng,
}
}

type attributeCandidate struct {
Name string
Attr *Attribute
Prefix string
Name string
Attr *Attribute
PrefixRange *hcl.Range
}

func (c *attributeCandidate) Label() string {
Expand All @@ -236,19 +240,24 @@ func (c *attributeCandidate) Documentation() MarkupContent {
return PlainText("")
}

func (c *attributeCandidate) Snippet() string {
name := strings.TrimPrefix(c.Name, c.Prefix)
return fmt.Sprintf("%s = %s", name, snippetForAttrType(0, c.Attr.Schema().AttributeType))
func (c *attributeCandidate) Snippet() TextEdit {
return &textEdit{
newText: fmt.Sprintf("%s = %s", c.Name, snippetForAttrType(0, c.Attr.Schema().AttributeType)),
rng: c.PrefixRange,
}
}

func (c *attributeCandidate) PlainText() string {
return strings.TrimPrefix(c.Name, c.Prefix)
func (c *attributeCandidate) PlainText() TextEdit {
return &textEdit{
newText: c.Name,
rng: c.PrefixRange,
}
}

type nestedBlockCandidate struct {
Name string
BlockType *BlockType
Prefix string
Name string
BlockType *BlockType
PrefixRange *hcl.Range
}

func (c *nestedBlockCandidate) Label() string {
Expand All @@ -269,11 +278,16 @@ func (c *nestedBlockCandidate) Documentation() MarkupContent {
return PlainText(c.BlockType.Schema().Block.Description)
}

func (c *nestedBlockCandidate) Snippet() string {
name := strings.TrimPrefix(c.Name, c.Prefix)
return snippetForNestedBlock(name)
func (c *nestedBlockCandidate) Snippet() TextEdit {
return &textEdit{
newText: snippetForNestedBlock(c.Name),
rng: c.PrefixRange,
}
}

func (c *nestedBlockCandidate) PlainText() string {
return strings.TrimPrefix(c.Name, c.Prefix)
func (c *nestedBlockCandidate) PlainText() TextEdit {
return &textEdit{
newText: c.Name,
rng: c.PrefixRange,
}
}
78 changes: 67 additions & 11 deletions internal/terraform/lang/config_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,16 +291,16 @@ func TestCompletableLabels_CompletionCandidatesAtPos_overLimit(t *testing.T) {
}`)

cl := &completableLabels{
logger: testLogger(),
logger: testLogger(),
parsedLabels: []*ParsedLabel{
{Name: "type", Range: hcl.Range{
Filename: "/test.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
}},
},
tBlock: tBlock,
labels: map[string][]*labelCandidate{
tBlock: tBlock,
labels: map[string][]*labelCandidate{
"type": []*labelCandidate{
{label: "aaa"},
{label: "bbb"},
Expand Down Expand Up @@ -328,16 +328,16 @@ func TestCompletableLabels_CompletionCandidatesAtPos_matchingLimit(t *testing.T)
}`)

cl := &completableLabels{
logger: testLogger(),
logger: testLogger(),
parsedLabels: []*ParsedLabel{
{Name: "type", Range: hcl.Range{
Filename: "/test.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
}},
},
tBlock: tBlock,
labels: map[string][]*labelCandidate{
tBlock: tBlock,
labels: map[string][]*labelCandidate{
"type": []*labelCandidate{
{label: "aaa"},
{label: "bbb"},
Expand All @@ -359,6 +359,62 @@ func TestCompletableLabels_CompletionCandidatesAtPos_matchingLimit(t *testing.T)
}
}

func TestCompletableLabels_CompletionCandidatesAtPos_withPrefix(t *testing.T) {
tBlock := newTestBlock(t, `resource "prov_xyz" {
}`)

cl := &completableLabels{
logger: testLogger(),
parsedLabels: []*ParsedLabel{
{Name: "type", Range: hcl.Range{
Filename: "/test.tf",
Start: hcl.Pos{Line: 1, Column: 10, Byte: 9},
End: hcl.Pos{Line: 1, Column: 20, Byte: 19},
}},
},
tBlock: tBlock,
labels: map[string][]*labelCandidate{
"type": []*labelCandidate{
{label: "prov_aaa"},
{label: "prov_bbb"},
{label: "ccc"},
},
},
}
c, err := cl.completionCandidatesAtPos(hcl.Pos{Line: 1, Column: 16, Byte: 15})
if err != nil {
t.Fatal(err)
}

if c.Len() != 2 {
t.Fatalf("Expected exactly 2 candidate, %d given", c.Len())
}

candidates := c.List()
te := candidates[0].PlainText()
expectedTextEdit := &textEdit{
newText: "prov_aaa",
rng: &hcl.Range{
Filename: "/test.tf",
Start: hcl.Pos{
Line: 1,
Column: 11,
Byte: 10,
},
End: hcl.Pos{
Line: 1,
Column: 19,
Byte: 18,
},
},
}

opts := cmp.AllowUnexported(textEdit{})
if diff := cmp.Diff(expectedTextEdit, te, opts); diff != "" {
t.Fatalf("Text edit doesn't match: %s", diff)
}
}

func renderCandidates(list CompletionCandidates, pos hcl.Pos) []renderedCandidate {
if list == nil {
return []renderedCandidate{}
Expand All @@ -377,7 +433,7 @@ func renderCandidates(list CompletionCandidates, pos hcl.Pos) []renderedCandidat
Documentation: doc,
Snippet: renderedSnippet{
Pos: pos,
Text: text,
Text: text.NewText(),
},
}
}
Expand Down
Loading