rubyterm 0.2.4 → 0.2.6
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/lib/bitmapwindow.rb +9 -5
- data/lib/charwidth.rb +37 -7
- data/lib/rubyterm/app.rb +19 -0
- data/lib/rubyterm/version.rb +1 -1
- data/lib/term.rb +5 -0
- data/lib/trackchanges.rb +4 -0
- data/lib/windowadapter.rb +8 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3ad358ca60e1bd23c01f81186b75826f64f1ba9cb519a149a33d56106561062
|
|
4
|
+
data.tar.gz: e367c640889e4724e4266410735df54fbd89a56536d48e4c94b72c8343bd3ac2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b70f474074dfbcbc6eac034340155de305f4ea8fb8daf43a89c82d88c2e5d18ebcedfb81567d230b62e57c63c82e8226533ecc4d20e994058772224149544678
|
|
7
|
+
data.tar.gz: a98d2d55fda7b4954612b82064dde3b95b58884a2e9ade88c65d75e1475c36982db442795a6e8d5514b61ed7b78e4f701235e9cc995442141ea3364cda14369a
|
data/lib/bitmapwindow.rb
CHANGED
|
@@ -83,10 +83,14 @@ class BitmapWindow
|
|
|
83
83
|
# for now - it does not affect correctness of the text, only its scale.
|
|
84
84
|
def draw(x, y, str, fg, bg, _lineattrs = nil)
|
|
85
85
|
fillrect(x, y, str.length * @char_w, @char_h, bg)
|
|
86
|
-
str.each_char.
|
|
87
|
-
|
|
86
|
+
cps = str.each_char.map(&:ord)
|
|
87
|
+
cps.each_with_index do |cp, i|
|
|
88
88
|
next if cp == 32 || cp == CharWidth::WIDE_SPACER # space / wide-glyph tail
|
|
89
|
-
|
|
89
|
+
# A WIDE_SPACER in the next cell means this is a double-width glyph; render
|
|
90
|
+
# it two cells wide so it overflows into the (skipped) spacer cell instead
|
|
91
|
+
# of being shrunk into one.
|
|
92
|
+
cells = cps[i + 1] == CharWidth::WIDE_SPACER ? 2 : 1
|
|
93
|
+
blit_glyph(cp, x + i * @char_w, y, fg, cells)
|
|
90
94
|
end
|
|
91
95
|
end
|
|
92
96
|
|
|
@@ -146,8 +150,8 @@ class BitmapWindow
|
|
|
146
150
|
end
|
|
147
151
|
end
|
|
148
152
|
|
|
149
|
-
def blit_glyph(codepoint, cx, cy, fg)
|
|
150
|
-
g = @cache.glyph(codepoint)
|
|
153
|
+
def blit_glyph(codepoint, cx, cy, fg, cells = 1)
|
|
154
|
+
g = @cache.glyph(codepoint, cells)
|
|
151
155
|
return unless g
|
|
152
156
|
if g.color?
|
|
153
157
|
blit_rgba(g, codepoint, cx, cy)
|
data/lib/charwidth.rb
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
# * width(cp) -> 1 or 2 cells (wide = CJK/Hangul/Kana/fullwidth + emoji)
|
|
12
12
|
# * emoji?(cp) -> whether to render in colour (the non-CJK wide blocks)
|
|
13
13
|
#
|
|
14
|
-
# Combining marks (width 0) are deliberately not handled yet (treated as 1).
|
|
15
14
|
module CharWidth
|
|
16
15
|
# Codepoint stored in the second cell of a double-width glyph: a blank,
|
|
17
16
|
# advancing placeholder. The terminal writes it after a wide char so the
|
|
@@ -19,6 +18,26 @@ module CharWidth
|
|
|
19
18
|
# advancing glyph and the bitmap/virtual backends skip it.
|
|
20
19
|
WIDE_SPACER = 0
|
|
21
20
|
|
|
21
|
+
# Zero-width (combining) codepoints. These modify the preceding base glyph
|
|
22
|
+
# rather than advancing the cursor, so they occupy no column — matching tmux
|
|
23
|
+
# (a lone U+FE0F reports cursor_x=0; a base+U+FE0F sequence stays the base's
|
|
24
|
+
# width). Notably this includes the variation selectors: U+FE0F (VS16, emoji
|
|
25
|
+
# presentation) and U+FE0E (VS15, text presentation) request a presentation
|
|
26
|
+
# for the preceding char but are themselves invisible and zero-width. Treating
|
|
27
|
+
# them as width 1 gave each its own cell, drawing a stray glyph over the next
|
|
28
|
+
# column. Per-script combining marks beyond these blocks are a deferred
|
|
29
|
+
# non-goal (still treated as width 1).
|
|
30
|
+
ZERO = [
|
|
31
|
+
0x0300..0x036F, # combining diacritical marks
|
|
32
|
+
0x1AB0..0x1AFF, # combining diacritical marks extended
|
|
33
|
+
0x1DC0..0x1DFF, # combining diacritical marks supplement
|
|
34
|
+
0x200B..0x200F, # ZWSP, ZWNJ, ZWJ, LRM, RLM (zero-width format)
|
|
35
|
+
0x20D0..0x20FF, # combining diacritical marks for symbols
|
|
36
|
+
0xFE00..0xFE0F, # variation selectors (incl. VS15/VS16)
|
|
37
|
+
0xFE20..0xFE2F, # combining half marks
|
|
38
|
+
0xE0100..0xE01EF # variation selectors supplement
|
|
39
|
+
].freeze
|
|
40
|
+
|
|
22
41
|
# Wide (two-cell) blocks: CJK, Hangul, Kana, fullwidth forms, and emoji.
|
|
23
42
|
#
|
|
24
43
|
# In the BMP symbol zone (Misc Symbols, Dingbats, Misc Symbols & Arrows)
|
|
@@ -45,9 +64,16 @@ module CharWidth
|
|
|
45
64
|
0x1FA70..0x1FAD6, 0x20000..0x2EBE0, 0x2F800..0x2FA1D, 0x30000..0x3134A
|
|
46
65
|
].freeze
|
|
47
66
|
|
|
48
|
-
#
|
|
49
|
-
# in colour
|
|
50
|
-
#
|
|
67
|
+
# Emoji code blocks (excludes CJK/Hangul/Kana/fullwidth): a codepoint renders
|
|
68
|
+
# in colour only if it is BOTH in one of these blocks AND wide (width 2) —
|
|
69
|
+
# see emoji?. The width-2 gate is what distinguishes emoji-presentation
|
|
70
|
+
# codepoints (⚡ U+26A1, ✨ U+2728 — colour, width 2) from text-presentation
|
|
71
|
+
# dingbats that merely share a block (❤ U+2764, ✔ U+2714, ⚠ U+26A0 — width 1).
|
|
72
|
+
# Both properties derive from the same Unicode Emoji_Presentation flag, so
|
|
73
|
+
# this is not "width deciding colour": rather, colour is the subset of these
|
|
74
|
+
# blocks that is wide enough to render a square colour glyph without spilling
|
|
75
|
+
# into the next cell. Digits, '#', '*' are absent, so they stay ordinary text
|
|
76
|
+
# even though Noto maps colour keycaps.
|
|
51
77
|
EMOJI = [
|
|
52
78
|
0x231A..0x232A, 0x23E9..0x23F3, 0x25FD..0x27BF, 0x2B1B..0x2B55,
|
|
53
79
|
0x1F18E..0x1F19A, 0x1F200..0x1F265, 0x1F300..0x1F5A4, 0x1F5FB..0x1F6FC,
|
|
@@ -56,18 +82,22 @@ module CharWidth
|
|
|
56
82
|
|
|
57
83
|
module_function
|
|
58
84
|
|
|
59
|
-
# Number of terminal cells a codepoint occupies (1 or 2).
|
|
85
|
+
# Number of terminal cells a codepoint occupies (0, 1 or 2).
|
|
60
86
|
def width(cp)
|
|
61
|
-
return 1 if cp <
|
|
87
|
+
return 1 if cp < 0x0300 # fast path: ASCII/Latin-1 (no combining below)
|
|
88
|
+
return 0 if in_ranges?(ZERO, cp)
|
|
89
|
+
return 1 if cp < 0x1100 # fast path: most other text
|
|
62
90
|
in_ranges?(WIDE, cp) ? 2 : 1
|
|
63
91
|
end
|
|
64
92
|
|
|
65
93
|
def wide?(cp) = width(cp) == 2
|
|
66
94
|
|
|
67
95
|
# Whether a codepoint should render as a colour emoji (when the font has it).
|
|
96
|
+
# Gated to width-2 emoji: a colour glyph is rasterised square (~2 cells wide),
|
|
97
|
+
# so colouring a width-1 cell would overflow and corrupt the next column.
|
|
68
98
|
def emoji?(cp)
|
|
69
99
|
return false if cp < 0x231A
|
|
70
|
-
in_ranges?(EMOJI, cp)
|
|
100
|
+
in_ranges?(EMOJI, cp) && width(cp) == 2
|
|
71
101
|
end
|
|
72
102
|
|
|
73
103
|
# Binary search over a sorted array of non-overlapping ranges.
|
data/lib/rubyterm/app.rb
CHANGED
|
@@ -420,6 +420,25 @@ class RubyTerm
|
|
|
420
420
|
@buffer.draw_flush
|
|
421
421
|
end
|
|
422
422
|
|
|
423
|
+
# A line of the scroll region [start..bottom] has scrolled up into
|
|
424
|
+
# history. The selection is stored in *buffer* coordinates (negative rows
|
|
425
|
+
# = scrollback), so the content the user selected now sits one row higher:
|
|
426
|
+
# cells inside the scrolled region - and anything already in scrollback,
|
|
427
|
+
# whose negative index shifts as the history grows - move up by one, while
|
|
428
|
+
# content below the region stays put. Shift the stored selection to match
|
|
429
|
+
# so reapply_selection keeps the highlight pinned to the same text as it
|
|
430
|
+
# scrolls (into scrollback and back), instead of leaving it on whatever
|
|
431
|
+
# rolls into the old screen rows. Called from TrackChanges#scroll_up via
|
|
432
|
+
# the adapter for every line moved into history.
|
|
433
|
+
def shift_selection_for_scroll(start, bottom)
|
|
434
|
+
return unless @select_startpos
|
|
435
|
+
[@select_startpos, @select_endpos].each do |pos|
|
|
436
|
+
next unless pos
|
|
437
|
+
y = pos[1]
|
|
438
|
+
pos[1] -= 1 if y < 0 || (y >= start && y <= bottom)
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
423
442
|
# FIXME: Cursor, selection etc. are "special" overlays on top of attributes.
|
|
424
443
|
# Allow the terminal to set a set of positions + fg/bg, and a set of ranges.
|
|
425
444
|
def render_selection
|
data/lib/rubyterm/version.rb
CHANGED
data/lib/term.rb
CHANGED
|
@@ -565,6 +565,11 @@ class Term
|
|
|
565
565
|
return if ch == 127 # DEL is ignored in the data stream
|
|
566
566
|
|
|
567
567
|
cw = CharWidth.width(ch)
|
|
568
|
+
# Zero-width codepoints (variation selectors like U+FE0F, combining marks,
|
|
569
|
+
# zero-width joiners) modify the preceding glyph. This buffer stores one
|
|
570
|
+
# codepoint per cell and can't compose, so drop them rather than letting
|
|
571
|
+
# them claim a cell of their own — which would paint over the next column.
|
|
572
|
+
return if cw == 0
|
|
568
573
|
# A double-width glyph can't straddle the right margin: if only one
|
|
569
574
|
# column is left, blank it and wrap so the glyph starts the next line.
|
|
570
575
|
if cw == 2 && @wraparound && @x == line_width - 1
|
data/lib/trackchanges.rb
CHANGED
|
@@ -69,6 +69,10 @@ class TrackChanges
|
|
|
69
69
|
start = @buffer.scroll_start.to_i
|
|
70
70
|
bottom = @buffer.scroll_end || (@rows - 1)
|
|
71
71
|
@buffer.scroll_up
|
|
72
|
+
# The content moved up in the buffer; keep any active text selection
|
|
73
|
+
# pinned to it. Done before the @suspend bail-out so the selection stays
|
|
74
|
+
# aligned even through jump-scrolled (unrendered) frames.
|
|
75
|
+
@adapter.scroll_selection(start, bottom)
|
|
72
76
|
return if @suspend
|
|
73
77
|
if @adapter.scrollback_mode
|
|
74
78
|
@adapter.scrollback_anchor
|
data/lib/windowadapter.rb
CHANGED
|
@@ -30,6 +30,14 @@ class WindowAdapter
|
|
|
30
30
|
# offset instead of letting the viewport drift.
|
|
31
31
|
def scrollback_anchor = @window.scrollback_anchor
|
|
32
32
|
|
|
33
|
+
# A line of the scroll region [start..bottom] has moved into history.
|
|
34
|
+
# Forward to the orchestrator so it can keep a live text selection pinned
|
|
35
|
+
# to the content it covers. The headless harness/bench hosts have no
|
|
36
|
+
# selection state and do not implement this, so it no-ops there.
|
|
37
|
+
def scroll_selection(start, bottom)
|
|
38
|
+
@term.shift_selection_for_scroll(start, bottom) if @term.respond_to?(:shift_selection_for_scroll)
|
|
39
|
+
end
|
|
40
|
+
|
|
33
41
|
# DECCOLM: the terminal asked to switch to +cols+ columns (80/132). The
|
|
34
42
|
# orchestrator (RubyTerm) owns the window pixels, font scale, config and
|
|
35
43
|
# the pty size report, so delegate to it. The headless harness "term"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubyterm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vidar Hokstad
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pure-x11
|