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 }