|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "fmt" |
| 6 | + "go/scanner" |
| 7 | + "go/token" |
| 8 | + "image/color" |
| 9 | + "log" |
| 10 | + "strings" |
| 11 | + |
| 12 | + "github.com/fogleman/gg" |
| 13 | +) |
| 14 | + |
| 15 | +type CodeView struct { |
| 16 | + alloc *Allocation |
| 17 | + |
| 18 | + // Information regarding the source code it contains |
| 19 | + fset *token.FileSet |
| 20 | + filename string |
| 21 | + source []byte |
| 22 | + sourceLines int |
| 23 | + sourceWidth int |
| 24 | + tokens []CodeViewToken |
| 25 | + |
| 26 | + // Information regarding the typography |
| 27 | + fontFace string |
| 28 | + fontSize float64 |
| 29 | + lineHeight float64 |
| 30 | +} |
| 31 | + |
| 32 | +type CodeViewToken struct { |
| 33 | + pos token.Pos |
| 34 | + tok token.Token |
| 35 | + lit string |
| 36 | +} |
| 37 | + |
| 38 | +func (tok *CodeViewToken) Len() int { |
| 39 | + switch tok.tok { |
| 40 | + case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, |
| 41 | + token.OR, token.XOR, token.LSS, token.GTR, token.ASSIGN, token.NOT, |
| 42 | + token.LPAREN, token.LBRACK, token.LBRACE, token.COMMA, token.PERIOD, |
| 43 | + token.RPAREN, token.RBRACK, token.RBRACE, token.SEMICOLON, token.COLON: |
| 44 | + // One-character operators |
| 45 | + return 1 |
| 46 | + case token.SHL, token.SHR, token.AND_NOT, token.ADD_ASSIGN, token.SUB_ASSIGN, |
| 47 | + token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN, token.AND_ASSIGN, |
| 48 | + token.OR_ASSIGN, token.XOR_ASSIGN, token.LAND, token.LOR, token.ARROW, |
| 49 | + token.INC, token.DEC, token.EQL, token.NEQ, token.LEQ, token.GEQ, |
| 50 | + token.DEFINE: |
| 51 | + // Two-character operators |
| 52 | + return 2 |
| 53 | + case token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN, token.ELLIPSIS: |
| 54 | + // Three-character operators |
| 55 | + return 3 |
| 56 | + } |
| 57 | + return len(tok.lit) |
| 58 | +} |
| 59 | + |
| 60 | +func NewCodeView(filename string, source []byte) *CodeView { |
| 61 | + codeview := &CodeView{ |
| 62 | + filename: filename, |
| 63 | + source: source, |
| 64 | + |
| 65 | + fontFace: "/usr/share/fonts/TTF/DejaVuSansMono.ttf", |
| 66 | + fontSize: 16, |
| 67 | + lineHeight: 24, |
| 68 | + } |
| 69 | + |
| 70 | + // Get line count |
| 71 | + |
| 72 | + lines := bytes.Split(source, []byte("\n")) |
| 73 | + for line := range lines { |
| 74 | + width := len(lines[line]) |
| 75 | + if width > codeview.sourceWidth { |
| 76 | + codeview.sourceWidth = width |
| 77 | + } |
| 78 | + codeview.sourceLines++ |
| 79 | + } |
| 80 | + |
| 81 | + // Tokenize |
| 82 | + |
| 83 | + codeview.fset = token.NewFileSet() |
| 84 | + scan := scanner.Scanner{} |
| 85 | + |
| 86 | + file := codeview.fset.AddFile(filename, codeview.fset.Base(), len(source)) |
| 87 | + scan.Init(file, source, nil, scanner.ScanComments) |
| 88 | + |
| 89 | + for { |
| 90 | + pos, tok, lit := scan.Scan() |
| 91 | + if tok == token.EOF { |
| 92 | + break |
| 93 | + } |
| 94 | + |
| 95 | + codeview.tokens = append(codeview.tokens, CodeViewToken{pos, tok, lit}) |
| 96 | + } |
| 97 | + |
| 98 | + return codeview |
| 99 | +} |
| 100 | + |
| 101 | +func (c *CodeView) Render(context *gg.Context) { |
| 102 | + alloc := c.Allocation() |
| 103 | + |
| 104 | + context.SetColor(PaperColorScheme.Back) |
| 105 | + context.DrawRectangle(0, 0, alloc.Width, alloc.Height) |
| 106 | + context.FillPreserve() |
| 107 | + context.Clip() |
| 108 | + defer context.ResetClip() |
| 109 | + |
| 110 | + context.SetColor(PaperColorScheme.Text) |
| 111 | + if err := context.LoadFontFace(c.fontFace, c.fontSize); err != nil { |
| 112 | + log.Print(err) |
| 113 | + return |
| 114 | + } |
| 115 | + |
| 116 | + lineY := c.lineHeight / 2 |
| 117 | + lines := bytes.Split(c.source, []byte("\n")) |
| 118 | + currentTokIndex := 0 |
| 119 | + |
| 120 | + for lineIndex, line := range lines { |
| 121 | + // Keep track of the indices to change colors |
| 122 | + type ColorMarker struct { |
| 123 | + Index int |
| 124 | + Color color.Color |
| 125 | + } |
| 126 | + fmt.Printf("\nline %d of %d: %s\n", lineIndex+1, len(lines), line) |
| 127 | + |
| 128 | + // Add all tokens from this line |
| 129 | + markers := []ColorMarker{{0, PaperColorScheme.Text}} |
| 130 | + for currentTokIndex < len(c.tokens) { |
| 131 | + tok := c.tokens[currentTokIndex] |
| 132 | + pos := c.fset.Position(tok.pos) |
| 133 | + if pos.Line != lineIndex+1 { |
| 134 | + // There are no more tokens on this line |
| 135 | + break |
| 136 | + } |
| 137 | + indexStart := pos.Column - 1 |
| 138 | + indexEnd := indexStart + tok.Len() |
| 139 | + if indexEnd > len(line) { |
| 140 | + indexEnd = len(line) |
| 141 | + } |
| 142 | + fmt.Printf("adding token %q %q index %d:%d\n", tok.tok, tok.lit, indexStart, indexEnd) |
| 143 | + markers = append(markers, |
| 144 | + ColorMarker{indexStart, PaperColorScheme.Tokens[tok.tok]}, |
| 145 | + ColorMarker{indexEnd, PaperColorScheme.Text}) |
| 146 | + currentTokIndex++ |
| 147 | + } |
| 148 | + markers = append(markers, ColorMarker{len(line), PaperColorScheme.Text}) |
| 149 | + |
| 150 | + // Iterate each consecutive pair of markers that has text in between |
| 151 | + var lineX float64 |
| 152 | + for index := 0; index < len(markers)-1; index++ { |
| 153 | + markerA, markerB := markers[index], markers[index+1] |
| 154 | + if markerA.Index == markerB.Index { |
| 155 | + continue |
| 156 | + } |
| 157 | + str := string(line[markerA.Index:markerB.Index]) |
| 158 | + str = strings.Replace(str, "\t", " ", -1) |
| 159 | + |
| 160 | + width, _ := context.MeasureString(str) |
| 161 | + context.SetColor(markerA.Color) |
| 162 | + context.DrawStringAnchored(str, lineX, lineY, 0, 0.5) |
| 163 | + |
| 164 | + lineX += width |
| 165 | + } |
| 166 | + lineY += c.lineHeight |
| 167 | + } |
| 168 | + context.Fill() |
| 169 | +} |
| 170 | + |
| 171 | +func (c *CodeView) Measure(orient Orientation, forsize float64) (minimum, natural float64) { |
| 172 | + switch orient { |
| 173 | + case Horizontal: |
| 174 | + // Natural and minimum size is the width of the longest line |
| 175 | + minimum = float64(c.sourceWidth) * c.fontSize |
| 176 | + natural = minimum |
| 177 | + |
| 178 | + case Vertical: |
| 179 | + // Natural size is the number of lines times the line height |
| 180 | + // Minimum size is 3 lines |
| 181 | + minimum = 3 * c.lineHeight |
| 182 | + natural = float64(c.sourceLines) * c.lineHeight |
| 183 | + } |
| 184 | + return |
| 185 | +} |
| 186 | + |
| 187 | +func (c *CodeView) Allocate(alloc *Allocation) { |
| 188 | + c.alloc = alloc |
| 189 | +} |
| 190 | + |
| 191 | +func (c *CodeView) Allocation() Allocation { |
| 192 | + return *c.alloc |
| 193 | +} |
0 commit comments