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