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