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.
@@ -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
- Style style;
117
+ bool visible = false;
95
118
 
96
- bool visible;
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
- int cell_width, int cell_height,
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);
@@ -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
  #
@@ -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 e.painter, t
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
- rows = (e.dy / @renderer.cell_height).round
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.round, cell_height: ch,
327
+ cell_width: cw, cell_height: ch,
262
328
  screen_width: width.to_i, screen_height: height.to_i)
263
329
  end
264
330
 
@@ -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 *%w[README .doc/ext/**/*.cpp]
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.4.0'
29
- s.add_dependency 'rucy', '~> 0.4.0'
30
- s.add_dependency 'rays', '~> 0.4.0'
31
- s.add_dependency 'reflexion', '~> 0.6.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*) &argv[0]);
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
- waitpid(self->pid, NULL, WNOHANG);// reap if already exited
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) return false;
238
+ if (self->pid < 0)
239
+ return false;
240
+
241
+ if (waitpid(self->pid, NULL, WNOHANG) == 0)
242
+ return true;
217
243
 
218
- return waitpid(self->pid, NULL, WNOHANG) == 0;
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