Skip to content

Commit 267de5c

Browse files
committed
Add ability to render with content decorators
1 parent 3ad36b0 commit 267de5c

File tree

2 files changed

+122
-22
lines changed

2 files changed

+122
-22
lines changed

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
77
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
88

99
[compat]
10-
ColorTypes = "≥ 0.3.4"
11-
FixedPointNumbers = "≥ 0.3.0"
12-
julia = "0.7.0"
10+
ColorTypes = "0.7 - 0.11"
11+
FixedPointNumbers = "0.5 - 0.8"
12+
julia = "0.7, 1"
1313

1414
[extras]
1515
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

src/VT100.jl

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export ScreenEmulator, LineEmulator, Emulator, parse!, parse_cell!, Cell,
1111
parseall!
1212
import Base: convert, write
1313
import REPL
14-
import REPL.Terminals: cmove_right
14+
import REPL.Terminals: cmove_right, CSI
1515
import Base: iterate, setindex!, getindex, lastindex
1616

1717
module Attributes
@@ -58,25 +58,42 @@ struct Cell
5858
end
5959

6060
const colorlist = Dict(
61-
:black => 0,
62-
:red => 1,
63-
:green => 2,
64-
:yellow => 3,
65-
:blue => 4,
66-
:magenta => 5,
67-
:cyan => 6,
68-
:white => 7,
69-
:default => 9
61+
:black => 0,
62+
:red => 1,
63+
:green => 2,
64+
:yellow => 3,
65+
:blue => 4,
66+
:magenta => 5,
67+
:cyan => 6,
68+
:light_grey => 7,
69+
:light_gray => 7,
70+
:default => 9,
71+
:dark_grey => 60,
72+
:dark_gray => 60,
73+
:light_red => 61,
74+
:light_green => 62,
75+
:light_yellow => 63,
76+
:light_blue => 64,
77+
:light_magenta => 65,
78+
:light_cyan => 66,
79+
:white => 67,
7080
)
7181

7282
function Cell(c::Cell;
7383
content = c.content, flags = c.flags, fg = c.fg, bg = c.bg,
7484
attrs = c.attrs, fg_rgb = c.fg_rgb, bg_rgb = c.bg_rgb)
75-
isa(fg, Symbol) && (fg = colorlist[fg]; flags & ~(FG_IS_256 | FG_IS_RGB))
76-
isa(bg, Symbol) && (bg = colorlist[bg]; flags & ~(BG_IS_256 | BG_IS_RGB))
85+
isa(fg, Symbol) && (fg = colorlist[fg]+30; flags & ~(FG_IS_256 | FG_IS_RGB))
86+
isa(bg, Symbol) && (bg = colorlist[bg]+40; flags & ~(BG_IS_256 | BG_IS_RGB))
7787
Cell(content,flags,fg,bg,attrs,fg_rgb,bg_rgb)
7888
end
7989
Cell(c::Char) = Cell(c,0,0,0,0,RGB8(0,0,0),RGB8(0,0,0))
90+
Cell(;kwargs...) = Cell(Cell(' '); kwargs...)
91+
(c::Cell)(ch::Char; kwargs...) = Cell(c, content = ch; kwargs...)
92+
Base.convert(::Type{Cell}, c::Char) = Cell(c)
93+
Base.size(c::Cell) = ()
94+
Base.length(c::Cell) = 1
95+
Base.iterate(c::Cell) = c, nothing
96+
Base.iterate(c::Cell, _) = nothing
8097

8198
# Encode x information if foreground color, y information in background color
8299
# r encodes the lowest 8 bits, g the next, b the hight bits
@@ -108,6 +125,8 @@ mutable struct Line
108125
Line() = new(Vector{Cell}(0),false)
109126
Line(data::Vector{Cell}) = new(data,false)
110127
end
128+
Base.length(l::Line) = length(l.data)
129+
Base.resize!(l::Line, n::Int) = resize!(l.data, n)
111130
iterate(l::Line, args...) = iterate(l.data, args...)
112131
setindex!(l::Line, c, i) = setindex!(l.data, c, i)
113132
getindex(l::Line,i) = getindex(l.data,i)
@@ -125,7 +144,7 @@ struct Size
125144
height::Int
126145
end
127146

128-
abstract type Emulator end
147+
abstract type Emulator <: AbstractArray{Cell, 2} end
129148

130149
mutable struct ScreenEmulator <: Emulator
131150
ViewPortSize::Size
@@ -145,9 +164,10 @@ mutable struct ScreenEmulator <: Emulator
145164
this
146165
end
147166
end
167+
Base.size(s::ScreenEmulator) = (s.ViewPortSize.height, s.ViewPortSize.width)
148168
create_cell(em::ScreenEmulator,c::Char) = Cell(em.cur_cell, content = c)
149169
cur_cell(em::ScreenEmulator) = em.cur_cell
150-
set_cur_cell(em::ScreenEmulator,c::Cell) = em.cur_cell = c
170+
set_cur_cell!(em::ScreenEmulator,c::Cell) = em.cur_cell = c
151171
function cmove(em::ScreenEmulator, line, col)
152172
em.debug && println("Moving to $line:$col")
153173
targetline = em.firstline-1+line
@@ -178,6 +198,34 @@ function cmove_up(em::ScreenEmulator, n)
178198
em.cursor = Cursor(em.cursor.line - n, em.cursor.column)
179199
end
180200

201+
function Base.getindex(em::ScreenEmulator, row::Int, col::Int)
202+
if col > em.ViewPortSize.width
203+
throw(BoundsError(em, row, col))
204+
end
205+
thisrow = em.lines[row]
206+
l = length(thisrow)
207+
if l < col
208+
fill_space!(thisrow, l+1, col)
209+
end
210+
return thisrow[col]
211+
end
212+
213+
function Base.setindex!(em::ScreenEmulator, c::Union{Cell, Char}, line::Int, col::Int)
214+
if col > em.ViewPortSize.width
215+
throw(BoundsError(em, line, col))
216+
end
217+
while line > length(em.lines)
218+
add_line!(em)
219+
end
220+
thisrow = em.lines[line]
221+
l = length(thisrow)
222+
if l < col
223+
fill_space!(thisrow, l+1, col)
224+
end
225+
thisrow[col] = c
226+
return thisrow[col]
227+
end
228+
181229
# An emulator that works a line at a time and does not support screen movement
182230
# commands
183231
mutable struct LineEmulator <: Emulator
@@ -255,15 +303,61 @@ const ExtendedContents = String[]
255303

256304
# Render the contents of this emulator into another terminal.
257305
function render(term::IO, em::Emulator)
258-
dump(term,devnull,em)
306+
buf = IOBuffer()
307+
dump(buf,devnull,em, render_content_decorators=true)
308+
write(term, take!(buf))
309+
end
310+
311+
function change_color(buf, is_fg, flags, color, rgb, is_256, is_rgb)
312+
write(buf,CSI)
313+
if (flags & is_256) != 0
314+
write(buf, is_fg ? '3' : '4', "8;5;",string(color))
315+
elseif (flags & is_rgb) != 0
316+
write(buf, is_fg ? '3' : '4', "8;2;",string(rgb.r.i),';',
317+
string(rgb.g.i),';',
318+
string(rgb.b.i))
319+
else
320+
write(buf,string(color))
321+
end
322+
write(buf,'m')
323+
end
324+
325+
function change_fg_color(buf, cell)
326+
change_color(buf, true, cell.flags, cell.fg, cell.fg_rgb, FG_IS_256, FG_IS_RGB)
327+
end
328+
329+
function change_bg_color(buf, cell)
330+
change_color(buf, false, cell.flags, cell.bg, cell.bg_rgb, BG_IS_256, BG_IS_RGB)
331+
end
332+
333+
function change_attrs(buf, want, have)
334+
# If we are clearing any bits
335+
if (want & IsACS) != (have & IsACS)
336+
write(buf,(want & IsACS) == 0 ? "\e(B" : "\e(0")
337+
end
338+
if (have & ~want) != 0
339+
write(buf,CSI,"0m")
340+
have = 0
341+
end
342+
if (want & Bright) != 0
343+
write(buf,CSI,"1m")
344+
end
345+
end
346+
347+
function switch_cell_attrs(buf,wantc,attrs)
348+
(wantc.bg != attrs.bg || wantc.bg_rgb != attrs.bg_rgb) && change_bg_color(buf,wantc)
349+
(wantc.fg != attrs.fg || wantc.fg_rgb != attrs.fg_rgb) && change_fg_color(buf,wantc)
350+
(wantc.attrs != attrs.attrs) && change_attrs(buf,wantc.attrs,attrs.attrs)
351+
wantc
259352
end
260353

261354
# Dump the emulator contents as a plain-contents text file and a decorator file
262355
# Intended mostly for regression testing.
263356
const default_decorators = ['A':'z';'a':'z';'0':'9']
264357
function dump(contents::IO, decorator::IO, em::Emulator, lines = nothing, decorator_map = Dict{Cell,Char}(),
265-
available_decorators = copy(default_decorators))
358+
available_decorators = copy(default_decorators); render_content_decorators=false)
266359
first = true
360+
attrs = Cell()
267361
for line in (lines === nothing ? em.lines : em.lines[lines])
268362
if first
269363
first = false
@@ -273,6 +367,9 @@ function dump(contents::IO, decorator::IO, em::Emulator, lines = nothing, decora
273367
end
274368
for cell in line
275369
c = cell.content
370+
if render_content_decorators
371+
attrs = switch_cell_attrs(contents, cell, attrs)
372+
end
276373
if c < '\Uf0000'
277374
write(contents,c)
278375
else
@@ -287,6 +384,9 @@ function dump(contents::IO, decorator::IO, em::Emulator, lines = nothing, decora
287384
write(decorator, decorator_map[template])
288385
end
289386
end
387+
if render_content_decorators
388+
switch_cell_attrs(contents, Cell(), attrs)
389+
end
290390
end
291391

292392
function fill_space!(line, from, to)
@@ -306,7 +406,7 @@ function insert_cell!(em, pos, c::Cell)
306406
if pos > em.ViewPortSize.width
307407
# Fill the remainder of this line with spaces
308408
fill_space!(line, l+1, em.ViewPortSize.width)
309-
# Remember that this line was wrapper
409+
# Remember that this line was wrapped
310410
line.wrapped = true
311411
insert_line!(em)
312412
cmove_down(em, 1); cmove_col(em, 1)
@@ -433,9 +533,9 @@ function parseSGR!(em::Emulator, params)
433533
error("Incorrect SGR sequence")
434534
end
435535
elseif 90 <= f1 <= 97
436-
set_cur_cell(em,Cell(cell,fg = f1-90, attrs=cell.attrs | Bright))
536+
set_cur_cell(em,Cell(cell,fg = f1, attrs=cell.attrs))
437537
elseif 100 <= f1 <= 107
438-
set_cur_cell(em,Cell(cell,fg = f1-100, attrs=cell.attrs | Bright))
538+
set_cur_cell(em,Cell(cell,bg = f1, attrs=cell.attrs))
439539
else
440540
error("Unimplemented CSIm $f1")
441541
end

0 commit comments

Comments
 (0)