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