rubyterm 0.1.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 +7 -0
- data/README.md +144 -0
- data/bin/record +3 -0
- data/bin/rubyterm +11 -0
- data/example-config.toml +61 -0
- data/lib/ansibackend.rb +155 -0
- data/lib/bitmapwindow.rb +176 -0
- data/lib/charsets.rb +52 -0
- data/lib/controller.rb +80 -0
- data/lib/escapeparser.rb +71 -0
- data/lib/keymap.rb +112 -0
- data/lib/palette.rb +14 -0
- data/lib/rubyterm/app.rb +580 -0
- data/lib/rubyterm/version.rb +5 -0
- data/lib/rubyterm.rb +47 -0
- data/lib/term.rb +657 -0
- data/lib/termbuffer.rb +365 -0
- data/lib/trackchanges.rb +319 -0
- data/lib/utf8decoder.rb +77 -0
- data/lib/window.rb +410 -0
- data/lib/windowadapter.rb +161 -0
- metadata +127 -0
data/lib/escapeparser.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# See:
|
|
2
|
+
# https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Controls-beginning-with-ESC
|
|
3
|
+
#
|
|
4
|
+
class EscapeParser
|
|
5
|
+
attr_reader :str, :state
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@state = :start
|
|
9
|
+
@str = ""
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def put(ch)
|
|
13
|
+
# ch is a codepoint, which for OSC/DCS string payloads (e.g. a window
|
|
14
|
+
# title) can be a multibyte UTF-8 character - decode it as UTF-8.
|
|
15
|
+
# Bare Integer#chr only handles 0..255 and raises RangeError on
|
|
16
|
+
# anything larger (e.g. a spinner glyph in claude's title sequence).
|
|
17
|
+
c = ch.chr(Encoding::UTF_8)
|
|
18
|
+
case @state
|
|
19
|
+
when :start
|
|
20
|
+
return false if ch < 32
|
|
21
|
+
@str << c
|
|
22
|
+
case c
|
|
23
|
+
when '['
|
|
24
|
+
@state = :csi
|
|
25
|
+
when ']'
|
|
26
|
+
@state = :oc
|
|
27
|
+
when 'P', 'X', '^', '_'
|
|
28
|
+
# DCS, SOS, PM, APC - string sequences terminated by ST (ESC \) or BEL
|
|
29
|
+
@state = :string
|
|
30
|
+
when /\w|[=>|}~6-9]/
|
|
31
|
+
@state = :complete
|
|
32
|
+
end
|
|
33
|
+
when :csi
|
|
34
|
+
# A C0 control byte inside a CSI is executed immediately and the
|
|
35
|
+
# sequence keeps parsing (per ECMA-48) - it must NOT be folded into
|
|
36
|
+
# the sequence or terminate it. Returning false routes it to Term's
|
|
37
|
+
# handle_control while @esc stays in :csi; ESC (27) restarts the
|
|
38
|
+
# sequence there. (vttest "cursor-control chars inside ESC sequences".)
|
|
39
|
+
return false if ch < 32
|
|
40
|
+
@str << c
|
|
41
|
+
@state = :complete if /[[:alpha:]]|[@]/.match(c)
|
|
42
|
+
when :oc
|
|
43
|
+
if ch == 7 then @state = :complete
|
|
44
|
+
elsif ch < 32 then return false
|
|
45
|
+
else @str << c
|
|
46
|
+
end
|
|
47
|
+
when :string
|
|
48
|
+
if ch == 7
|
|
49
|
+
@state = :complete
|
|
50
|
+
elsif ch == 27
|
|
51
|
+
@state = :string_esc
|
|
52
|
+
elsif ch < 32
|
|
53
|
+
return false
|
|
54
|
+
else
|
|
55
|
+
@str << c
|
|
56
|
+
end
|
|
57
|
+
when :string_esc
|
|
58
|
+
if c == '\\'
|
|
59
|
+
@state = :complete
|
|
60
|
+
else
|
|
61
|
+
@str << 27.chr << c
|
|
62
|
+
@state = :string
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
return false
|
|
66
|
+
end
|
|
67
|
+
true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def complete?; @state == :complete; end
|
|
71
|
+
end
|
data/lib/keymap.rb
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
$kmap = nil
|
|
4
|
+
def update_keymap(dpy)
|
|
5
|
+
reply = dpy.get_keyboard_mapping
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if reply
|
|
9
|
+
$kmap = reply.keysyms.map do |c|
|
|
10
|
+
if c == 0
|
|
11
|
+
nil
|
|
12
|
+
elsif X11::KeySyms[c]
|
|
13
|
+
X11::KeySyms[c]
|
|
14
|
+
elsif c < 0x100
|
|
15
|
+
c.chr(Encoding::ISO_8859_1)
|
|
16
|
+
elsif c.between?(0xffb0, 0xffb9)
|
|
17
|
+
"KP_#{c-0xffb0}".to_sym
|
|
18
|
+
elsif c.between?(0xffbe, 0xffe0)
|
|
19
|
+
"f#{c-0xffbe+1}".to_sym
|
|
20
|
+
elsif c.between?(0xff08, 0xffff)
|
|
21
|
+
# FIXME:
|
|
22
|
+
raise "keyboard_#{c.to_s(16)}".to_s
|
|
23
|
+
elsif c.between?(0x01000100, 0x0110FFFF)
|
|
24
|
+
(c-0x01000100).
|
|
25
|
+
chr(Encoding::UTF_32) rescue c.to_s(16)
|
|
26
|
+
else
|
|
27
|
+
STDERR.puts "keymap: unknown_#{c.to_s(16)}"
|
|
28
|
+
end
|
|
29
|
+
end.each_slice(reply.keysyms_per_keycode).to_a
|
|
30
|
+
#ks = ks.map {|s| s.compact.sort_by{|x| x.to_s}.uniq }.to_a # This is for testing/ease of reading only
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def lookup_keysym(dpy, event)
|
|
35
|
+
update_keymap(dpy) if !$kmap
|
|
36
|
+
$kmap[event.detail-dpy.display_info.min_keycode] rescue nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
SPECIALS = {
|
|
40
|
+
"+" => :"ctrl_+",
|
|
41
|
+
"-" => :"ctrl_-"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def lookup_string(dpy, event)
|
|
45
|
+
ks = Array(lookup_keysym(dpy, event))
|
|
46
|
+
str = ""
|
|
47
|
+
shift = event.state.anybits?(0x01)
|
|
48
|
+
meta = event.state.anybits?(0x08)
|
|
49
|
+
ctrl = event.state.anybits?(0x04)
|
|
50
|
+
i = shift && ks[1] ? 1 : 0
|
|
51
|
+
|
|
52
|
+
return SPECIALS[ks[i]] if ctrl && SPECIALS[ks[i]]
|
|
53
|
+
|
|
54
|
+
if shift
|
|
55
|
+
case ks[i]
|
|
56
|
+
# FIXME: This is messed up.
|
|
57
|
+
when :XK_ISO_Left_Tab then return :shift_tab, nil
|
|
58
|
+
when :XK_Down; then return :shift_down, nil
|
|
59
|
+
when :XK_Up; then return :shift_up, nil
|
|
60
|
+
when :XK_Page_Up; then return :shift_page_up, nil
|
|
61
|
+
when :XK_Page_Down; then return :shift_page_down, nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if ks[i].is_a?(String)
|
|
66
|
+
if ctrl
|
|
67
|
+
str = (ks[i][0].ord & 0x9f).chr # Strip 0x60
|
|
68
|
+
elsif meta
|
|
69
|
+
str = "\e#{ks[i]}"
|
|
70
|
+
else
|
|
71
|
+
str = ks[i]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
return ks[i], str
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Map X key events to escapes
|
|
80
|
+
KEYMAP = {
|
|
81
|
+
:enter => "\r",
|
|
82
|
+
:backspace => "\x7F",
|
|
83
|
+
:tab => "\t",
|
|
84
|
+
:XK_Escape => "\e",
|
|
85
|
+
:f1 => "\e[11~",
|
|
86
|
+
:f2 => "\e[12~",
|
|
87
|
+
:f3 => "\e[13~",
|
|
88
|
+
:f4 => "\e[14~",
|
|
89
|
+
:f5 => "\e[15~",
|
|
90
|
+
:f6 => "\e[16~",
|
|
91
|
+
:f7 => "\e[17~",
|
|
92
|
+
:f8 => "\e[18~",
|
|
93
|
+
:f9 => "\e[19~",
|
|
94
|
+
:f10 => "\e[20~",
|
|
95
|
+
:f11 => "\e[21~",
|
|
96
|
+
:f12 => "\e[22~",
|
|
97
|
+
:shift_up => "\e[1;2A",
|
|
98
|
+
:shift_down => "\e[1;2B",
|
|
99
|
+
:shift_tab => "\e[Z",
|
|
100
|
+
:XK_Left => "\e[D",
|
|
101
|
+
:XK_Right => "\e[C",
|
|
102
|
+
:XK_Down => "\e[B",
|
|
103
|
+
:XK_ISO_Left_Tab => "\t",
|
|
104
|
+
:XK_Up => "\e[A",
|
|
105
|
+
:XK_Delete => "\e[P",
|
|
106
|
+
:XK_Page_Up => "\x1b[5~",
|
|
107
|
+
:XK_Page_Down => "\x1b[6~",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def keysym_to_vt102(ks)
|
|
111
|
+
KEYMAP[ks]
|
|
112
|
+
end
|
data/lib/palette.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
PALETTE_BASIC = [
|
|
2
|
+
0x000000, 0xd73737, 0x60ac39, 0xae9513, 0x6684e1, 0xb854d4, 0x1fad83, 0xcccccc,
|
|
3
|
+
0x7d7a68, 0xff3737, 0x80dc39, 0xfff013, 0x6694ff, 0xd854ff, 0x1fdda3, 0xffffff]
|
|
4
|
+
|
|
5
|
+
PALETTE256= PALETTE_BASIC +
|
|
6
|
+
[0,0x5f, 0x87, 0xaf, 0xd7, 0xff].repeated_permutation(3).sort.map{|ary| (ary[0]<<16)+(ary[1]<<8)+ary[2]}+
|
|
7
|
+
(8..244).step(10).map{|n| (n<<16)+(n<<8)+n}
|
|
8
|
+
|
|
9
|
+
# Default foreground/background, stored as basic-palette indices (strings;
|
|
10
|
+
# Term resolves them to colours). Defined here so the library is
|
|
11
|
+
# self-contained; guarded so callers that pre-define them (tests, harness)
|
|
12
|
+
# don't clash.
|
|
13
|
+
FG = "7" unless defined?(FG)
|
|
14
|
+
BG = "0" unless defined?(BG)
|