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 }