rubyterm 0.2.2 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eaf9bef055d78f4fe83be5108bf948285ba3b36df6049dc68b60f97ff5ec2468
4
- data.tar.gz: aba855f9e0abe0f223fdacd85a84796eb905b254052c14f4515894a62433a467
3
+ metadata.gz: dad934f37ee68f63661c2d7bbd2e8c7af64a8811fb8fd27b9d5bd63787a8f021
4
+ data.tar.gz: 124c7b36ea94bef960b7bbc78f9143149cce43cf443b4e7d9ed99207e40d1f01
5
5
  SHA512:
6
- metadata.gz: fcf76c9027bc5448199736f5faf67fd4bebd592ed3470981f821374ddb85d22c45bab8cb15d289bf0da32c3fc4edb196d02eee6ee14d14457f688cd528a6a22a
7
- data.tar.gz: e37c492d2661b587f952ce3606eaf08e86e69fe5d07c50799b3bb9540003eb863804488fdcb05205962540ded847fa05edc311bacad0ac2482943bdb77741c22
6
+ metadata.gz: ea7bf3f07aa5088ca07bc79058aa836d20af2c95d5d6887d7fe5e7fd8f44283170c4fa2df7078d5f10a271582b5d339143473e8fc3d1af51d74c42f562e3e0a5
7
+ data.tar.gz: 491468d6aab9c96754001425718102aaa1c677c994ca1f82c47a3fa07196327bb3ed132ef72c547d77904216f6ed9f700373fdd3c6b08cae3769904f36f562a9
data/bin/record ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/env ruby
2
+
3
+ exec(%{ruby harness/cli.rb record --out /tmp/#{ARGV[0].gsub("/","_")}.rec -- #{ARGV.join(" ")}})
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.with_index do |ch, i|
87
- cp = ch.ord
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
- blit_glyph(cp, x + i * @char_w, y, fg)
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,19 +18,62 @@ 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.
42
+ #
43
+ # In the BMP symbol zone (Misc Symbols, Dingbats, Misc Symbols & Arrows)
44
+ # only the East Asian Wide + emoji-presentation codepoints are double-width;
45
+ # the surrounding text dingbats are single-width. A broad 0x25FD..0x27BF (and
46
+ # peers) wrongly widened text characters like U+273B (the spinner ✻ used by
47
+ # TUIs), shifting the cursor one column on every line that contained one and
48
+ # corrupting incremental redraws. These ranges now match tmux exactly.
23
49
  WIDE = [
24
- 0x1100..0x115F, 0x231A..0x232A, 0x23E9..0x23F3, 0x25FD..0x27BF,
25
- 0x2B1B..0x2B55, 0x2E80..0xA4C6, 0xA960..0xA97C, 0xAC00..0xD7A3,
50
+ 0x1100..0x115F,
51
+ 0x231A..0x231B, 0x2329..0x232A, 0x23E9..0x23EC, 0x23F0..0x23F0,
52
+ 0x23F3..0x23F3, 0x25FD..0x25FE, 0x2614..0x2615, 0x2648..0x2653,
53
+ 0x267F..0x267F, 0x2693..0x2693, 0x26A1..0x26A1, 0x26AA..0x26AB,
54
+ 0x26BD..0x26BE, 0x26C4..0x26C5, 0x26CE..0x26CE, 0x26D4..0x26D4,
55
+ 0x26EA..0x26EA, 0x26F2..0x26F3, 0x26F5..0x26F5, 0x26FA..0x26FA,
56
+ 0x26FD..0x26FD, 0x2705..0x2705, 0x270A..0x270B, 0x2728..0x2728,
57
+ 0x274C..0x274C, 0x274E..0x274E, 0x2753..0x2755, 0x2757..0x2757,
58
+ 0x2795..0x2797, 0x27B0..0x27B0, 0x27BF..0x27BF, 0x2B1B..0x2B1C,
59
+ 0x2B50..0x2B50, 0x2B55..0x2B55,
60
+ 0x2E80..0xA4C6, 0xA960..0xA97C, 0xAC00..0xD7A3,
26
61
  0xF900..0xFAD9, 0xFE10..0xFE6B, 0xFF01..0xFF60, 0xFFE0..0xFFE6,
27
62
  0x16FE0..0x18D08, 0x1B000..0x1B2FB, 0x1F18E..0x1F19A, 0x1F200..0x1F265,
28
63
  0x1F300..0x1F5A4, 0x1F5FB..0x1F6FC, 0x1F7E0..0x1F7EB, 0x1F90C..0x1F9FF,
29
64
  0x1FA70..0x1FAD6, 0x20000..0x2EBE0, 0x2F800..0x2FA1D, 0x30000..0x3134A
30
65
  ].freeze
31
66
 
32
- # The emoji subset of WIDE (excludes CJK/Hangul/Kana/fullwidth): these render
33
- # in colour when a colour font provides them. Digits, '#', '*' etc. are not
34
- # here, so they stay as ordinary text even though Noto maps colour keycaps.
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.
35
77
  EMOJI = [
36
78
  0x231A..0x232A, 0x23E9..0x23F3, 0x25FD..0x27BF, 0x2B1B..0x2B55,
37
79
  0x1F18E..0x1F19A, 0x1F200..0x1F265, 0x1F300..0x1F5A4, 0x1F5FB..0x1F6FC,
@@ -40,18 +82,22 @@ module CharWidth
40
82
 
41
83
  module_function
42
84
 
43
- # Number of terminal cells a codepoint occupies (1 or 2).
85
+ # Number of terminal cells a codepoint occupies (0, 1 or 2).
44
86
  def width(cp)
45
- return 1 if cp < 0x1100 # fast path: ASCII/Latin and most text
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
46
90
  in_ranges?(WIDE, cp) ? 2 : 1
47
91
  end
48
92
 
49
93
  def wide?(cp) = width(cp) == 2
50
94
 
51
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.
52
98
  def emoji?(cp)
53
99
  return false if cp < 0x231A
54
- in_ranges?(EMOJI, cp)
100
+ in_ranges?(EMOJI, cp) && width(cp) == 2
55
101
  end
56
102
 
57
103
  # Binary search over a sorted array of non-overlapping ranges.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RubyTerm
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/term.rb CHANGED
@@ -115,6 +115,15 @@ class Term
115
115
  @buffer.clear # the buffer (TrackChanges) also clears the backend
116
116
  end
117
117
 
118
+ # Cursor + charset save/restore, shared by DECSC/DECRC (ESC 7 / ESC 8) and
119
+ # DEC private modes 1048/1049. DECRC with no prior save defaults to the home
120
+ # position and default charsets rather than leaving @x/@y nil (which would
121
+ # crash draw_cursor).
122
+ def save_cursor = @saved = [@x, @y, @gl, @gr, @g.dup]
123
+ def restore_cursor
124
+ @x, @y, @gl, @gr, @g = @saved || [0, 0, 0, nil, [DefaultCharset, nil, nil, nil]]
125
+ end
126
+
118
127
  # RIS - Reset to Initial State (ESC c). Full reset: restore margins,
119
128
  # modes, charsets, tab stops and attributes to their defaults, home the
120
129
  # cursor and clear the screen.
@@ -408,6 +417,16 @@ class Term
408
417
  # FIXME: Save/restore
409
418
  # FIXME: Scrollback should be disabled/enabled.
410
419
  clear_screen
420
+ when 1048
421
+ # Save (h) / restore (l) cursor, as DECSC / DECRC.
422
+ set ? save_cursor : restore_cursor
423
+ when 1049
424
+ # Alternate screen + cursor save/restore. Like mode 47 this
425
+ # terminal has no separate alt buffer yet, so it just clears; the
426
+ # cursor save (on enter) / restore (on leave) is what apps rely on
427
+ # to land the cursor back where it was before the alt screen.
428
+ set ? save_cursor : restore_cursor
429
+ clear_screen
411
430
 
412
431
  # Extended mouse modes
413
432
  # See https://terminalguide.namepad.de/mouse/
@@ -546,6 +565,11 @@ class Term
546
565
  return if ch == 127 # DEL is ignored in the data stream
547
566
 
548
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
549
573
  # A double-width glyph can't straddle the right margin: if only one
550
574
  # column is left, blank it and wrap so the glyph starts the next line.
551
575
  if cw == 2 && @wraparound && @x == line_width - 1
@@ -596,12 +620,8 @@ class Term
596
620
  when ")B"; @g[1] = DefaultCharset
597
621
  when "(0"; @g[0] = GraphicsCharset
598
622
  when ")0"; @g[1] = GraphicsCharset
599
- when "7"; @saved = [@x,@y,@gl,@gr,@g.dup]
600
- when "8"
601
- # DECRC with no prior DECSC: default to home position and default
602
- # charsets rather than leaving @x/@y nil (which crashes draw_cursor).
603
- sx, sy, sgl, sgr, sg = @saved || [0, 0, 0, nil, [DefaultCharset, nil, nil, nil]]
604
- @x, @y, @gl, @gr, @g = sx, sy, sgl, sgr, sg
623
+ when "7"; save_cursor # DECSC
624
+ when "8"; restore_cursor # DECRC
605
625
  else
606
626
  unhandled(:escape, s)
607
627
  end
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.2
4
+ version: 0.2.5
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-22 00:00:00.000000000 Z
11
+ date: 2026-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pure-x11
@@ -95,6 +95,7 @@ extensions: []
95
95
  extra_rdoc_files: []
96
96
  files:
97
97
  - README.md
98
+ - bin/record
98
99
  - bin/rubyterm
99
100
  - example-config.toml
100
101
  - lib/ansibackend.rb