Skip to content

Commit

Permalink
textDocument/complete: Pass TextEdit instead of static text (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko authored Jun 4, 2020
1 parent a4231c8 commit aa8f5e8
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 81 deletions.
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

0 comments on commit aa8f5e8

Please sign in to comment.