Skip to content

Commit 33edc09

Browse files
committed
feat: Make terminal backend ✨
1 parent 220f712 commit 33edc09

File tree

5 files changed

+80
-57
lines changed

5 files changed

+80
-57
lines changed

examples/dataframe.jl

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ function TUI.view(m::Model)
1313
m.table
1414
end
1515

16-
function TUI.update!(m::Model, evt::TUI.KeyEvent)
16+
function TUI.update!(m::Model, evt::TUI.ResizeEvent)
1717
@info "" evt
18+
TUI.recalculate_view(m.table)
19+
TUI.update!(TUI.terminal(m), evt)
20+
end
21+
22+
function TUI.update!(m::Model, evt::TUI.KeyEvent)
23+
# @info "" evt
1824
if TUI.keypress(evt) == "q"
1925
m.quit = true
2026
elseif TUI.keypress(evt) == "j"

src/TerminalUserInterfaces.jl

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ using InlineTest
99
using KiwiConstraintSolver
1010
using Tables
1111

12-
export Terminal
13-
1412
const TUI = TerminalUserInterfaces
1513

1614
include("logger.jl")

src/app.jl

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11

2+
"""
3+
```julia
4+
import TerminalUserInterface as TUI
5+
6+
struct TUIModel <: TUI.Model
7+
quit::Bool
8+
end
9+
```
10+
"""
211
abstract type Model end
312

413
terminal(::Model) = TERMINAL[]
5-
init!(m::Model, ::Terminal) = m.quit = false
14+
init!(m::Model, ::TerminalBackend) = m.quit = false
615
update!(m::Model, ::Crossterm.Event) = update!(m)
716
update!(::Model, ::Nothing) = nothing
817
update!(::Model) = @info "No update"

src/terminal.jl

+59-53
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import Base.print
22
import Dates
33

4+
abstract type TerminalBackend end
5+
46
"""
5-
Terminal
7+
CrosstermTerminal
68
"""
7-
struct Terminal
9+
struct CrosstermTerminal <: TerminalBackend
810
buffers::Vector{Buffer}
911
current::Ref{UInt8}
1012
previous::Ref{UInt8}
@@ -13,27 +15,26 @@ struct Terminal
1315
keyboard_buffer::Vector{Char}
1416
kind::String
1517
wait::Float64
16-
ispaused::Ref{Bool}
17-
isclosed::Ref{Bool}
18-
function Terminal()
18+
function CrosstermTerminal()
1919
(; w, h) = Crossterm.size()
2020
rect = Rect(1, 1, w, h)
2121
buffers = [Buffer(rect), Buffer(rect)]
2222
current = 1
2323
previous = 2
2424
cursor_hidden = false
25-
ispaused = Ref{Bool}(false)
26-
isclosed = Ref{Bool}(false)
27-
t = new(buffers, current, previous, cursor_hidden, rect, Char[], get(ENV, "TERM", ""), 1 / 1000, ispaused, isclosed)
25+
t = new(buffers, current, previous, cursor_hidden, rect, Char[], get(ENV, "TERM", ""), 1 / 1000)
2826
TERMINAL[] = t
2927
return t
3028
end
3129
end
3230

33-
function close(t::Terminal)
34-
t.isclosed[] = true
35-
t.ispaused[] = true
36-
end
31+
# TODO: come up with better way to expose terminal
32+
const TERMINAL = Ref{CrosstermTerminal}()
33+
34+
# TODO: make terminal function return any backend. Right now there's only one backend - `CrosstermTerminal`
35+
Terminal() = CrosstermTerminal()
36+
37+
function close(::CrosstermTerminal) end
3738

3839
"""
3940
Get event (nonblocking)
@@ -42,7 +43,7 @@ Get event (nonblocking)
4243
4344
- wait (seconds)
4445
"""
45-
function try_get_event(t::Terminal; wait = t.wait)
46+
function try_get_event(t::CrosstermTerminal; wait = t.wait)
4647
if Crossterm.poll(Dates.Nanosecond(round(wait * 1e9)))
4748
Crossterm.read()
4849
else
@@ -53,29 +54,32 @@ end
5354
"""
5455
Get event (blocking)
5556
"""
56-
function get_event(::Terminal)
57+
function get_event(::CrosstermTerminal)
5758
Crossterm.read()
5859
end
5960

60-
function update!(t::Terminal, evt) end
61-
function update!(t::Terminal, evt::Crossterm.Event{Crossterm.MouseEvent}) end
62-
function update!(t::Terminal, evt::Crossterm.Event{Crossterm.KeyEvent}) end
63-
function update!(t::Terminal, evt::Crossterm.Event{Crossterm.ResizeEvent})
61+
"""
62+
Flush terminal
63+
"""
64+
flush(::CrosstermTerminal) = Crossterm.flush()
65+
66+
function update!(t::CrosstermTerminal, evt) end
67+
function update!(t::CrosstermTerminal, evt::Crossterm.Event{Crossterm.MouseEvent}) end
68+
function update!(t::CrosstermTerminal, evt::Crossterm.Event{Crossterm.KeyEvent}) end
69+
function update!(t::CrosstermTerminal, evt::Crossterm.Event{Crossterm.ResizeEvent})
6470
(; w, h) = evt.data
6571
resize(t, w, h)
6672
clear_screen(t)
6773
move_cursor_home(t)
68-
Crossterm.flush()
74+
flush(t)
6975
end
7076

71-
const TERMINAL = Ref{Terminal}()
72-
7377
const END = 2 + 1
7478

7579
"""
7680
Draw terminal contents
7781
"""
78-
function draw(t::Terminal)
82+
function draw(t::CrosstermTerminal)
7983
pb = previous_buffer(t)
8084
cb = current_buffer(t)
8185
save_cursor(t)
@@ -104,122 +108,122 @@ end
104108
"""
105109
Move cursor
106110
"""
107-
move_cursor(t::Terminal, row, col) = Crossterm.to(; x = col - 1, y = row - 1)
111+
move_cursor(::CrosstermTerminal, row, col) = Crossterm.to(; x = col - 1, y = row - 1)
108112
"""
109113
Move cursor up
110114
"""
111-
move_cursor_up(t::Terminal, row = 1) = Crossterm.up(row)
115+
move_cursor_up(::CrosstermTerminal, row = 1) = Crossterm.up(row)
112116
"""
113117
Move cursor down
114118
"""
115-
move_cursor_down(t::Terminal, row = 1) = Crossterm.down(row)
119+
move_cursor_down(::CrosstermTerminal, row = 1) = Crossterm.down(row)
116120
"""
117121
Move cursor right
118122
"""
119-
move_cursor_right(t::Terminal, col = 1) = Crossterm.right(col)
123+
move_cursor_right(::CrosstermTerminal, col = 1) = Crossterm.right(col)
120124
"""
121125
Move cursor left
122126
"""
123-
move_cursor_left(t::Terminal, col = 1) = Crossterm.left(col)
127+
move_cursor_left(::CrosstermTerminal, col = 1) = Crossterm.left(col)
124128
"""
125129
Move cursor home
126130
"""
127-
move_cursor_home(t::Terminal) = Crossterm.to(; x = 0, y = 0)
131+
move_cursor_home(::CrosstermTerminal) = Crossterm.to(; x = 0, y = 0)
128132

129133
"""
130134
Clear screen
131135
"""
132-
clear_screen(t::Terminal) = Crossterm.clear()
136+
clear_screen(::CrosstermTerminal) = Crossterm.clear()
133137
"""
134138
Clear screen from cursor up onwards
135139
"""
136-
clear_screen_from_cursor_up(t::Terminal) = Crossterm.clear(Crossterm.ClearType.FROM_CURSOR_UP)
140+
clear_screen_from_cursor_up(::CrosstermTerminal) = Crossterm.clear(Crossterm.ClearType.FROM_CURSOR_UP)
137141
"""
138142
Clear screen from cursor down onwards
139143
"""
140-
clear_screen_from_cursor_down(t::Terminal) = Crossterm.clear(Crossterm.ClearType.FROM_CURSOR_DOWN)
144+
clear_screen_from_cursor_down(::CrosstermTerminal) = Crossterm.clear(Crossterm.ClearType.FROM_CURSOR_DOWN)
141145

142146
"""
143147
Clear line
144148
"""
145-
clear_line(t::Terminal) = Crossterm.clear(Crossterm.ClearType.CURRENT_LINE)
149+
clear_line(::CrosstermTerminal) = Crossterm.clear(Crossterm.ClearType.CURRENT_LINE)
146150
"""
147151
Clear line from cursor right onwards
148152
"""
149-
clear_line_from_cursor_right(t::Terminal) = Crossterm.clear(Crossterm.ClearType.UNTIL_NEW_LINE)
153+
clear_line_from_cursor_right(::CrosstermTerminal) = Crossterm.clear(Crossterm.ClearType.UNTIL_NEW_LINE)
150154
"""
151155
Clear line from cursor left onwards
152156
"""
153-
clear_line_from_cursor_left(t::Terminal) = Crossterm.clear(Crossterm.ClearType.CURRENT_LINE)
157+
clear_line_from_cursor_left(::CrosstermTerminal) = Crossterm.clear(Crossterm.ClearType.CURRENT_LINE)
154158

155159
"""
156160
Hide cursor
157161
"""
158-
hide_cursor(t::Terminal) = Crossterm.hide()
162+
hide_cursor(::CrosstermTerminal) = Crossterm.hide()
159163
"""
160164
Show cursor
161165
"""
162-
show_cursor(t::Terminal) = Crossterm.show()
166+
show_cursor(::CrosstermTerminal) = Crossterm.show()
163167

164168
"""
165169
Save cursor
166170
"""
167-
save_cursor(t::Terminal) = Crossterm.save()
171+
save_cursor(::CrosstermTerminal) = Crossterm.save()
168172
"""
169173
Restore cursor
170174
"""
171-
restore_cursor(t::Terminal) = Crossterm.restore()
175+
restore_cursor(::CrosstermTerminal) = Crossterm.restore()
172176

173177
"""
174178
Change cursor to default
175179
"""
176-
change_cursor_to_default(t::Terminal) = Crossterm.style(Crossterm.Style.DEFAULT_USER_SHAPE)
180+
change_cursor_to_default(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.DEFAULT_USER_SHAPE)
177181

178182
"""
179183
Change cursor to blinking block
180184
"""
181-
change_cursor_to_blinking_block(t::Terminal) = Crossterm.style(Crossterm.Style.BLINKING_BLOCK)
185+
change_cursor_to_blinking_block(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.BLINKING_BLOCK)
182186

183187
"""
184188
Change cursor to steady block
185189
"""
186-
change_cursor_to_steady_block(t::Terminal) = Crossterm.style(Crossterm.Style.STEADY_BLOCK)
190+
change_cursor_to_steady_block(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.STEADY_BLOCK)
187191
"""
188192
Change cursor to blinking underline
189193
"""
190-
change_cursor_to_blinking_underline(t::Terminal) = Crossterm.style(Crossterm.Style.BLINKING_UNDER_SCORE)
194+
change_cursor_to_blinking_underline(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.BLINKING_UNDER_SCORE)
191195
"""
192196
Change cursor to steady underline
193197
"""
194-
change_cursor_to_steady_underline(t::Terminal) = Crossterm.style(Crossterm.Style.STEADY_UNDER_SCORE)
198+
change_cursor_to_steady_underline(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.STEADY_UNDER_SCORE)
195199
"""
196200
Change cursor to blinking ibeam
197201
"""
198-
change_cursor_to_blinking_ibeam(t::Terminal) = Crossterm.style(Crossterm.Style.BLINKING_BAR)
202+
change_cursor_to_blinking_ibeam(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.BLINKING_BAR)
199203
"""
200204
Change cursor to steady ibeam
201205
"""
202-
change_cursor_to_steady_ibeam(t::Terminal) = Crossterm.style(Crossterm.Style.STEADY_BAR)
206+
change_cursor_to_steady_ibeam(::CrosstermTerminal) = Crossterm.style(Crossterm.Style.STEADY_BAR)
203207

204208
"""
205209
Alternate screen TUI mode
206210
"""
207-
tui_mode(t::Terminal) = Crossterm.alternate_screen(true)
211+
tui_mode(::CrosstermTerminal) = Crossterm.alternate_screen(true)
208212
"""
209213
Default mode
210214
"""
211-
default_mode(t::Terminal) = Crossterm.alternate_screen(false)
215+
default_mode(::CrosstermTerminal) = Crossterm.alternate_screen(false)
212216

213217
"""
214218
Get current buffer
215219
"""
216-
current_buffer(t::Terminal)::Buffer = t.buffers[t.current[]]
220+
current_buffer(t::CrosstermTerminal)::Buffer = t.buffers[t.current[]]
217221
current_buffer()::Buffer = current_buffer(TERMINAL[])
218222

219223
"""
220224
Get previous buffer
221225
"""
222-
previous_buffer(t::Terminal)::Buffer = t.buffers[t.previous[]]
226+
previous_buffer(t::CrosstermTerminal)::Buffer = t.buffers[t.previous[]]
223227
previous_buffer()::Buffer = previous_buffer(TERMINAL[])
224228

225229
put(c::SubString{String}) = Crossterm.print(string(c))
@@ -231,13 +235,13 @@ function put(cell::Cell)
231235
put(string(inv(cell.style)))
232236
end
233237

234-
render(t::Terminal, widget) = render(t, widget, area(t))
235-
render(t::Terminal, widget, r::Rect) = render(widget, r, current_buffer(t))
238+
render(t::CrosstermTerminal, widget) = render(t, widget, area(t))
239+
render(t::CrosstermTerminal, widget, r::Rect) = render(widget, r, current_buffer(t))
236240

237241
"""
238242
Resize terminal
239243
"""
240-
function resize(t::Terminal, w::Int, h::Int)
244+
function resize(t::CrosstermTerminal, w::Int, h::Int)
241245
rect = Rect(1, 1, w, h)
242246
t.terminal_size[] = rect
243247
t.buffers[t.current[]] = Buffer(rect)
@@ -247,7 +251,9 @@ end
247251
"""
248252
Area of terminal as Rect (width, height)
249253
"""
250-
area(t::Terminal) = t.terminal_size[]
254+
function area(t::CrosstermTerminal)
255+
t.terminal_size[]
256+
end
251257

252258
function tui(f::Function; flags...)
253259
r = nothing

src/widgets/table.jl

+4
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ function previous(table::Table)
8080
table.state.selected = selection
8181
end
8282

83+
function recalculate_view(table::Table)
84+
table.column_widths = []
85+
end
86+
8387
function get_columns_widths(table::Table, max_width::Int)
8488
constraints = Constraint[]
8589
for constraint in table.widths

0 commit comments

Comments
 (0)