tarazed 0.2.5 → 0.2.7
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/CHANGELOG.md +8 -0
- data/lib/tarazed/grid.rb +44 -1
- data/lib/tarazed/scrollback.rb +19 -0
- data/lib/tarazed/version.rb +1 -1
- data/lib/tarazed/vt.rb +12 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edb5f9868496d45a99573a1ae693aca013d7d40240af9f87fdaa20d23e390f24
|
|
4
|
+
data.tar.gz: 789f6f2f36a48caa960b11d304a5e1188e14bfeb09a9dd61d4199a61877e030c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82bc214aeee5727b48b0f69732fb0313aa2d95080ce0a14e2f4a6cf32507aade915af1ad7e3c15055419322c3372cede1a52c1d9d15379140ca9344fbed37da5
|
|
7
|
+
data.tar.gz: c1157de1a6ce563eb663160342044f110360221e90dcb4de7644d277772798e593a3bad191b4d2ae03fd6fd7b0aefcc4fe364b45bb46b0ea2fd1f27d7eb27808
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.7 - 2026-09-21
|
|
6
|
+
|
|
7
|
+
- Fix trusted RubyGems publishing for the terminal core.
|
|
8
|
+
|
|
9
|
+
## 0.2.6 - 2026-09-21
|
|
10
|
+
|
|
11
|
+
- Add terminal damage tracking, synchronized-output state, focus notifications, and scrollback search.
|
|
12
|
+
|
|
5
13
|
## 0.2.5 - 2026-09-19
|
|
6
14
|
|
|
7
15
|
- Keep open Windows ConPTY reads non-EOF while waiting for output.
|
data/lib/tarazed/grid.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
3
4
|
require "unicode/display_width"
|
|
4
5
|
|
|
5
6
|
module Tarazed
|
|
6
7
|
class Grid
|
|
7
|
-
attr_reader :columns, :rows, :cells, :scrollback, :cursor_x, :cursor_y, :scroll_top, :scroll_bottom
|
|
8
|
+
attr_reader :columns, :rows, :cells, :scrollback, :cursor_x, :cursor_y, :scroll_top, :scroll_bottom,
|
|
9
|
+
:synchronized
|
|
8
10
|
attr_accessor :foreground, :background, :attributes, :hyperlink, :autowrap, :insert_mode, :origin_mode,
|
|
9
11
|
:cursor_visible
|
|
10
12
|
|
|
@@ -25,10 +27,30 @@ module Tarazed
|
|
|
25
27
|
@alternate = nil
|
|
26
28
|
@scroll_top, @scroll_bottom = 0, rows - 1
|
|
27
29
|
@cells = Array.new(rows) { blank_row }
|
|
30
|
+
@damage_rows = Set.new
|
|
31
|
+
@synchronized = false
|
|
32
|
+
mark_all_damage
|
|
28
33
|
@tabs = (8...columns).step(8).to_a
|
|
29
34
|
save_cursor
|
|
30
35
|
end
|
|
31
36
|
|
|
37
|
+
# Return the inclusive range of rows changed since the last clear.
|
|
38
|
+
# A renderer can rebuild only this range and then call #clear_damage.
|
|
39
|
+
def damage
|
|
40
|
+
return if @damage_rows.empty?
|
|
41
|
+
|
|
42
|
+
@damage_rows.min..@damage_rows.max
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def damage_rows = @damage_rows.to_a.sort.freeze
|
|
46
|
+
def clear_damage = (@damage_rows.clear; self)
|
|
47
|
+
def mark_damage(row) = (@damage_rows.add(row) if row.between?(0, rows - 1); self)
|
|
48
|
+
def mark_all_damage = (@damage_rows.merge(0...rows); self)
|
|
49
|
+
def synchronized? = @synchronized
|
|
50
|
+
def synchronized=(value)
|
|
51
|
+
@synchronized = !!value
|
|
52
|
+
end
|
|
53
|
+
|
|
32
54
|
def [](row, column = nil) = column ? cells[row]&.[](column) : cells[row]
|
|
33
55
|
def alternate? = !@alternate.nil?
|
|
34
56
|
def lines = cells.map { |row| row.map(&:text).join.rstrip }
|
|
@@ -37,6 +59,7 @@ module Tarazed
|
|
|
37
59
|
def wrap_pending? = @wrap_pending
|
|
38
60
|
|
|
39
61
|
def put(char)
|
|
62
|
+
mark_damage(cursor_y)
|
|
40
63
|
width = self.class.width(char)
|
|
41
64
|
previous_x = @wrap_pending ? cursor_x : cursor_x - 1
|
|
42
65
|
if previous_x >= 0 && !char.ascii_only?
|
|
@@ -113,6 +136,7 @@ module Tarazed
|
|
|
113
136
|
@cursor_x = remainder.zero? ? columns - 1 : remainder
|
|
114
137
|
@cursor_y = rows - 1
|
|
115
138
|
@wrap_pending = remainder.zero?
|
|
139
|
+
mark_all_damage
|
|
116
140
|
else
|
|
117
141
|
text.each_byte { |byte| put(byte.chr) }
|
|
118
142
|
end
|
|
@@ -125,6 +149,7 @@ module Tarazed
|
|
|
125
149
|
end
|
|
126
150
|
|
|
127
151
|
def move(x: cursor_x, y: cursor_y, relative: false)
|
|
152
|
+
previous_y = cursor_y
|
|
128
153
|
if relative
|
|
129
154
|
x += cursor_x
|
|
130
155
|
y += cursor_y
|
|
@@ -133,6 +158,7 @@ module Tarazed
|
|
|
133
158
|
@cursor_x = x.clamp(0, columns - 1)
|
|
134
159
|
@cursor_y = y.clamp(top, bottom)
|
|
135
160
|
@wrap_pending = false
|
|
161
|
+
mark_damage(previous_y).mark_damage(@cursor_y)
|
|
136
162
|
end
|
|
137
163
|
|
|
138
164
|
def position(row, column)
|
|
@@ -183,6 +209,7 @@ module Tarazed
|
|
|
183
209
|
scrollback.push(removed) if scroll_top.zero? && scroll_bottom == rows - 1 && !alternate?
|
|
184
210
|
cells.insert(scroll_bottom, blank_row)
|
|
185
211
|
end
|
|
212
|
+
mark_all_damage
|
|
186
213
|
end
|
|
187
214
|
|
|
188
215
|
def scroll_down(count = 1)
|
|
@@ -190,6 +217,7 @@ module Tarazed
|
|
|
190
217
|
cells.delete_at(scroll_bottom)
|
|
191
218
|
cells.insert(scroll_top, blank_row)
|
|
192
219
|
end
|
|
220
|
+
mark_all_damage
|
|
193
221
|
end
|
|
194
222
|
|
|
195
223
|
def erase_display(mode = 0)
|
|
@@ -203,6 +231,7 @@ module Tarazed
|
|
|
203
231
|
when 2 then @cells = Array.new(rows) { blank_row }
|
|
204
232
|
when 3 then scrollback.clear
|
|
205
233
|
end
|
|
234
|
+
mark_all_damage
|
|
206
235
|
end
|
|
207
236
|
|
|
208
237
|
def erase_line(mode = 0)
|
|
@@ -216,6 +245,7 @@ module Tarazed
|
|
|
216
245
|
clear_wide(cursor_y, column)
|
|
217
246
|
cells[cursor_y][column] = blank
|
|
218
247
|
end
|
|
248
|
+
mark_damage(cursor_y)
|
|
219
249
|
end
|
|
220
250
|
|
|
221
251
|
def erase_characters(count = 1)
|
|
@@ -223,6 +253,7 @@ module Tarazed
|
|
|
223
253
|
clear_wide(cursor_y, column)
|
|
224
254
|
cells[cursor_y][column] = blank
|
|
225
255
|
end
|
|
256
|
+
mark_damage(cursor_y)
|
|
226
257
|
end
|
|
227
258
|
|
|
228
259
|
def insert_characters(count = 1)
|
|
@@ -230,6 +261,7 @@ module Tarazed
|
|
|
230
261
|
cells[cursor_y].insert(cursor_x, *Array.new([count, columns - cursor_x].min) { blank })
|
|
231
262
|
cells[cursor_y] = cells[cursor_y].first(columns)
|
|
232
263
|
normalize_row(cursor_y)
|
|
264
|
+
mark_damage(cursor_y)
|
|
233
265
|
end
|
|
234
266
|
|
|
235
267
|
def delete_characters(count = 1)
|
|
@@ -238,6 +270,7 @@ module Tarazed
|
|
|
238
270
|
cells[cursor_y].slice!(cursor_x, count)
|
|
239
271
|
cells[cursor_y].concat(Array.new(count) { blank })
|
|
240
272
|
normalize_row(cursor_y)
|
|
273
|
+
mark_damage(cursor_y)
|
|
241
274
|
end
|
|
242
275
|
|
|
243
276
|
def insert_lines(count = 1)
|
|
@@ -247,6 +280,7 @@ module Tarazed
|
|
|
247
280
|
cells.delete_at(scroll_bottom)
|
|
248
281
|
cells.insert(cursor_y, blank_row)
|
|
249
282
|
end
|
|
283
|
+
mark_all_damage
|
|
250
284
|
end
|
|
251
285
|
|
|
252
286
|
def delete_lines(count = 1)
|
|
@@ -256,6 +290,7 @@ module Tarazed
|
|
|
256
290
|
cells.delete_at(cursor_y)
|
|
257
291
|
cells.insert(scroll_bottom, blank_row)
|
|
258
292
|
end
|
|
293
|
+
mark_all_damage
|
|
259
294
|
end
|
|
260
295
|
|
|
261
296
|
def save_cursor
|
|
@@ -278,10 +313,12 @@ module Tarazed
|
|
|
278
313
|
@cursor_x = @cursor_y = 0
|
|
279
314
|
@scroll_top, @scroll_bottom = 0, rows - 1
|
|
280
315
|
@wrap_pending = false
|
|
316
|
+
mark_all_damage
|
|
281
317
|
elsif !enable && alternate?
|
|
282
318
|
@cells, @alternate = @alternate, nil
|
|
283
319
|
@scroll_top, @scroll_bottom = 0, rows - 1
|
|
284
320
|
restore_cursor if save
|
|
321
|
+
mark_all_damage
|
|
285
322
|
end
|
|
286
323
|
end
|
|
287
324
|
|
|
@@ -307,6 +344,7 @@ module Tarazed
|
|
|
307
344
|
@tabs = (@tabs.select { |column| column < columns } + added_tabs).uniq.sort
|
|
308
345
|
@scroll_top, @scroll_bottom = 0, rows - 1
|
|
309
346
|
move
|
|
347
|
+
mark_all_damage
|
|
310
348
|
end
|
|
311
349
|
|
|
312
350
|
# Coordinates are [column, row]; finish is exclusive, including scrollback
|
|
@@ -347,6 +385,11 @@ module Tarazed
|
|
|
347
385
|
explicit.uniq
|
|
348
386
|
end
|
|
349
387
|
|
|
388
|
+
def search(query, regex: false, history: true, normalize: true)
|
|
389
|
+
source = history ? scrollback.to_a + cells : cells
|
|
390
|
+
scrollback.search(query, regex: regex, normalize: normalize, rows: source)
|
|
391
|
+
end
|
|
392
|
+
|
|
350
393
|
def file_paths(row, cwd: Dir.pwd)
|
|
351
394
|
line = cells.fetch(row).map(&:text).join
|
|
352
395
|
line.scan(%r{(?:\A|[\s("'])([^\s:"'()]+):(\d+)(?::(\d+))?}).filter_map do |name, number, column|
|
data/lib/tarazed/scrollback.rb
CHANGED
|
@@ -30,6 +30,25 @@ module Tarazed
|
|
|
30
30
|
@rows << row.freeze
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def search(query, regex: false, normalize: true, rows: @rows)
|
|
34
|
+
matcher = regex ? Regexp.new(query.to_s) : query.to_s
|
|
35
|
+
rows.each_with_index.filter_map do |cells, index|
|
|
36
|
+
text = cells.map(&:text).join.rstrip
|
|
37
|
+
comparable = normalize && text.respond_to?(:unicode_normalize) ? text.unicode_normalize(:nfc) : text
|
|
38
|
+
match = regex ? matcher.match(comparable) : comparable.index(matcher)
|
|
39
|
+
next unless match
|
|
40
|
+
|
|
41
|
+
range = if match.is_a?(MatchData)
|
|
42
|
+
match.begin(0)...match.end(0)
|
|
43
|
+
else
|
|
44
|
+
match...(match + matcher.length)
|
|
45
|
+
end
|
|
46
|
+
{index: index, text: text, range: range}.freeze
|
|
47
|
+
end.freeze
|
|
48
|
+
rescue RegexpError => error
|
|
49
|
+
raise ArgumentError, "invalid search pattern: #{error.message}"
|
|
50
|
+
end
|
|
51
|
+
|
|
33
52
|
def advance(count) = @total += count
|
|
34
53
|
end
|
|
35
54
|
end
|
data/lib/tarazed/version.rb
CHANGED
data/lib/tarazed/vt.rb
CHANGED
|
@@ -92,6 +92,14 @@ module Tarazed
|
|
|
92
92
|
modes[2004] ? "\e[200~#{text.gsub("\e[201~", "")}\e[201~" : text
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
def synchronized? = !!modes[2026]
|
|
96
|
+
|
|
97
|
+
def focus(active:)
|
|
98
|
+
return "" unless modes[1004]
|
|
99
|
+
|
|
100
|
+
active ? "\e[I" : "\e[O"
|
|
101
|
+
end
|
|
102
|
+
|
|
95
103
|
def mouse(button:, column:, row:, action: :press, shift: false, alt: false, control: false)
|
|
96
104
|
tracking = [1000, 1002, 1003].find { |mode| modes[mode] }
|
|
97
105
|
return "" unless tracking
|
|
@@ -355,6 +363,10 @@ module Tarazed
|
|
|
355
363
|
when 1049 then grid.alternate(enabled)
|
|
356
364
|
when 1000, 1002, 1003
|
|
357
365
|
[1000, 1002, 1003].each { |other| @modes[other] = false unless other == mode } if enabled
|
|
366
|
+
when 1004
|
|
367
|
+
# Focus reporting is emitted by #focus when the host window changes.
|
|
368
|
+
when 2026
|
|
369
|
+
grid.synchronized = enabled
|
|
358
370
|
end
|
|
359
371
|
elsif mode == 4
|
|
360
372
|
grid.insert_mode = enabled
|