github.com/undoio/delve@v1.9.0/pkg/terminal/colorize/colorize.go (about)

     1  package colorize
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/parser"
     7  	"go/token"
     8  	"io"
     9  	"io/ioutil"
    10  	"path/filepath"
    11  	"reflect"
    12  	"sort"
    13  )
    14  
    15  // Style describes the style of a chunk of text.
    16  type Style uint8
    17  
    18  const (
    19  	NormalStyle Style = iota
    20  	KeywordStyle
    21  	StringStyle
    22  	NumberStyle
    23  	CommentStyle
    24  	LineNoStyle
    25  	ArrowStyle
    26  )
    27  
    28  // Print prints to out a syntax highlighted version of the text read from
    29  // reader, between lines startLine and endLine.
    30  func Print(out io.Writer, path string, reader io.Reader, startLine, endLine, arrowLine int, colorEscapes map[Style]string) error {
    31  	buf, err := ioutil.ReadAll(reader)
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	w := &lineWriter{w: out, lineRange: [2]int{startLine, endLine}, arrowLine: arrowLine, colorEscapes: colorEscapes}
    37  
    38  	if filepath.Ext(path) != ".go" {
    39  		w.Write(NormalStyle, buf, true)
    40  		return nil
    41  	}
    42  
    43  	var fset token.FileSet
    44  	f, err := parser.ParseFile(&fset, path, buf, parser.ParseComments)
    45  	if err != nil {
    46  		w.Write(NormalStyle, buf, true)
    47  		return nil
    48  	}
    49  
    50  	var base int
    51  
    52  	fset.Iterate(func(file *token.File) bool {
    53  		base = file.Base()
    54  		return false
    55  	})
    56  
    57  	toks := []colorTok{}
    58  
    59  	emit := func(tok token.Token, start, end token.Pos) {
    60  		if _, ok := tokenToStyle[tok]; !ok {
    61  			return
    62  		}
    63  		start -= token.Pos(base)
    64  		if end == token.NoPos {
    65  			// end == token.NoPos it's a keyword and we have to find where it ends by looking at the file
    66  			for end = start; end < token.Pos(len(buf)); end++ {
    67  				if buf[end] < 'a' || buf[end] > 'z' {
    68  					break
    69  				}
    70  			}
    71  		} else {
    72  			end -= token.Pos(base)
    73  		}
    74  		if start < 0 || start >= end || end > token.Pos(len(buf)) {
    75  			// invalid token?
    76  			return
    77  		}
    78  		toks = append(toks, colorTok{tok, int(start), int(end)})
    79  	}
    80  
    81  	for _, cgrp := range f.Comments {
    82  		for _, cmnt := range cgrp.List {
    83  			emit(token.COMMENT, cmnt.Pos(), cmnt.End())
    84  		}
    85  	}
    86  
    87  	ast.Inspect(f, func(n ast.Node) bool {
    88  		if n == nil {
    89  			return true
    90  		}
    91  
    92  		switch n := n.(type) {
    93  		case *ast.File:
    94  			emit(token.PACKAGE, f.Package, token.NoPos)
    95  			return true
    96  		case *ast.BasicLit:
    97  			emit(n.Kind, n.Pos(), n.End())
    98  			return true
    99  		case *ast.Ident:
   100  			//TODO(aarzilli): builtin functions? basic types?
   101  			return true
   102  		case *ast.IfStmt:
   103  			emit(token.IF, n.If, token.NoPos)
   104  			if n.Else != nil {
   105  				for elsepos := int(n.Body.End()) - base; elsepos < len(buf)-4; elsepos++ {
   106  					if string(buf[elsepos:][:4]) == "else" {
   107  						emit(token.ELSE, token.Pos(elsepos+base), token.Pos(elsepos+base+4))
   108  						break
   109  					}
   110  				}
   111  			}
   112  			return true
   113  		}
   114  
   115  		nval := reflect.ValueOf(n)
   116  		if nval.Kind() != reflect.Ptr {
   117  			return true
   118  		}
   119  		nval = nval.Elem()
   120  		if nval.Kind() != reflect.Struct {
   121  			return true
   122  		}
   123  
   124  		tokposval := nval.FieldByName("TokPos")
   125  		tokval := nval.FieldByName("Tok")
   126  		if tokposval != (reflect.Value{}) && tokval != (reflect.Value{}) {
   127  			emit(tokval.Interface().(token.Token), tokposval.Interface().(token.Pos), token.NoPos)
   128  		}
   129  
   130  		for _, kwname := range []string{"Case", "Begin", "Defer", "For", "Func", "Go", "Interface", "Map", "Return", "Select", "Struct", "Switch"} {
   131  			kwposval := nval.FieldByName(kwname)
   132  			if kwposval != (reflect.Value{}) {
   133  				kwpos, ok := kwposval.Interface().(token.Pos)
   134  				if ok && kwpos != token.NoPos {
   135  					emit(token.ILLEGAL, kwpos, token.NoPos)
   136  				}
   137  			}
   138  		}
   139  
   140  		return true
   141  	})
   142  
   143  	sort.Slice(toks, func(i, j int) bool { return toks[i].start < toks[j].start })
   144  
   145  	flush := func(start, end int, style Style) {
   146  		if start < end {
   147  			w.Write(style, buf[start:end], end == len(buf))
   148  		}
   149  	}
   150  
   151  	cur := 0
   152  	for _, tok := range toks {
   153  		flush(cur, tok.start, NormalStyle)
   154  		flush(tok.start, tok.end, tokenToStyle[tok.tok])
   155  		cur = tok.end
   156  	}
   157  	if cur != len(buf) {
   158  		flush(cur, len(buf), NormalStyle)
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  var tokenToStyle = map[token.Token]Style{
   165  	token.ILLEGAL:     KeywordStyle,
   166  	token.COMMENT:     CommentStyle,
   167  	token.INT:         NumberStyle,
   168  	token.FLOAT:       NumberStyle,
   169  	token.IMAG:        NumberStyle,
   170  	token.CHAR:        StringStyle,
   171  	token.STRING:      StringStyle,
   172  	token.BREAK:       KeywordStyle,
   173  	token.CASE:        KeywordStyle,
   174  	token.CHAN:        KeywordStyle,
   175  	token.CONST:       KeywordStyle,
   176  	token.CONTINUE:    KeywordStyle,
   177  	token.DEFAULT:     KeywordStyle,
   178  	token.DEFER:       KeywordStyle,
   179  	token.ELSE:        KeywordStyle,
   180  	token.FALLTHROUGH: KeywordStyle,
   181  	token.FOR:         KeywordStyle,
   182  	token.FUNC:        KeywordStyle,
   183  	token.GO:          KeywordStyle,
   184  	token.GOTO:        KeywordStyle,
   185  	token.IF:          KeywordStyle,
   186  	token.IMPORT:      KeywordStyle,
   187  	token.INTERFACE:   KeywordStyle,
   188  	token.MAP:         KeywordStyle,
   189  	token.PACKAGE:     KeywordStyle,
   190  	token.RANGE:       KeywordStyle,
   191  	token.RETURN:      KeywordStyle,
   192  	token.SELECT:      KeywordStyle,
   193  	token.STRUCT:      KeywordStyle,
   194  	token.SWITCH:      KeywordStyle,
   195  	token.TYPE:        KeywordStyle,
   196  	token.VAR:         KeywordStyle,
   197  }
   198  
   199  type colorTok struct {
   200  	tok        token.Token // the token type or ILLEGAL for keywords
   201  	start, end int         // start and end positions of the token
   202  }
   203  
   204  type lineWriter struct {
   205  	w         io.Writer
   206  	lineRange [2]int
   207  	arrowLine int
   208  
   209  	curStyle Style
   210  	started  bool
   211  	lineno   int
   212  
   213  	colorEscapes map[Style]string
   214  }
   215  
   216  func (w *lineWriter) style(style Style) {
   217  	if w.colorEscapes == nil {
   218  		return
   219  	}
   220  	esc := w.colorEscapes[style]
   221  	if esc == "" {
   222  		esc = w.colorEscapes[NormalStyle]
   223  	}
   224  	fmt.Fprintf(w.w, "%s", esc)
   225  }
   226  
   227  func (w *lineWriter) inrange() bool {
   228  	lno := w.lineno
   229  	if !w.started {
   230  		lno = w.lineno + 1
   231  	}
   232  	return lno >= w.lineRange[0] && lno < w.lineRange[1]
   233  }
   234  
   235  func (w *lineWriter) nl() {
   236  	w.lineno++
   237  	if !w.inrange() || !w.started {
   238  		return
   239  	}
   240  	w.style(ArrowStyle)
   241  	if w.lineno == w.arrowLine {
   242  		fmt.Fprintf(w.w, "=>")
   243  	} else {
   244  		fmt.Fprintf(w.w, "  ")
   245  	}
   246  	w.style(LineNoStyle)
   247  	fmt.Fprintf(w.w, "%4d:\t", w.lineno)
   248  	w.style(w.curStyle)
   249  }
   250  
   251  func (w *lineWriter) writeInternal(style Style, data []byte) {
   252  	if !w.inrange() {
   253  		return
   254  	}
   255  
   256  	if !w.started {
   257  		w.started = true
   258  		w.curStyle = style
   259  		w.nl()
   260  	} else if w.curStyle != style {
   261  		w.curStyle = style
   262  		w.style(w.curStyle)
   263  	}
   264  
   265  	w.w.Write(data)
   266  }
   267  
   268  func (w *lineWriter) Write(style Style, data []byte, last bool) {
   269  	cur := 0
   270  	for i := range data {
   271  		if data[i] == '\n' {
   272  			if last && i == len(data)-1 {
   273  				w.writeInternal(style, data[cur:i])
   274  				if w.curStyle != NormalStyle {
   275  					w.style(NormalStyle)
   276  				}
   277  				if w.inrange() {
   278  					w.w.Write([]byte{'\n'})
   279  				}
   280  				last = false
   281  			} else {
   282  				w.writeInternal(style, data[cur:i+1])
   283  				w.nl()
   284  			}
   285  			cur = i + 1
   286  		}
   287  	}
   288  	if cur < len(data) {
   289  		w.writeInternal(style, data[cur:])
   290  	}
   291  	if last {
   292  		if w.curStyle != NormalStyle {
   293  			w.style(NormalStyle)
   294  		}
   295  		if w.inrange() {
   296  			w.w.Write([]byte{'\n'})
   297  		}
   298  	}
   299  }