github.com/jd-ly/tools@v0.5.7/internal/lsp/snippet/snippet_builder.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package snippet implements the specification for the LSP snippet format. 6 // 7 // Snippets are "tab stop" templates returned as an optional attribute of LSP 8 // completion candidates. As the user presses tab, they cycle through a series of 9 // tab stops defined in the snippet. Each tab stop can optionally have placeholder 10 // text, which can be pre-selected by editors. For a full description of syntax 11 // and features, see "Snippet Syntax" at 12 // https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_completion. 13 // 14 // A typical snippet looks like "foo(${1:i int}, ${2:s string})". 15 package snippet 16 17 import ( 18 "fmt" 19 "strings" 20 ) 21 22 // A Builder is used to build an LSP snippet piecemeal. 23 // The zero value is ready to use. Do not copy a non-zero Builder. 24 type Builder struct { 25 // currentTabStop is the index of the previous tab stop. The 26 // next tab stop will be currentTabStop+1. 27 currentTabStop int 28 sb strings.Builder 29 } 30 31 // Escape characters defined in https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_completion under "Grammar". 32 var replacer = strings.NewReplacer( 33 `\`, `\\`, 34 `}`, `\}`, 35 `$`, `\$`, 36 ) 37 38 func (b *Builder) WriteText(s string) { 39 replacer.WriteString(&b.sb, s) 40 } 41 42 func (b *Builder) PrependText(s string) { 43 rawSnip := b.String() 44 b.sb.Reset() 45 b.WriteText(s) 46 b.sb.WriteString(rawSnip) 47 } 48 49 // WritePlaceholder writes a tab stop and placeholder value to the Builder. 50 // The callback style allows for creating nested placeholders. To write an 51 // empty tab stop, provide a nil callback. 52 func (b *Builder) WritePlaceholder(fn func(*Builder)) { 53 fmt.Fprintf(&b.sb, "${%d:", b.nextTabStop()) 54 if fn != nil { 55 fn(b) 56 } 57 b.sb.WriteByte('}') 58 } 59 60 // WriteFinalTabstop marks where cursor ends up after the user has 61 // cycled through all the normal tab stops. It defaults to the 62 // character after the snippet. 63 func (b *Builder) WriteFinalTabstop() { 64 fmt.Fprint(&b.sb, "$0") 65 } 66 67 // In addition to '\', '}', and '$', snippet choices also use '|' and ',' as 68 // meta characters, so they must be escaped within the choices. 69 var choiceReplacer = strings.NewReplacer( 70 `\`, `\\`, 71 `}`, `\}`, 72 `$`, `\$`, 73 `|`, `\|`, 74 `,`, `\,`, 75 ) 76 77 // WriteChoice writes a tab stop and list of text choices to the Builder. 78 // The user's editor will prompt the user to choose one of the choices. 79 func (b *Builder) WriteChoice(choices []string) { 80 fmt.Fprintf(&b.sb, "${%d|", b.nextTabStop()) 81 for i, c := range choices { 82 if i != 0 { 83 b.sb.WriteByte(',') 84 } 85 choiceReplacer.WriteString(&b.sb, c) 86 } 87 b.sb.WriteString("|}") 88 } 89 90 // String returns the built snippet string. 91 func (b *Builder) String() string { 92 return b.sb.String() 93 } 94 95 // nextTabStop returns the next tab stop index for a new placeholder. 96 func (b *Builder) nextTabStop() int { 97 // Tab stops start from 1, so increment before returning. 98 b.currentTabStop++ 99 return b.currentTabStop 100 }