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})". 15package snippet 16 17import ( 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. 24type 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". 32var replacer = strings.NewReplacer( 33 `\`, `\\`, 34 `}`, `\}`, 35 `$`, `\$`, 36) 37 38func (b *Builder) WriteText(s string) { 39 replacer.WriteString(&b.sb, s) 40} 41 42// WritePlaceholder writes a tab stop and placeholder value to the Builder. 43// The callback style allows for creating nested placeholders. To write an 44// empty tab stop, provide a nil callback. 45func (b *Builder) WritePlaceholder(fn func(*Builder)) { 46 fmt.Fprintf(&b.sb, "${%d:", b.nextTabStop()) 47 if fn != nil { 48 fn(b) 49 } 50 b.sb.WriteByte('}') 51} 52 53// WriteFinalTabstop marks where cursor ends up after the user has 54// cycled through all the normal tab stops. It defaults to the 55// character after the snippet. 56func (b *Builder) WriteFinalTabstop() { 57 fmt.Fprint(&b.sb, "$0") 58} 59 60// In addition to '\', '}', and '$', snippet choices also use '|' and ',' as 61// meta characters, so they must be escaped within the choices. 62var choiceReplacer = strings.NewReplacer( 63 `\`, `\\`, 64 `}`, `\}`, 65 `$`, `\$`, 66 `|`, `\|`, 67 `,`, `\,`, 68) 69 70// WriteChoice writes a tab stop and list of text choices to the Builder. 71// The user's editor will prompt the user to choose one of the choices. 72func (b *Builder) WriteChoice(choices []string) { 73 fmt.Fprintf(&b.sb, "${%d|", b.nextTabStop()) 74 for i, c := range choices { 75 if i != 0 { 76 b.sb.WriteByte(',') 77 } 78 choiceReplacer.WriteString(&b.sb, c) 79 } 80 b.sb.WriteString("|}") 81} 82 83// String returns the built snippet string. 84func (b *Builder) String() string { 85 return b.sb.String() 86} 87 88// nextTabStop returns the next tab stop index for a new placeholder. 89func (b *Builder) nextTabStop() int { 90 // Tab stops start from 1, so increment before returning. 91 b.currentTabStop++ 92 return b.currentTabStop 93} 94