mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 02:57:52 +08:00
140 lines
3.6 KiB
Go
140 lines
3.6 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// SourceContext is a range of text in a source code. It can point to another
|
|
// SourceContext, thus forming a linked list. It is used for tracebacks.
|
|
type SourceContext struct {
|
|
Name string
|
|
Source string
|
|
Begin int
|
|
End int
|
|
Next *SourceContext
|
|
|
|
savedPprintInfo *contextPprintInfo
|
|
}
|
|
|
|
func NewSourceContext(name, source string, begin, end int, next *SourceContext) *SourceContext {
|
|
return &SourceContext{name, source, begin, end, next, nil}
|
|
}
|
|
|
|
// contextPprintInfo is information about the source context that are friendly to
|
|
// human, used when pretty-printing.
|
|
type contextPprintInfo struct {
|
|
// Head is the piece of text immediately before Culprit, extending to, but
|
|
// not including the closest line boundary. If Culprit already starts after
|
|
// a line boundary, Head is an empty string.
|
|
Head string
|
|
// Culprit is Source[Begin:End], with any trailing newlines stripped.
|
|
Culprit string
|
|
// Tail is the piece of text immediately after Culprit, extending to, but
|
|
// not including the closet line boundary. If Culprit already ends before a
|
|
// line boundary, Tail is an empty string.
|
|
Tail string
|
|
// BeginLine is the (1-based) line number that the first character of Culprit is on.
|
|
BeginLine int
|
|
// EndLine is the (1-based) line number that the last character of Culprit is on.
|
|
EndLine int
|
|
}
|
|
|
|
// Variables controlling the style of the culprit.
|
|
var (
|
|
CulpritStyle = "1;4"
|
|
CulpritPlaceHolder = "^"
|
|
)
|
|
|
|
func (sc *SourceContext) pprintInfo() *contextPprintInfo {
|
|
if sc.savedPprintInfo != nil {
|
|
return sc.savedPprintInfo
|
|
}
|
|
|
|
before := sc.Source[:sc.Begin]
|
|
culprit := sc.Source[sc.Begin:sc.End]
|
|
after := sc.Source[sc.End:]
|
|
|
|
head := lastLine(before)
|
|
beginLine := strings.Count(before, "\n") + 1
|
|
|
|
// If the culprit ends with a newline, stripe it. Otherwise, tail is nonempty.
|
|
var tail string
|
|
if strings.HasSuffix(culprit, "\n") {
|
|
culprit = culprit[:len(culprit)-1]
|
|
} else {
|
|
tail = firstLine(after)
|
|
}
|
|
|
|
endLine := beginLine + strings.Count(culprit, "\n")
|
|
|
|
sc.savedPprintInfo = &contextPprintInfo{head, culprit, tail, beginLine, endLine}
|
|
return sc.savedPprintInfo
|
|
}
|
|
|
|
// Pprint pretty-prints a SourceContext.
|
|
func (sc *SourceContext) Pprint(w io.Writer, sourceIndent string) {
|
|
if sc.complainBadPosition(w) {
|
|
return
|
|
}
|
|
sc.printPosDescription(w)
|
|
fmt.Fprintf(w, "\n%s%s", sourceIndent, "")
|
|
sc.pprintRelevantSource(w, sourceIndent)
|
|
}
|
|
|
|
func (sc *SourceContext) complainBadPosition(w io.Writer) bool {
|
|
if sc.Begin == -1 {
|
|
fmt.Fprintf(w, "%s, unknown position", sc.Name)
|
|
return true
|
|
} else if sc.Begin < 0 || sc.End > len(sc.Source) || sc.Begin > sc.End {
|
|
fmt.Fprintf(w, "%s, invalid position %d-%d", sc.Name, sc.Begin, sc.End)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (sc *SourceContext) printPosDescription(w io.Writer) {
|
|
info := sc.pprintInfo()
|
|
|
|
if info.BeginLine == info.EndLine {
|
|
fmt.Fprintf(w, "%s, line %d:", sc.Name, info.BeginLine)
|
|
} else {
|
|
fmt.Fprintf(w, "%s, line %d-%d:", sc.Name, info.BeginLine, info.EndLine)
|
|
}
|
|
}
|
|
|
|
func (sc *SourceContext) pprintRelevantSource(w io.Writer, sourceIndent string) {
|
|
info := sc.pprintInfo()
|
|
|
|
fmt.Fprint(w, info.Head)
|
|
|
|
culprit := info.Culprit
|
|
if culprit == "" {
|
|
culprit = CulpritPlaceHolder
|
|
}
|
|
|
|
for i, line := range strings.Split(culprit, "\n") {
|
|
if i > 0 {
|
|
fmt.Fprintf(w, "\n%s", sourceIndent)
|
|
}
|
|
fmt.Fprintf(w, "\033[%sm%s\033[m", CulpritStyle, line)
|
|
}
|
|
|
|
fmt.Fprint(w, info.Tail)
|
|
}
|
|
|
|
func firstLine(s string) string {
|
|
i := strings.IndexByte(s, '\n')
|
|
if i == -1 {
|
|
return s
|
|
}
|
|
return s[:i]
|
|
}
|
|
|
|
func lastLine(s string) string {
|
|
// When s does not contain '\n', LastIndexByte returns -1, which happens to
|
|
// be what we want.
|
|
return s[strings.LastIndexByte(s, '\n')+1:]
|
|
}
|