parent
c40774d3d4
commit
8e91d3b0bf
@ -54,8 +54,24 @@ type Config struct {
|
||||
ShowLocationExpr bool `yaml:"show-location-expr"`
|
||||
|
||||
// Source list line-number color (3/4 bit color codes as defined
|
||||
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
|
||||
SourceListLineColor int `yaml:"source-list-line-color"`
|
||||
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors),
|
||||
// or a string containing a terminal escape sequence.
|
||||
SourceListLineColor interface{} `yaml:"source-list-line-color"`
|
||||
|
||||
// Source list arrow color, as a terminal escape sequence.
|
||||
SourceListArrowColor string `yaml:"source-list-arrow-color"`
|
||||
|
||||
// Source list keyword color, as a terminal escape sequence.
|
||||
SourceListKeywordColor string `yaml:"source-list-keyword-color"`
|
||||
|
||||
// Source list string color, as a terminal escape sequence.
|
||||
SourceListStringColor string `yaml:"source-list-string-color"`
|
||||
|
||||
// Source list number color, as a terminal escape sequence.
|
||||
SourceListNumberColor string `yaml:"source-list-number-color"`
|
||||
|
||||
// Source list comment color, as a terminal escape sequence.
|
||||
SourceListCommentColor string `yaml:"source-list-comment-color"`
|
||||
|
||||
// number of lines to list above and below cursor when printfile() is
|
||||
// called (i.e. when execution stops, listCommand is used, etc)
|
||||
@ -215,8 +231,16 @@ func writeDefaultConfig(f *os.File) error {
|
||||
# Uncomment the following line and set your preferred ANSI foreground color
|
||||
# for source line numbers in the (list) command (if unset, default is 34,
|
||||
# dark blue) See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
|
||||
# Alternatively a string containing an escape sequence can also be used.
|
||||
# source-list-line-color: 34
|
||||
|
||||
# Uncomment the following lines to change the colors used by syntax highlighting.
|
||||
# source-list-keyword-color: "\x1b[0m"
|
||||
# source-list-string-color: "\x1b[92m"
|
||||
# source-list-number-color: "\x1b[0m"
|
||||
# source-list-comment-color: "\x1b[95m"
|
||||
# source-list-arrow-color: "\x1b[93m"
|
||||
|
||||
# Uncomment to change the number of lines printed above and below cursor when
|
||||
# listing source code.
|
||||
# source-list-line-count: 5
|
||||
|
||||
298
pkg/terminal/colorize/colorize.go
Normal file
298
pkg/terminal/colorize/colorize.go
Normal file
@ -0,0 +1,298 @@
|
||||
package colorize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Style describes the style of a chunk of text.
|
||||
type Style uint8
|
||||
|
||||
const (
|
||||
NormalStyle Style = iota
|
||||
KeywordStyle
|
||||
StringStyle
|
||||
NumberStyle
|
||||
CommentStyle
|
||||
LineNoStyle
|
||||
ArrowStyle
|
||||
)
|
||||
|
||||
// Print prints to out a syntax highlighted version of the text read from
|
||||
// reader, between lines startLine and endLine.
|
||||
func Print(out io.Writer, path string, reader io.Reader, startLine, endLine, arrowLine int, colorEscapes map[Style]string) error {
|
||||
buf, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := &lineWriter{w: out, lineRange: [2]int{startLine, endLine}, arrowLine: arrowLine, colorEscapes: colorEscapes}
|
||||
|
||||
if filepath.Ext(path) != ".go" {
|
||||
w.Write(NormalStyle, buf, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
var fset token.FileSet
|
||||
f, err := parser.ParseFile(&fset, path, buf, parser.ParseComments)
|
||||
if err != nil {
|
||||
w.Write(NormalStyle, buf, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
var base int
|
||||
|
||||
fset.Iterate(func(file *token.File) bool {
|
||||
base = file.Base()
|
||||
return false
|
||||
})
|
||||
|
||||
toks := []colorTok{}
|
||||
|
||||
emit := func(tok token.Token, start, end token.Pos) {
|
||||
if _, ok := tokenToStyle[tok]; !ok {
|
||||
return
|
||||
}
|
||||
start -= token.Pos(base)
|
||||
if end == token.NoPos {
|
||||
// end == token.NoPos it's a keyword and we have to find where it ends by looking at the file
|
||||
for end = start; end < token.Pos(len(buf)); end++ {
|
||||
if buf[end] < 'a' || buf[end] > 'z' {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
end -= token.Pos(base)
|
||||
}
|
||||
if start < 0 || start >= end || end > token.Pos(len(buf)) {
|
||||
// invalid token?
|
||||
return
|
||||
}
|
||||
toks = append(toks, colorTok{tok, int(start), int(end)})
|
||||
}
|
||||
|
||||
emit(token.PACKAGE, f.Package, token.NoPos)
|
||||
|
||||
for _, cgrp := range f.Comments {
|
||||
for _, cmnt := range cgrp.List {
|
||||
emit(token.COMMENT, cmnt.Pos(), cmnt.End())
|
||||
}
|
||||
}
|
||||
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch n := n.(type) {
|
||||
case *ast.BasicLit:
|
||||
emit(n.Kind, n.Pos(), n.End())
|
||||
return true
|
||||
case *ast.Ident:
|
||||
//TODO(aarzilli): builtin functions? basic types?
|
||||
return true
|
||||
case *ast.IfStmt:
|
||||
emit(token.IF, n.If, token.NoPos)
|
||||
if n.Else != nil {
|
||||
for elsepos := int(n.Body.End()) - base; elsepos < len(buf)-4; elsepos++ {
|
||||
if string(buf[elsepos:][:4]) == "else" {
|
||||
emit(token.ELSE, token.Pos(elsepos+base), token.Pos(elsepos+base+4))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
nval := reflect.ValueOf(n)
|
||||
if nval.Kind() != reflect.Ptr {
|
||||
return true
|
||||
}
|
||||
nval = nval.Elem()
|
||||
if nval.Kind() != reflect.Struct {
|
||||
return true
|
||||
}
|
||||
|
||||
tokposval := nval.FieldByName("TokPos")
|
||||
tokval := nval.FieldByName("Tok")
|
||||
if tokposval != (reflect.Value{}) && tokval != (reflect.Value{}) {
|
||||
emit(tokval.Interface().(token.Token), tokposval.Interface().(token.Pos), token.NoPos)
|
||||
}
|
||||
|
||||
for _, kwname := range []string{"Case", "Begin", "Defer", "Pacakge", "For", "Func", "Go", "Interface", "Map", "Return", "Select", "Struct", "Switch"} {
|
||||
kwposval := nval.FieldByName(kwname)
|
||||
if kwposval != (reflect.Value{}) {
|
||||
kwpos, ok := kwposval.Interface().(token.Pos)
|
||||
if ok {
|
||||
emit(token.ILLEGAL, kwpos, token.NoPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
sort.Slice(toks, func(i, j int) bool { return toks[i].start < toks[j].start })
|
||||
|
||||
flush := func(start, end int, style Style) {
|
||||
if start < end {
|
||||
w.Write(style, buf[start:end], end == len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
cur := 0
|
||||
for _, tok := range toks {
|
||||
flush(cur, tok.start, NormalStyle)
|
||||
flush(tok.start, tok.end, tokenToStyle[tok.tok])
|
||||
cur = tok.end
|
||||
}
|
||||
if cur != len(buf) {
|
||||
flush(cur, len(buf), NormalStyle)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var tokenToStyle = map[token.Token]Style{
|
||||
token.ILLEGAL: KeywordStyle,
|
||||
token.COMMENT: CommentStyle,
|
||||
token.INT: NumberStyle,
|
||||
token.FLOAT: NumberStyle,
|
||||
token.IMAG: NumberStyle,
|
||||
token.CHAR: StringStyle,
|
||||
token.STRING: StringStyle,
|
||||
token.BREAK: KeywordStyle,
|
||||
token.CASE: KeywordStyle,
|
||||
token.CHAN: KeywordStyle,
|
||||
token.CONST: KeywordStyle,
|
||||
token.CONTINUE: KeywordStyle,
|
||||
token.DEFAULT: KeywordStyle,
|
||||
token.DEFER: KeywordStyle,
|
||||
token.ELSE: KeywordStyle,
|
||||
token.FALLTHROUGH: KeywordStyle,
|
||||
token.FOR: KeywordStyle,
|
||||
token.FUNC: KeywordStyle,
|
||||
token.GO: KeywordStyle,
|
||||
token.GOTO: KeywordStyle,
|
||||
token.IF: KeywordStyle,
|
||||
token.IMPORT: KeywordStyle,
|
||||
token.INTERFACE: KeywordStyle,
|
||||
token.MAP: KeywordStyle,
|
||||
token.PACKAGE: KeywordStyle,
|
||||
token.RANGE: KeywordStyle,
|
||||
token.RETURN: KeywordStyle,
|
||||
token.SELECT: KeywordStyle,
|
||||
token.STRUCT: KeywordStyle,
|
||||
token.SWITCH: KeywordStyle,
|
||||
token.TYPE: KeywordStyle,
|
||||
token.VAR: KeywordStyle,
|
||||
}
|
||||
|
||||
type colorTok struct {
|
||||
tok token.Token // the token type or ILLEGAL for keywords
|
||||
start, end int // start and end positions of the token
|
||||
}
|
||||
|
||||
type lineWriter struct {
|
||||
w io.Writer
|
||||
lineRange [2]int
|
||||
arrowLine int
|
||||
|
||||
curStyle Style
|
||||
started bool
|
||||
lineno int
|
||||
|
||||
colorEscapes map[Style]string
|
||||
}
|
||||
|
||||
func (w *lineWriter) style(style Style) {
|
||||
if w.colorEscapes == nil {
|
||||
return
|
||||
}
|
||||
esc := w.colorEscapes[style]
|
||||
if esc == "" {
|
||||
esc = w.colorEscapes[NormalStyle]
|
||||
}
|
||||
fmt.Fprintf(w.w, "%s", esc)
|
||||
}
|
||||
|
||||
func (w *lineWriter) inrange() bool {
|
||||
lno := w.lineno
|
||||
if !w.started {
|
||||
lno = w.lineno + 1
|
||||
}
|
||||
return lno >= w.lineRange[0] && lno < w.lineRange[1]
|
||||
}
|
||||
|
||||
func (w *lineWriter) nl() {
|
||||
w.lineno++
|
||||
if !w.inrange() || !w.started {
|
||||
return
|
||||
}
|
||||
w.style(ArrowStyle)
|
||||
if w.lineno == w.arrowLine {
|
||||
fmt.Fprintf(w.w, "=>")
|
||||
} else {
|
||||
fmt.Fprintf(w.w, " ")
|
||||
}
|
||||
w.style(LineNoStyle)
|
||||
fmt.Fprintf(w.w, "%4d:\t", w.lineno)
|
||||
w.style(w.curStyle)
|
||||
}
|
||||
|
||||
func (w *lineWriter) writeInternal(style Style, data []byte) {
|
||||
if !w.inrange() {
|
||||
return
|
||||
}
|
||||
|
||||
if !w.started {
|
||||
w.started = true
|
||||
w.curStyle = style
|
||||
w.nl()
|
||||
} else if w.curStyle != style {
|
||||
w.curStyle = style
|
||||
w.style(w.curStyle)
|
||||
}
|
||||
|
||||
w.w.Write(data)
|
||||
}
|
||||
|
||||
func (w *lineWriter) Write(style Style, data []byte, last bool) {
|
||||
cur := 0
|
||||
for i := range data {
|
||||
if data[i] == '\n' {
|
||||
if last && i == len(data)-1 {
|
||||
w.writeInternal(style, data[cur:i])
|
||||
if w.curStyle != NormalStyle {
|
||||
w.style(NormalStyle)
|
||||
}
|
||||
if w.inrange() {
|
||||
w.w.Write([]byte{'\n'})
|
||||
}
|
||||
last = false
|
||||
} else {
|
||||
w.writeInternal(style, data[cur:i+1])
|
||||
w.nl()
|
||||
}
|
||||
cur = i + 1
|
||||
}
|
||||
}
|
||||
if cur < len(data) {
|
||||
w.writeInternal(style, data[cur:])
|
||||
}
|
||||
if last {
|
||||
if w.curStyle != NormalStyle {
|
||||
w.style(NormalStyle)
|
||||
}
|
||||
if w.inrange() {
|
||||
w.w.Write([]byte{'\n'})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/cosiner/argv"
|
||||
"github.com/go-delve/delve/pkg/locspec"
|
||||
"github.com/go-delve/delve/pkg/terminal/colorize"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
"github.com/go-delve/delve/service/rpc2"
|
||||
@ -2247,7 +2248,7 @@ func printcontext(t *Term, state *api.DebuggerState) {
|
||||
|
||||
if th.File == "" {
|
||||
fmt.Printf("Stopped at: 0x%x\n", state.CurrentThread.PC)
|
||||
t.Println("=>", "no source available")
|
||||
_ = colorize.Print(t.stdout, "", bytes.NewReader([]byte("no source available")), 1, 10, 1, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -2424,6 +2425,13 @@ func printfile(t *Term, filename string, line int, showArrow bool) error {
|
||||
if filename == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
lineCount := t.conf.GetSourceListLineCount()
|
||||
arrowLine := 0
|
||||
if showArrow {
|
||||
arrowLine = line
|
||||
}
|
||||
|
||||
file, err := os.Open(t.substitutePath(filename))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -2436,38 +2444,7 @@ func printfile(t *Term, filename string, line int, showArrow bool) error {
|
||||
fmt.Println("Warning: listing may not match stale executable")
|
||||
}
|
||||
|
||||
lineCount := t.conf.GetSourceListLineCount()
|
||||
|
||||
buf := bufio.NewScanner(file)
|
||||
l := line
|
||||
for i := 1; i < l-lineCount; i++ {
|
||||
if !buf.Scan() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
s := l - lineCount
|
||||
if s < 1 {
|
||||
s = 1
|
||||
}
|
||||
|
||||
for i := s; i <= l+lineCount; i++ {
|
||||
if !buf.Scan() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var prefix string
|
||||
if showArrow {
|
||||
prefix = " "
|
||||
if i == l {
|
||||
prefix = "=>"
|
||||
}
|
||||
}
|
||||
|
||||
prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
|
||||
t.Println(prefix, buf.Text())
|
||||
}
|
||||
return nil
|
||||
return colorize.Print(t.stdout, file.Name(), file, line-lineCount, line+lineCount+1, arrowLine, t.colorEscapes)
|
||||
}
|
||||
|
||||
// ExitRequestError is returned when the user
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
"github.com/go-delve/delve/pkg/locspec"
|
||||
"github.com/go-delve/delve/pkg/terminal/colorize"
|
||||
"github.com/go-delve/delve/pkg/terminal/starbind"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
@ -51,10 +52,10 @@ type Term struct {
|
||||
prompt string
|
||||
line *liner.State
|
||||
cmds *Commands
|
||||
dumb bool
|
||||
stdout io.Writer
|
||||
InitFile string
|
||||
displays []string
|
||||
colorEscapes map[colorize.Style]string
|
||||
|
||||
historyFile *os.File
|
||||
|
||||
@ -84,30 +85,41 @@ func New(client service.Client, conf *config.Config) *Term {
|
||||
conf = &config.Config{}
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
|
||||
dumb := strings.ToLower(os.Getenv("TERM")) == "dumb"
|
||||
if dumb {
|
||||
w = os.Stdout
|
||||
} else {
|
||||
w = getColorableWriter()
|
||||
}
|
||||
|
||||
if (conf.SourceListLineColor > ansiWhite &&
|
||||
conf.SourceListLineColor < ansiBrBlack) ||
|
||||
conf.SourceListLineColor < ansiBlack ||
|
||||
conf.SourceListLineColor > ansiBrWhite {
|
||||
conf.SourceListLineColor = ansiBlue
|
||||
}
|
||||
|
||||
t := &Term{
|
||||
client: client,
|
||||
conf: conf,
|
||||
prompt: "(dlv) ",
|
||||
line: liner.NewLiner(),
|
||||
cmds: cmds,
|
||||
dumb: dumb,
|
||||
stdout: w,
|
||||
stdout: os.Stdout,
|
||||
}
|
||||
|
||||
if strings.ToLower(os.Getenv("TERM")) != "dumb" {
|
||||
t.stdout = getColorableWriter()
|
||||
t.colorEscapes = make(map[colorize.Style]string)
|
||||
t.colorEscapes[colorize.NormalStyle] = terminalResetEscapeCode
|
||||
wd := func(s string, defaultCode int) string {
|
||||
if s == "" {
|
||||
return fmt.Sprintf(terminalHighlightEscapeCode, defaultCode)
|
||||
}
|
||||
return s
|
||||
}
|
||||
t.colorEscapes[colorize.KeywordStyle] = conf.SourceListKeywordColor
|
||||
t.colorEscapes[colorize.StringStyle] = wd(conf.SourceListStringColor, ansiBrGreen)
|
||||
t.colorEscapes[colorize.NumberStyle] = conf.SourceListNumberColor
|
||||
t.colorEscapes[colorize.CommentStyle] = wd(conf.SourceListCommentColor, ansiBrMagenta)
|
||||
t.colorEscapes[colorize.ArrowStyle] = wd(conf.SourceListArrowColor, ansiBrYellow)
|
||||
switch x := conf.SourceListLineColor.(type) {
|
||||
case string:
|
||||
t.colorEscapes[colorize.LineNoStyle] = x
|
||||
case int:
|
||||
if (x > ansiWhite && x < ansiBrBlack) || x < ansiBlack || x > ansiBrWhite {
|
||||
x = ansiBlue
|
||||
}
|
||||
t.colorEscapes[colorize.LineNoStyle] = fmt.Sprintf(terminalHighlightEscapeCode, x)
|
||||
case nil:
|
||||
t.colorEscapes[colorize.LineNoStyle] = fmt.Sprintf(terminalHighlightEscapeCode, ansiBlue)
|
||||
}
|
||||
}
|
||||
|
||||
if client != nil {
|
||||
@ -273,15 +285,6 @@ func (t *Term) Run() (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Println prints a line to the terminal.
|
||||
func (t *Term) Println(prefix, str string) {
|
||||
if !t.dumb {
|
||||
terminalColorEscapeCode := fmt.Sprintf(terminalHighlightEscapeCode, t.conf.SourceListLineColor)
|
||||
prefix = fmt.Sprintf("%s%s%s", terminalColorEscapeCode, prefix, terminalResetEscapeCode)
|
||||
}
|
||||
fmt.Fprintf(t.stdout, "%s%s\n", prefix, str)
|
||||
}
|
||||
|
||||
// Substitutes directory to source file.
|
||||
//
|
||||
// Ensures that only directory is substituted, for example:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user