reflex-terminal 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.doc/ext/reflex-terminal/renderer.cpp +33 -1
- data/.doc/ext/reflex-terminal/terminal.cpp +26 -10
- data/.github/workflows/release-gem.yml +4 -49
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +3 -37
- data/ChangeLog.md +23 -0
- data/Gemfile.lock +26 -0
- data/README.md +2 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/terminal.rb +24 -13
- data/ext/reflex-terminal/renderer.cpp +37 -1
- data/ext/reflex-terminal/terminal.cpp +27 -10
- data/include/reflex/terminal.h +30 -5
- data/include/reflex-terminal/renderer.h +8 -0
- data/lib/reflex/terminal.rb +3 -2
- data/lib/reflex/terminal_view.rb +76 -10
- data/reflex-terminal.gemspec +5 -5
- data/src/pty.cpp +33 -4
- data/src/renderer.cpp +350 -80
- data/src/terminal.cpp +237 -105
- data/src/win32/pty.cpp +2 -2
- data/test/test_renderer.rb +122 -4
- data/test/test_terminal.rb +163 -8
- data/test/test_terminal_view.rb +1 -1
- metadata +12 -12
- data/.github/workflows/utils.rb +0 -127
data/include/reflex/terminal.h
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
#include <reflex/defs.h>
|
|
14
14
|
#include <reflex/event.h>
|
|
15
15
|
|
|
16
|
+
#ifdef COLOR_BACKGROUND
|
|
17
|
+
#undef COLOR_BACKGROUND // by Win32's winuser.h
|
|
18
|
+
#endif
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
namespace Reflex
|
|
18
22
|
{
|
|
@@ -53,6 +57,23 @@ namespace Reflex
|
|
|
53
57
|
|
|
54
58
|
};// Attribute
|
|
55
59
|
|
|
60
|
+
enum Underline
|
|
61
|
+
{
|
|
62
|
+
|
|
63
|
+
UNDERLINE_NONE = 0,
|
|
64
|
+
|
|
65
|
+
UNDERLINE_SINGLE,
|
|
66
|
+
|
|
67
|
+
UNDERLINE_DOUBLE,
|
|
68
|
+
|
|
69
|
+
UNDERLINE_CURLY,
|
|
70
|
+
|
|
71
|
+
UNDERLINE_DOTTED,
|
|
72
|
+
|
|
73
|
+
UNDERLINE_DASHED
|
|
74
|
+
|
|
75
|
+
};// Underline
|
|
76
|
+
|
|
56
77
|
enum {COLOR_NONE = -1};
|
|
57
78
|
|
|
58
79
|
int x, width;// in cells
|
|
@@ -61,7 +82,7 @@ namespace Reflex
|
|
|
61
82
|
|
|
62
83
|
uint cell_offset, cell_size;
|
|
63
84
|
|
|
64
|
-
int fg, bg;// 0xRRGGBB, or COLOR_NONE
|
|
85
|
+
int fg, bg, ul;// 0xRRGGBB, or COLOR_NONE
|
|
65
86
|
|
|
66
87
|
uint attribs;
|
|
67
88
|
|
|
@@ -89,11 +110,13 @@ namespace Reflex
|
|
|
89
110
|
|
|
90
111
|
};// Style
|
|
91
112
|
|
|
92
|
-
int x, y;
|
|
113
|
+
int x = 0, y = 0;
|
|
114
|
+
|
|
115
|
+
Style style = BLOCK;
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
bool visible = false;
|
|
95
118
|
|
|
96
|
-
bool
|
|
119
|
+
bool operator == (const Cursor&) const = default;
|
|
97
120
|
|
|
98
121
|
};// Cursor
|
|
99
122
|
|
|
@@ -141,7 +164,7 @@ namespace Reflex
|
|
|
141
164
|
|
|
142
165
|
void resize (
|
|
143
166
|
int columns, int rows,
|
|
144
|
-
|
|
167
|
+
coord cell_width, coord cell_height,
|
|
145
168
|
int screen_width, int screen_height);
|
|
146
169
|
|
|
147
170
|
void feed (const char* bytes, size_t size);
|
|
@@ -164,6 +187,8 @@ namespace Reflex
|
|
|
164
187
|
|
|
165
188
|
bool is_alive () const;
|
|
166
189
|
|
|
190
|
+
bool is_blinking () const;
|
|
191
|
+
|
|
167
192
|
bool is_mouse_tracking () const;
|
|
168
193
|
|
|
169
194
|
void select (int x1, int y1, int x2, int y2);
|
|
@@ -40,6 +40,14 @@ namespace ReflexTerminal
|
|
|
40
40
|
|
|
41
41
|
size_t glyph_count () const;
|
|
42
42
|
|
|
43
|
+
void set_blink_visible (bool visible);
|
|
44
|
+
|
|
45
|
+
bool blink_visible () const;
|
|
46
|
+
|
|
47
|
+
void set_background_alpha (float alpha);
|
|
48
|
+
|
|
49
|
+
float background_alpha () const;
|
|
50
|
+
|
|
43
51
|
void draw (
|
|
44
52
|
Painter* painter, const Reflex::Terminal& terminal,
|
|
45
53
|
const Bounds& bounds);
|
data/lib/reflex/terminal.rb
CHANGED
|
@@ -48,9 +48,10 @@ module Reflex
|
|
|
48
48
|
|
|
49
49
|
# Yields each style span of the visible screen.
|
|
50
50
|
#
|
|
51
|
-
# @yield [x, y, width, text, fg, bg, flags] a run of cells sharing
|
|
51
|
+
# @yield [x, y, width, text, fg, bg, ul, flags] a run of cells sharing
|
|
52
52
|
# the same style; x, y and width are in cells, and fg/bg are
|
|
53
|
-
# 0xRRGGBB or nil for the terminal's default color
|
|
53
|
+
# 0xRRGGBB or nil for the terminal's default color. ul is the
|
|
54
|
+
# underline color (SGR 58), 0xRRGGBB or nil to draw with fg
|
|
54
55
|
#
|
|
55
56
|
# @return [Enumerator] when no block is given
|
|
56
57
|
#
|
data/lib/reflex/terminal_view.rb
CHANGED
|
@@ -11,6 +11,8 @@ module Reflex
|
|
|
11
11
|
|
|
12
12
|
class TerminalView < View
|
|
13
13
|
|
|
14
|
+
include HasTextPreedit
|
|
15
|
+
|
|
14
16
|
DEFAULT_FONT_SIZE = 14
|
|
15
17
|
|
|
16
18
|
DEFAULT_FONT_NAME =
|
|
@@ -21,6 +23,8 @@ module Reflex
|
|
|
21
23
|
|
|
22
24
|
CURSOR_BLINK_INTERVAL = 0.5
|
|
23
25
|
|
|
26
|
+
TEXT_BLINK_INTERVAL = 0.5
|
|
27
|
+
|
|
24
28
|
def initialize(
|
|
25
29
|
*args,
|
|
26
30
|
terminal: nil, # attach this instead of spawning one
|
|
@@ -30,13 +34,11 @@ module Reflex
|
|
|
30
34
|
font_size: DEFAULT_FONT_SIZE,
|
|
31
35
|
**kwargs, &block)
|
|
32
36
|
|
|
37
|
+
@terminal, @command, @envs, @font_size = terminal, command, envs, font_size
|
|
38
|
+
@scroll_rows, @cursor_blink, @prev_bells = 0, true, (terminal&.bells || 0)
|
|
39
|
+
@renderer = ReflexTerminal::Renderer.new
|
|
40
|
+
self.font = font || DEFAULT_FONT_NAME
|
|
33
41
|
super(*args, **kwargs, &block)
|
|
34
|
-
@terminal, @command, @envs = terminal, command, envs
|
|
35
|
-
@renderer = ReflexTerminal::Renderer.new
|
|
36
|
-
@cursor_blink = true
|
|
37
|
-
@prev_bells = terminal&.bells || 0
|
|
38
|
-
@font_size = font_size
|
|
39
|
-
self.font = font || DEFAULT_FONT_NAME
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
attr_reader :terminal
|
|
@@ -64,17 +66,26 @@ module Reflex
|
|
|
64
66
|
font.size
|
|
65
67
|
end
|
|
66
68
|
|
|
69
|
+
def background_alpha=(alpha)
|
|
70
|
+
@renderer.background_alpha = alpha
|
|
71
|
+
redraw
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def background_alpha()
|
|
75
|
+
@renderer.background_alpha
|
|
76
|
+
end
|
|
77
|
+
|
|
67
78
|
def on_attach(e)
|
|
68
79
|
unless @terminal
|
|
69
80
|
@terminal = Terminal.new
|
|
70
81
|
@terminal.spawn(@envs, *[@command].compact)
|
|
71
82
|
end
|
|
72
83
|
resize_terminal
|
|
73
|
-
focus
|
|
74
84
|
end
|
|
75
85
|
|
|
76
86
|
def on_detach(e)
|
|
77
87
|
stop_cursor_blink
|
|
88
|
+
stop_text_blink
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def on_activate(e)
|
|
@@ -95,6 +106,7 @@ module Reflex
|
|
|
95
106
|
@renderer.bake_glyphs t
|
|
96
107
|
redraw
|
|
97
108
|
end
|
|
109
|
+
update_text_blink t
|
|
98
110
|
if t.bells > @prev_bells
|
|
99
111
|
bells, @prev_bells = t.bells - @prev_bells, t.bells
|
|
100
112
|
on_bell BellEvent.new(bells)
|
|
@@ -105,7 +117,8 @@ module Reflex
|
|
|
105
117
|
t = @terminal || return
|
|
106
118
|
|
|
107
119
|
@renderer.draw e.painter, t, e.bounds
|
|
108
|
-
draw_cursor
|
|
120
|
+
draw_cursor e.painter, t
|
|
121
|
+
draw_preedit e.painter, t
|
|
109
122
|
end
|
|
110
123
|
|
|
111
124
|
def on_key_down(e)
|
|
@@ -125,6 +138,13 @@ module Reflex
|
|
|
125
138
|
@terminal&.write_key e
|
|
126
139
|
end
|
|
127
140
|
|
|
141
|
+
# tells the input method where to put the candidate window.
|
|
142
|
+
def text_input_bounds()
|
|
143
|
+
x, y, = @terminal&.cursor || [0, 0]
|
|
144
|
+
cw, ch = @renderer.cell_width, @renderer.cell_height
|
|
145
|
+
[x * cw, y * ch, preedit_width, ch]
|
|
146
|
+
end
|
|
147
|
+
|
|
128
148
|
def on_pointer_down(e)
|
|
129
149
|
focus
|
|
130
150
|
t = @terminal || return
|
|
@@ -149,6 +169,8 @@ module Reflex
|
|
|
149
169
|
@selecting = false
|
|
150
170
|
end
|
|
151
171
|
|
|
172
|
+
alias on_pointer_cancel on_pointer_up
|
|
173
|
+
|
|
152
174
|
def on_pointer_move(e)
|
|
153
175
|
t = @terminal || return
|
|
154
176
|
return t.write_pointer(e) unless @selecting
|
|
@@ -169,8 +191,11 @@ module Reflex
|
|
|
169
191
|
if t.mouse_tracking?
|
|
170
192
|
t.write_wheel e
|
|
171
193
|
else
|
|
172
|
-
|
|
194
|
+
@scroll_rows += e.dy / @renderer.cell_height
|
|
195
|
+
rows = @scroll_rows.truncate
|
|
173
196
|
return if rows == 0
|
|
197
|
+
|
|
198
|
+
@scroll_rows -= rows
|
|
174
199
|
t.scroll_by rows
|
|
175
200
|
redraw
|
|
176
201
|
end
|
|
@@ -216,6 +241,26 @@ module Reflex
|
|
|
216
241
|
@cursor_blinker = nil
|
|
217
242
|
end
|
|
218
243
|
|
|
244
|
+
def update_text_blink(terminal)
|
|
245
|
+
if terminal.blinking?
|
|
246
|
+
@text_blinker ||= interval TEXT_BLINK_INTERVAL do
|
|
247
|
+
@renderer.blink_visible = !@renderer.blink_visible?
|
|
248
|
+
redraw
|
|
249
|
+
end
|
|
250
|
+
elsif @text_blinker
|
|
251
|
+
stop_text_blink
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def stop_text_blink()
|
|
256
|
+
@text_blinker&.stop
|
|
257
|
+
@text_blinker = nil
|
|
258
|
+
unless @renderer.blink_visible?
|
|
259
|
+
@renderer.blink_visible = true
|
|
260
|
+
redraw
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
219
264
|
def draw_cursor(painter, terminal)
|
|
220
265
|
x, y, style, visible = terminal.cursor
|
|
221
266
|
return unless visible
|
|
@@ -244,6 +289,27 @@ module Reflex
|
|
|
244
289
|
end
|
|
245
290
|
end
|
|
246
291
|
|
|
292
|
+
def preedit_width()
|
|
293
|
+
preedit? ? font.width(preedit) : @renderer.cell_width
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def draw_preedit(painter, terminal)
|
|
297
|
+
return unless preedit?
|
|
298
|
+
|
|
299
|
+
x, y, = terminal.cursor
|
|
300
|
+
cw, ch = @renderer.cell_width, @renderer.cell_height
|
|
301
|
+
x, y = x * cw, y * ch
|
|
302
|
+
fore = terminal.foreground_color || Color.new(1, 1, 1)
|
|
303
|
+
back = terminal.background_color || Color.new(0, 0, 0)
|
|
304
|
+
|
|
305
|
+
painter.push fill: back, stroke: nil, font: font do |p|
|
|
306
|
+
p.rect x, y, preedit_width, ch
|
|
307
|
+
|
|
308
|
+
p.fill fore
|
|
309
|
+
super p, x, y, height: ch
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
247
313
|
def to_cell(x, y)
|
|
248
314
|
# Clamped to the screen so that a drag leaving the view keeps selecting
|
|
249
315
|
# up to the edge, rather than asking for a cell the terminal will refuse.
|
|
@@ -258,7 +324,7 @@ module Reflex
|
|
|
258
324
|
@terminal.resize(
|
|
259
325
|
(width / cw).floor.clamp(1..),
|
|
260
326
|
(height / ch).floor.clamp(1..),
|
|
261
|
-
cell_width: cw
|
|
327
|
+
cell_width: cw, cell_height: ch,
|
|
262
328
|
screen_width: width.to_i, screen_height: height.to_i)
|
|
263
329
|
end
|
|
264
330
|
|
data/reflex-terminal.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
|
|
|
11
11
|
|
|
12
12
|
ext = ReflexTerminal::Extension
|
|
13
13
|
name = ext.name true
|
|
14
|
-
rdocs = glob.call
|
|
14
|
+
rdocs = glob.call(*%w[README .doc/ext/**/*.cpp])
|
|
15
15
|
|
|
16
16
|
s.name = name
|
|
17
17
|
s.version = ext.version
|
|
@@ -25,10 +25,10 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
s.platform = Gem::Platform::RUBY
|
|
26
26
|
s.required_ruby_version = '>= 3.0.0'
|
|
27
27
|
|
|
28
|
-
s.add_dependency 'xot', '~> 0.
|
|
29
|
-
s.add_dependency 'rucy', '~> 0.
|
|
30
|
-
s.add_dependency 'rays', '~> 0.
|
|
31
|
-
s.add_dependency 'reflexion', '~> 0.
|
|
28
|
+
s.add_dependency 'xot', '~> 0.5.0'
|
|
29
|
+
s.add_dependency 'rucy', '~> 0.5.0'
|
|
30
|
+
s.add_dependency 'rays', '~> 0.5.0'
|
|
31
|
+
s.add_dependency 'reflexion', '~> 0.7.0'
|
|
32
32
|
|
|
33
33
|
s.files = `git ls-files`.split $/
|
|
34
34
|
s.executables = s.files.grep(%r{^bin/}) {|f| File.basename f}
|
data/src/pty.cpp
CHANGED
|
@@ -103,7 +103,7 @@ namespace Reflex
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// exec the real path; argv[0] may be the login shell name
|
|
106
|
-
execvp(args[0].c_str(), (char* const*)
|
|
106
|
+
execvp(args[0].c_str(), (char* const*) argv.data());
|
|
107
107
|
_exit(127);// exec failed
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -197,10 +197,32 @@ namespace Reflex
|
|
|
197
197
|
::close(self->fd);
|
|
198
198
|
self->fd = -1;
|
|
199
199
|
}
|
|
200
|
+
|
|
200
201
|
if (self->pid >= 0)
|
|
201
202
|
{
|
|
202
|
-
|
|
203
|
+
pid_t pid = self->pid;
|
|
203
204
|
self->pid = -1;
|
|
205
|
+
|
|
206
|
+
// closing the master is not a hangup: the kernel revokes the
|
|
207
|
+
// terminal when the process that owned it goes away, and none has.
|
|
208
|
+
// The group, not the child, so that its own children leave too
|
|
209
|
+
::kill(-pid, SIGHUP);
|
|
210
|
+
|
|
211
|
+
enum {HANGUP_WAIT_MSEC = 100};
|
|
212
|
+
for (int i = 0; i < HANGUP_WAIT_MSEC; ++i)
|
|
213
|
+
{
|
|
214
|
+
// zero is the only answer that means one is still to come:
|
|
215
|
+
// is_child_alive() reaps, and a reaped child is gone already
|
|
216
|
+
if (waitpid(pid, NULL, WNOHANG) != 0) return;
|
|
217
|
+
usleep(1000);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// polled rather than waited on, because a signal arriving
|
|
221
|
+
// meanwhile would cut a blocking wait short and leave the child
|
|
222
|
+
// unreaped. Nothing refuses SIGKILL, so this is not a long wait
|
|
223
|
+
::kill(-pid, SIGKILL);
|
|
224
|
+
while (waitpid(pid, NULL, WNOHANG) == 0)
|
|
225
|
+
usleep(1000);
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
228
|
|
|
@@ -213,9 +235,16 @@ namespace Reflex
|
|
|
213
235
|
bool
|
|
214
236
|
PTY::is_child_alive () const
|
|
215
237
|
{
|
|
216
|
-
if (self->pid < 0)
|
|
238
|
+
if (self->pid < 0)
|
|
239
|
+
return false;
|
|
240
|
+
|
|
241
|
+
if (waitpid(self->pid, NULL, WNOHANG) == 0)
|
|
242
|
+
return true;
|
|
217
243
|
|
|
218
|
-
|
|
244
|
+
// waitpid reaps as it answers, and a pid the kernel is free to hand
|
|
245
|
+
// out again is not one for close() to signal later
|
|
246
|
+
self->pid = -1;
|
|
247
|
+
return false;
|
|
219
248
|
}
|
|
220
249
|
|
|
221
250
|
PTY::operator bool () const
|