GoPLS Viewer

Home|gopls/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
5package present
6
7import (
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.
20var 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.
29var NotesEnabled = false
30
31func init() {
32    Register("code"parseCode)
33    Register("play"parseCode)
34}
35
36type 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
46func (c CodePresentCmd() string   { return c.Cmd }
47func (c CodeTemplateName() 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.
52var (
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//
60//    .code [-numbers] [-edit] <filename> [address] [highlight]
61//
62// The directive may also be ".play" if the snippet is executable.
63func parseCode(ctx *ContextsourceFile stringsourceLine intcmd string) (Elemerror) {
64    cmd = strings.TrimSpace(cmd)
65    origCmd := cmd
66
67    // Pull off the HL, if any, from the end of the input line.
68    highlight := ""
69    if hl := highlightRE.FindStringSubmatchIndex(cmd); len(hl) == 4 {
70        if hl[2] < 0 || hl[3] < 0 {
71            return nilfmt.Errorf("%s:%d invalid highlight syntax"sourceFilesourceLine)
72        }
73        highlight = cmd[hl[2]:hl[3]]
74        cmd = cmd[:hl[2]-2]
75    }
76
77    // Parse the remaining command line.
78    // Arguments:
79    // args[0]: whole match
80    // args[1]:  .code/.play
81    // args[2]: flags ("-edit -numbers")
82    // args[3]: file name
83    // args[4]: optional address
84    args := codeRE.FindStringSubmatch(cmd)
85    if len(args) != 5 {
86        return nilfmt.Errorf("%s:%d: syntax error for .code/.play invocation"sourceFilesourceLine)
87    }
88    commandflagsfileaddr := args[1], args[2], args[3], strings.TrimSpace(args[4])
89    play := command == "play" && PlayEnabled
90
91    // Read in code file and (optionally) match address.
92    filename := filepath.Join(filepath.Dir(sourceFile), file)
93    textByteserr := ctx.ReadFile(filename)
94    if err != nil {
95        return nilfmt.Errorf("%s:%d: %v"sourceFilesourceLineerr)
96    }
97    lohierr := addrToByteRange(addr0textBytes)
98    if err != nil {
99        return nilfmt.Errorf("%s:%d: %v"sourceFilesourceLineerr)
100    }
101    if lo > hi {
102        // The search in addrToByteRange can wrap around so we might
103        // end up with the range ending before its starting point
104        hilo = lohi
105    }
106
107    // Acme pattern matches can stop mid-line,
108    // so run to end of line in both directions if not at line start/end.
109    for lo > 0 && textBytes[lo-1] != '\n' {
110        lo--
111    }
112    if hi > 0 {
113        for hi < len(textBytes) && textBytes[hi-1] != '\n' {
114            hi++
115        }
116    }
117
118    lines := codeLines(textByteslohi)
119
120    data := &codeTemplateData{
121        Lines:   formatLines(lineshighlight),
122        Edit:    strings.Contains(flags"-edit"),
123        Numbersstrings.Contains(flags"-numbers"),
124    }
125
126    // Include before and after in a hidden span for playground code.
127    if play {
128        data.Prefix = textBytes[:lo]
129        data.Suffix = textBytes[hi:]
130    }
131
132    var buf bytes.Buffer
133    if err := codeTemplate.Execute(&bufdata); err != nil {
134        return nilerr
135    }
136    return Code{
137        Cmd:      origCmd,
138        Text:     template.HTML(buf.String()),
139        Play:     play,
140        Edit:     data.Edit,
141        FileNamefilepath.Base(filename),
142        Ext:      filepath.Ext(filename),
143        Raw:      rawCode(lines),
144    }, nil
145}
146
147// formatLines returns a new slice of codeLine with the given lines
148// replacing tabs with spaces and adding highlighting where needed.
149func formatLines(lines []codeLinehighlight string) []codeLine {
150    formatted := make([]codeLinelen(lines))
151    for iline := range lines {
152        // Replace tabs with spaces, which work better in HTML.
153        line.L = strings.Replace(line.L"\t""    ", -1)
154
155        // Highlight lines that end with "// HL[highlight]"
156        // and strip the magic comment.
157        if m := hlCommentRE.FindStringSubmatch(line.L); m != nil {
158            line.L = m[1]
159            line.HL = m[2] == highlight
160        }
161
162        formatted[i] = line
163    }
164    return formatted
165}
166
167// rawCode returns the code represented by the given codeLines without any kind
168// of formatting.
169func rawCode(lines []codeLine) []byte {
170    b := new(bytes.Buffer)
171    for _line := range lines {
172        b.WriteString(line.L)
173        b.WriteByte('\n')
174    }
175    return b.Bytes()
176}
177
178type codeTemplateData struct {
179    Lines          []codeLine
180    PrefixSuffix []byte
181    EditNumbers  bool
182}
183
184var leadingSpaceRE = regexp.MustCompile(`^[ \t]*`)
185
186var codeTemplate = template.Must(template.New("code").Funcs(template.FuncMap{
187    "trimSpace":    strings.TrimSpace,
188    "leadingSpace"leadingSpaceRE.FindString,
189}).Parse(codeTemplateHTML))
190
191const codeTemplateHTML = `
192{{with .Prefix}}<pre style="display: none"><span>{{printf "%s" .}}</span></pre>{{end -}}
193
194<pre{{if .Edit}} contenteditable="true" spellcheck="false"{{end}}{{if .Numbers}} class="numbers"{{end}}>{{/*
195    */}}{{range .Lines}}<span num="{{.N}}">{{/*
196    */}}{{if .HL}}{{leadingSpace .L}}<b>{{trimSpace .L}}</b>{{/*
197    */}}{{else}}{{.L}}{{end}}{{/*
198*/}}</span>
199{{end}}</pre>
200{{with .Suffix}}<pre style="display: none"><span>{{printf "%s" .}}</span></pre>{{end -}}
201`
202
203// codeLine represents a line of code extracted from a source file.
204type codeLine struct {
205    L  string // The line of code.
206    N  int    // The line number from the source file.
207    HL bool   // Whether the line should be highlighted.
208}
209
210// codeLines takes a source file and returns the lines that
211// span the byte range specified by start and end.
212// It discards lines that end in "OMIT".
213func codeLines(src []bytestartend int) (lines []codeLine) {
214    startLine := 1
215    for ib := range src {
216        if i == start {
217            break
218        }
219        if b == '\n' {
220            startLine++
221        }
222    }
223    s := bufio.NewScanner(bytes.NewReader(src[start:end]))
224    for n := startLines.Scan(); n++ {
225        l := s.Text()
226        if strings.HasSuffix(l"OMIT") {
227            continue
228        }
229        lines = append(linescodeLine{LlNn})
230    }
231    // Trim leading and trailing blank lines.
232    for len(lines) > 0 && len(lines[0].L) == 0 {
233        lines = lines[1:]
234    }
235    for len(lines) > 0 && len(lines[len(lines)-1].L) == 0 {
236        lines = lines[:len(lines)-1]
237    }
238    return
239}
240
241func parseArgs(name stringline intargs []string) (res []interface{}, err error) {
242    res = make([]interface{}, len(args))
243    for iv := range args {
244        if len(v) == 0 {
245            return nilfmt.Errorf("%s:%d bad code argument %q"namelinev)
246        }
247        switch v[0] {
248        case '0''1''2''3''4''5''6''7''8''9':
249            nerr := strconv.Atoi(v)
250            if err != nil {
251                return nilfmt.Errorf("%s:%d bad code argument %q"namelinev)
252            }
253            res[i] = n
254        case '/':
255            if len(v) < 2 || v[len(v)-1] != '/' {
256                return nilfmt.Errorf("%s:%d bad code argument %q"namelinev)
257            }
258            res[i] = v
259        case '$':
260            res[i] = "$"
261        case '_':
262            if len(v) == 1 {
263                // Do nothing; "_" indicates an intentionally empty parameter.
264                break
265            }
266            fallthrough
267        default:
268            return nilfmt.Errorf("%s:%d bad code argument %q"namelinev)
269        }
270    }
271    return
272}
273
MembersX
formatLines.RangeStmt_4584.i
parseArgs.RangeStmt_7167.BlockStmt.BlockStmt.n
parseArgs.args
bytes
NotesEnabled
Code.Raw
codeTemplateData.Prefix
codeLines.src
codeLines.n
parseArgs.name
fmt
parseCode.filename
codeLines.RangeStmt_6524.b
template
parseCode.sourceLine
rawCode.lines
codeLine.N
codeLines.startLine
parseCode.ctx
parseCode.origCmd
formatLines.RangeStmt_4584.line
rawCode.RangeStmt_5136.line
codeLines.s
codeLines.BlockStmt.l
formatLines
formatLines.RangeStmt_4584.BlockStmt.m
codeLines.start
Code.TemplateName.c
parseCode.hl
codeTemplateData.Edit
bufio
Code.Edit
rawCode
rawCode.b
codeLines.lines
Code.Cmd
parseCode.args
parseCode.lines
codeTemplateData.Lines
codeLine.L
codeLine.HL
parseCode
parseCode.textBytes
parseCode.hi
parseCode.data
filepath
PlayEnabled
Code.Text
codeTemplateData.Numbers
Code.Ext
parseArgs.line
parseArgs.res
formatLines.formatted
Code.Play
Code.FileName
Code.PresentCmd.c
Code.PresentCmd
parseCode.cmd
parseCode.addr
formatLines.highlight
codeTemplateData
parseArgs
parseArgs.RangeStmt_7167.BlockStmt.BlockStmt.err
Code.TemplateName
codeLines.RangeStmt_6524.i
parseArgs.err
codeLine
Code
parseCode.highlight
parseCode.err
parseCode.lo
parseCode.buf
formatLines.lines
codeTemplateHTML
parseArgs.RangeStmt_7167.i
parseArgs.RangeStmt_7167.v
parseCode.sourceFile
codeTemplateData.Suffix
codeLines
codeLines.end
Members
X