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 }