Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / present / code.go
1 // Copyright 2012 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 present
6
7 import (
8         "bufio"
9         "bytes"
10         "fmt"
11         "html/template"
12         "path/filepath"
13         "regexp"
14         "strconv"
15         "strings"
16 )
17
18 // PlayEnabled specifies whether runnable playground snippets should be
19 // displayed in the present user interface.
20 var PlayEnabled = false
21
22 // TODO(adg): replace the PlayEnabled flag with something less spaghetti-like.
23 // Instead this will probably be determined by a template execution Context
24 // value that contains various global metadata required when rendering
25 // templates.
26
27 // NotesEnabled specifies whether presenter notes should be displayed in the
28 // present user interface.
29 var NotesEnabled = false
30
31 func init() {
32         Register("code", parseCode)
33         Register("play", parseCode)
34 }
35
36 type Code struct {
37         Cmd      string // original command from present source
38         Text     template.HTML
39         Play     bool   // runnable code
40         Edit     bool   // editable code
41         FileName string // file name
42         Ext      string // file extension
43         Raw      []byte // content of the file
44 }
45
46 func (c Code) PresentCmd() string   { return c.Cmd }
47 func (c Code) TemplateName() string { return "code" }
48
49 // The input line is a .code or .play entry with a file name and an optional HLfoo marker on the end.
50 // Anything between the file and HL (if any) is an address expression, which we treat as a string here.
51 // We pick off the HL first, for easy parsing.
52 var (
53         highlightRE = regexp.MustCompile(`\s+HL([a-zA-Z0-9_]+)?$`)
54         hlCommentRE = regexp.MustCompile(`(.+) // HL(.*)$`)
55         codeRE      = regexp.MustCompile(`\.(code|play)\s+((?:(?:-edit|-numbers)\s+)*)([^\s]+)(?:\s+(.*))?$`)
56 )
57
58 // parseCode parses a code present directive. Its syntax:
59 //   .code [-numbers] [-edit] <filename> [address] [highlight]
60 // The directive may also be ".play" if the snippet is executable.
61 func parseCode(ctx *Context, sourceFile string, sourceLine int, cmd string) (Elem, error) {
62         cmd = strings.TrimSpace(cmd)
63         origCmd := cmd
64
65         // Pull off the HL, if any, from the end of the input line.
66         highlight := ""
67         if hl := highlightRE.FindStringSubmatchIndex(cmd); len(hl) == 4 {
68                 if hl[2] < 0 || hl[3] < 0 {
69                         return nil, fmt.Errorf("%s:%d invalid highlight syntax", sourceFile, sourceLine)
70                 }
71                 highlight = cmd[hl[2]:hl[3]]
72                 cmd = cmd[:hl[2]-2]
73         }
74
75         // Parse the remaining command line.
76         // Arguments:
77         // args[0]: whole match
78         // args[1]:  .code/.play
79         // args[2]: flags ("-edit -numbers")
80         // args[3]: file name
81         // args[4]: optional address
82         args := codeRE.FindStringSubmatch(cmd)
83         if len(args) != 5 {
84                 return nil, fmt.Errorf("%s:%d: syntax error for .code/.play invocation", sourceFile, sourceLine)
85         }
86         command, flags, file, addr := args[1], args[2], args[3], strings.TrimSpace(args[4])
87         play := command == "play" && PlayEnabled
88
89         // Read in code file and (optionally) match address.
90         filename := filepath.Join(filepath.Dir(sourceFile), file)
91         textBytes, err := ctx.ReadFile(filename)
92         if err != nil {
93                 return nil, fmt.Errorf("%s:%d: %v", sourceFile, sourceLine, err)
94         }
95         lo, hi, err := addrToByteRange(addr, 0, textBytes)
96         if err != nil {
97                 return nil, fmt.Errorf("%s:%d: %v", sourceFile, sourceLine, err)
98         }
99         if lo > hi {
100                 // The search in addrToByteRange can wrap around so we might
101                 // end up with the range ending before its starting point
102                 hi, lo = lo, hi
103         }
104
105         // Acme pattern matches can stop mid-line,
106         // so run to end of line in both directions if not at line start/end.
107         for lo > 0 && textBytes[lo-1] != '\n' {
108                 lo--
109         }
110         if hi > 0 {
111                 for hi < len(textBytes) && textBytes[hi-1] != '\n' {
112                         hi++
113                 }
114         }
115
116         lines := codeLines(textBytes, lo, hi)
117
118         data := &codeTemplateData{
119                 Lines:   formatLines(lines, highlight),
120                 Edit:    strings.Contains(flags, "-edit"),
121                 Numbers: strings.Contains(flags, "-numbers"),
122         }
123
124         // Include before and after in a hidden span for playground code.
125         if play {
126                 data.Prefix = textBytes[:lo]
127                 data.Suffix = textBytes[hi:]
128         }
129
130         var buf bytes.Buffer
131         if err := codeTemplate.Execute(&buf, data); err != nil {
132                 return nil, err
133         }
134         return Code{
135                 Cmd:      origCmd,
136                 Text:     template.HTML(buf.String()),
137                 Play:     play,
138                 Edit:     data.Edit,
139                 FileName: filepath.Base(filename),
140                 Ext:      filepath.Ext(filename),
141                 Raw:      rawCode(lines),
142         }, nil
143 }
144
145 // formatLines returns a new slice of codeLine with the given lines
146 // replacing tabs with spaces and adding highlighting where needed.
147 func formatLines(lines []codeLine, highlight string) []codeLine {
148         formatted := make([]codeLine, len(lines))
149         for i, line := range lines {
150                 // Replace tabs with spaces, which work better in HTML.
151                 line.L = strings.Replace(line.L, "\t", "    ", -1)
152
153                 // Highlight lines that end with "// HL[highlight]"
154                 // and strip the magic comment.
155                 if m := hlCommentRE.FindStringSubmatch(line.L); m != nil {
156                         line.L = m[1]
157                         line.HL = m[2] == highlight
158                 }
159
160                 formatted[i] = line
161         }
162         return formatted
163 }
164
165 // rawCode returns the code represented by the given codeLines without any kind
166 // of formatting.
167 func rawCode(lines []codeLine) []byte {
168         b := new(bytes.Buffer)
169         for _, line := range lines {
170                 b.WriteString(line.L)
171                 b.WriteByte('\n')
172         }
173         return b.Bytes()
174 }
175
176 type codeTemplateData struct {
177         Lines          []codeLine
178         Prefix, Suffix []byte
179         Edit, Numbers  bool
180 }
181
182 var leadingSpaceRE = regexp.MustCompile(`^[ \t]*`)
183
184 var codeTemplate = template.Must(template.New("code").Funcs(template.FuncMap{
185         "trimSpace":    strings.TrimSpace,
186         "leadingSpace": leadingSpaceRE.FindString,
187 }).Parse(codeTemplateHTML))
188
189 const codeTemplateHTML = `
190 {{with .Prefix}}<pre style="display: none"><span>{{printf "%s" .}}</span></pre>{{end -}}
191
192 <pre{{if .Edit}} contenteditable="true" spellcheck="false"{{end}}{{if .Numbers}} class="numbers"{{end}}>{{/*
193         */}}{{range .Lines}}<span num="{{.N}}">{{/*
194         */}}{{if .HL}}{{leadingSpace .L}}<b>{{trimSpace .L}}</b>{{/*
195         */}}{{else}}{{.L}}{{end}}{{/*
196 */}}</span>
197 {{end}}</pre>
198 {{with .Suffix}}<pre style="display: none"><span>{{printf "%s" .}}</span></pre>{{end -}}
199 `
200
201 // codeLine represents a line of code extracted from a source file.
202 type codeLine struct {
203         L  string // The line of code.
204         N  int    // The line number from the source file.
205         HL bool   // Whether the line should be highlighted.
206 }
207
208 // codeLines takes a source file and returns the lines that
209 // span the byte range specified by start and end.
210 // It discards lines that end in "OMIT".
211 func codeLines(src []byte, start, end int) (lines []codeLine) {
212         startLine := 1
213         for i, b := range src {
214                 if i == start {
215                         break
216                 }
217                 if b == '\n' {
218                         startLine++
219                 }
220         }
221         s := bufio.NewScanner(bytes.NewReader(src[start:end]))
222         for n := startLine; s.Scan(); n++ {
223                 l := s.Text()
224                 if strings.HasSuffix(l, "OMIT") {
225                         continue
226                 }
227                 lines = append(lines, codeLine{L: l, N: n})
228         }
229         // Trim leading and trailing blank lines.
230         for len(lines) > 0 && len(lines[0].L) == 0 {
231                 lines = lines[1:]
232         }
233         for len(lines) > 0 && len(lines[len(lines)-1].L) == 0 {
234                 lines = lines[:len(lines)-1]
235         }
236         return
237 }
238
239 func parseArgs(name string, line int, args []string) (res []interface{}, err error) {
240         res = make([]interface{}, len(args))
241         for i, v := range args {
242                 if len(v) == 0 {
243                         return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v)
244                 }
245                 switch v[0] {
246                 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
247                         n, err := strconv.Atoi(v)
248                         if err != nil {
249                                 return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v)
250                         }
251                         res[i] = n
252                 case '/':
253                         if len(v) < 2 || v[len(v)-1] != '/' {
254                                 return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v)
255                         }
256                         res[i] = v
257                 case '$':
258                         res[i] = "$"
259                 case '_':
260                         if len(v) == 1 {
261                                 // Do nothing; "_" indicates an intentionally empty parameter.
262                                 break
263                         }
264                         fallthrough
265                 default:
266                         return nil, fmt.Errorf("%s:%d bad code argument %q", name, line, v)
267                 }
268         }
269         return
270 }