1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
import std/streams
import std/strutils
import std/unicode
import types/cell
import utils/strwidth
type StreamRenderer* = ref object
ansiparser: AnsiCodeParser
format: Format
af: bool
stream: Stream
newline: bool
w: int
j: int # byte in line
proc newStreamRenderer*(): StreamRenderer =
return StreamRenderer(ansiparser: AnsiCodeParser(state: PARSE_DONE))
proc rewind*(renderer: StreamRenderer) =
renderer.format = Format()
renderer.ansiparser.state = PARSE_DONE
proc addFormat(grid: var FlexibleGrid, renderer: StreamRenderer) =
if renderer.af:
renderer.af = false
if renderer.j == grid[^1].str.len:
grid[^1].addFormat(renderer.w, renderer.format)
proc processBackspace(grid: var FlexibleGrid, renderer: StreamRenderer,
r: Rune): bool =
let pj = renderer.j
var cr: Rune
fastRuneAt(grid[^1].str, renderer.j, cr)
if r == Rune('_') or cr == Rune('_') or r == cr:
let flag = if r == cr: FLAG_BOLD else: FLAG_UNDERLINE
if r != cr and cr == Rune('_'):
# original is _, we must replace :(
# like less, we assume no double _ for double width characters.
grid[^1].str.delete(pj..<renderer.j)
let s = $r
grid[^1].str.insert(s, pj)
renderer.j = pj + s.len
let n = grid[^1].findFormatN(renderer.w) - 1
if n != -1 and grid[^1].formats[n].pos == renderer.w:
let flags = grid[^1].formats[n].format.flags
if r == cr and r == Rune('_') and flag in flags:
# double overstrike of _, this is nonsensical on a teletype but less(1)
# treats it as an underline so we do that too
grid[^1].formats[n].format.flags.incl(FLAG_UNDERLINE)
else:
grid[^1].formats[n].format.flags.incl(flag)
elif n != -1:
var format = grid[^1].formats[n].format
format.flags.incl(flag)
grid[^1].insertFormat(renderer.w, n + 1, format)
else:
grid[^1].addFormat(renderer.w, Format(flags: {flag}))
renderer.w += r.twidth(renderer.w)
if renderer.j == grid[^1].str.len:
grid[^1].addFormat(renderer.w, Format())
return true
let n = grid[^1].findFormatN(renderer.w)
grid[^1].formats.setLen(n)
grid[^1].str.setLen(renderer.j)
return false
proc processAscii(grid: var FlexibleGrid, renderer: StreamRenderer, c: char) =
case c
of '\b':
if renderer.j == 0:
grid[^1].str &= c
inc renderer.j
renderer.w += Rune(c).twidth(renderer.w)
else:
let (r, len) = lastRune(grid[^1].str, grid[^1].str.high)
renderer.j -= len
renderer.w -= r.twidth(renderer.w)
of '\n':
grid.addFormat(renderer)
renderer.newline = true
of '\r': discard
of '\e':
renderer.ansiparser.reset()
else:
grid.addFormat(renderer)
grid[^1].str &= c
renderer.w += Rune(c).twidth(renderer.w)
inc renderer.j
proc renderChunk*(grid: var FlexibleGrid, renderer: StreamRenderer,
buf: openArray[char]) =
if grid.len == 0:
grid.addLine()
var i = 0
while i < buf.len:
if renderer.newline:
# avoid newline at end of stream
grid.addLine()
renderer.newline = false
renderer.w = 0
renderer.j = 0
let pi = i
var r: Rune
fastRuneAt(buf, i, r)
if renderer.j < grid[^1].str.len:
if grid.processBackspace(renderer, r):
continue
if uint32(r) < 0x80:
let c = char(r)
if renderer.ansiparser.state != PARSE_DONE:
if not renderer.ansiparser.parseAnsiCode(renderer.format, c):
if renderer.ansiparser.state == PARSE_DONE:
renderer.af = true
continue
grid.processAscii(renderer, c)
else:
grid.addFormat(renderer)
grid[^1].str &= r
renderer.w += r.twidth(renderer.w)
renderer.j += i - pi
|