Skip to content

Commit

Permalink
Improve UX of completion items
Browse files Browse the repository at this point in the history
Move descriptions to the documentation field, update to terraform-json 0.5.0 that has additional description support, etc.
  • Loading branch information
paultyng committed May 26, 2020
1 parent 3e4ca73 commit c103c43
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 81 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/google/go-cmp v0.4.0
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/hcl/v2 v2.3.0
github.com/hashicorp/terraform-json v0.4.0
github.com/hashicorp/terraform-json v0.5.0
github.com/mitchellh/cli v1.0.0
github.com/pmezard/go-difflib v1.0.0
github.com/sourcegraph/go-lsp v0.0.0-20200117082640-b19bb38222e2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+d
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
github.com/hashicorp/terraform-json v0.4.0 h1:KNh29iNxozP5adfUFBJ4/fWd0Cu3taGgjHB38JYqOF4=
github.com/hashicorp/terraform-json v0.4.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU=
github.com/hashicorp/terraform-json v0.5.0 h1:7TV3/F3y7QVSuN4r9BEXqnWqrAyeOtON8f0wvREtyzs=
github.com/hashicorp/terraform-json v0.5.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down
11 changes: 10 additions & 1 deletion internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ func CompletionList(candidates lang.CompletionCandidates, pos hcl.Pos, caps lsp.
}

func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupport bool) lsp.CompletionItem {
// TODO: deprecated / tags?

doc := ""
if c := candidate.Documentation(); c != nil {
// TODO: markdown handling
doc = c.Value()
}

if snippetSupport {
pos, newText := candidate.Snippet(pos)

return lsp.CompletionItem{
Label: candidate.Label(),
Kind: lsp.CIKField,
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},
Expand All @@ -49,5 +57,6 @@ func CompletionItem(candidate lang.CompletionCandidate, pos hcl.Pos, snippetSupp
Kind: lsp.CIKField,
InsertTextFormat: lsp.ITFPlainText,
Detail: candidate.Detail(),
Documentation: doc,
}
}
72 changes: 72 additions & 0 deletions internal/mdplain/mdplain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package mdplain

import (
"fmt"
"regexp"
)

type replacement struct {
re *regexp.Regexp
sub string
}

var replacements = []replacement{
// rules heavily inspired by: https://github.com/stiang/remove-markdown/blob/master/index.js
// back references were removed

// Header
{regexp.MustCompile(`\n={2,}`), "\n"},
// Fenced codeblocks
{regexp.MustCompile(`~{3}.*\n`), ""},
// Strikethrough
{regexp.MustCompile("~~"), ""},
// Fenced codeblocks
{regexp.MustCompile("`{3}.*\\n"), ""},
// Remove HTML tags
{regexp.MustCompile(`<[^>]*>`), ""},
// Remove setext-style headers
{regexp.MustCompile(`^[=\-]{2,}\s*$`), ""},
// Remove footnotes?
{regexp.MustCompile(`\[\^.+?\](\: .*?$)?`), ""},
{regexp.MustCompile(`\s{0,2}\[.*?\]: .*?$`), ""},
// Remove images
{regexp.MustCompile(`\!\[(.*?)\][\[\(].*?[\]\)]`), "$1"},
// Remove inline links
{regexp.MustCompile(`\[(.*?)\][\[\(].*?[\]\)]`), "$1"},
// Remove blockquotes
{regexp.MustCompile(`^\s{0,3}>\s?`), ""},
// Remove reference-style links?
{regexp.MustCompile(`^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$`), ""},
// Remove atx-style headers
{regexp.MustCompile(`^(\n)?\s{0,}#{1,6}\s+| {0,}(\n)?\s{0,}#{0,} {0,}(\n)?\s{0,}$`), "$1$2$3"},
// Remove emphasis (repeat the line to remove double emphasis)
{regexp.MustCompile(`([*_]{1,3})([^\t\n\f\r *_].*?[^\t\n\f\r *_]{0,1})([*_]{1,3})`), "$2"},
{regexp.MustCompile(`([*_]{1,3})([^\t\n\f\r *_].*?[^\t\n\f\r *_]{0,1})([*_]{1,3})`), "$2"},
// Remove code blocks
{regexp.MustCompile("(`{3,})(.*?)(`{3,})"), "$2"},
// Remove inline code
{regexp.MustCompile("`(.+?)`"), "$1"},
// Replace two or more newlines with exactly two? Not entirely sure this belongs here...
{regexp.MustCompile(`\n{2,}`), "\n\n"},
}

// Clean runs a VERY naive cleanup of markdown text to make it more palatable as plain text.
func Clean(markdown string) string {
// TODO: maybe use https://github.com/russross/blackfriday/tree/v2, write custom renderer or
// generate HTML then process that to plaintext using https://github.com/jaytaylor/html2text

fmt.Printf("cleaning %q\n", markdown)

result := markdown
before := markdown

for _, r := range replacements {
result = r.re.ReplaceAllString(result, r.sub)
if before != result {
fmt.Printf("RE: %q, %q to %q\n", r.re, before, result)
}
before = result
}

return string(result)
}
38 changes: 38 additions & 0 deletions internal/mdplain/mdplain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package mdplain_test

import (
"testing"

"github.com/hashicorp/terraform-ls/internal/mdplain"
)

func TestClean(t *testing.T) {
for _, c := range []struct {
markdown string
expected string
}{
{"", ""},

{"_foo_", "foo"},
{"__foo__", "foo"},
{"foo_bar", "foo_bar"},

{"*foo*", "foo"},
{"**foo**", "foo"},
{"Desc **2**", "Desc 2"},
{"1 * 3 = 3", "1 * 3 = 3"},

{"## Header", "Header"},
{"Header\n====\n\nSome text", "Header\n\nSome text"},

{"* item 1\n* item 2\n\n\nSome text", "* item 1\n* item 2\n\nSome text"},
} {
t.Run(c.expected, func(t *testing.T) {
actual := mdplain.Clean(c.markdown)

if c.expected != actual {
t.Fatalf("expected:\n%s\n\ngot:\n%s\n", c.expected, actual)
}
})
}
}
37 changes: 35 additions & 2 deletions internal/terraform/lang/config_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (

hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
tfjson "github.com/hashicorp/terraform-json"
)

type configBlockFactory interface {
New(*hclsyntax.Block) (ConfigBlock, error)
LabelSchema() LabelSchema
Documentation() MarkupContent
}

type labelCandidates map[string][]CompletionCandidate
Expand Down Expand Up @@ -129,8 +131,9 @@ func (l *completeList) IsComplete() bool {
}

type labelCandidate struct {
label string
detail string
label string
detail string
documentation MarkupContent
}

func (c *labelCandidate) Label() string {
Expand All @@ -141,6 +144,10 @@ func (c *labelCandidate) Detail() string {
return c.detail
}

func (c *labelCandidate) Documentation() MarkupContent {
return c.documentation
}

func (c *labelCandidate) Snippet(pos hcl.Pos) (hcl.Pos, string) {
return pos, c.label
}
Expand All @@ -156,9 +163,25 @@ func (c *attributeCandidate) Label() string {
}

func (c *attributeCandidate) Detail() string {
if c.Attr == nil {
return ""
}
return schemaAttributeDetail(c.Attr.Schema())
}

func (c *attributeCandidate) Documentation() MarkupContent {
if c.Attr == nil {
return PlainText("")
}
if schema := c.Attr.Schema(); schema != nil {
if schema.DescriptionKind == tfjson.SchemaDescriptionKindMarkdown {
return Markdown(schema.Description)
}
return PlainText(schema.Description)
}
return PlainText("")
}

func (c *attributeCandidate) Snippet(pos hcl.Pos) (hcl.Pos, string) {
return pos, fmt.Sprintf("%s = %s", c.Name, snippetForAttrType(0, c.Attr.Schema().AttributeType))
}
Expand All @@ -177,6 +200,16 @@ func (c *nestedBlockCandidate) Detail() string {
return schemaBlockDetail(c.BlockType)
}

func (c *nestedBlockCandidate) Documentation() MarkupContent {
if c.BlockType == nil || c.BlockType.Schema() == nil || c.BlockType.Schema().Block == nil {
return PlainText("")
}
if c.BlockType.Schema().Block.DescriptionKind == tfjson.SchemaDescriptionKindMarkdown {
return Markdown(c.BlockType.Schema().Block.Description)
}
return PlainText(c.BlockType.Schema().Block.Description)
}

func (c *nestedBlockCandidate) Snippet(pos hcl.Pos) (hcl.Pos, string) {
return pos, snippetForNestedBlock(c.Name)
}
Loading

0 comments on commit c103c43

Please sign in to comment.