tarazed 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/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/docs/adr/000-template.md +17 -0
- data/docs/adr/001-terminal-core-boundary.md +26 -0
- data/docs/adr/README.md +3 -0
- data/lib/tarazed/cell.rb +5 -0
- data/lib/tarazed/grid.rb +379 -0
- data/lib/tarazed/pty.rb +204 -0
- data/lib/tarazed/scrollback.rb +29 -0
- data/lib/tarazed/version.rb +5 -0
- data/lib/tarazed/vt.rb +444 -0
- data/lib/tarazed/windows/conpty.rb +255 -0
- data/lib/tarazed.rb +12 -0
- data/sig/tarazed.rbs +139 -0
- metadata +86 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fiddle"
|
|
4
|
+
|
|
5
|
+
module Tarazed
|
|
6
|
+
module Windows
|
|
7
|
+
class Library
|
|
8
|
+
def initialize(name)
|
|
9
|
+
@handle = Fiddle.dlopen(name)
|
|
10
|
+
@functions = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fn(name, arguments, result, need_gvl: true)
|
|
14
|
+
@functions[[name, arguments, result, need_gvl]] ||= Fiddle::Function.new(
|
|
15
|
+
@handle[name.to_s], arguments, result, name: name.to_s, need_gvl: need_gvl
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Windows ConPTY backend. PTY owns the shared grid and VT parser.
|
|
21
|
+
class ConPTY
|
|
22
|
+
P, I, U, N, V = Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_UINT, Fiddle::TYPE_SIZE_T,
|
|
23
|
+
Fiddle::TYPE_VOID
|
|
24
|
+
private_constant :P, :I, :U, :N, :V
|
|
25
|
+
|
|
26
|
+
attr_reader :pid
|
|
27
|
+
|
|
28
|
+
def initialize(command: nil, columns: 80, rows: 24, cwd: nil, env: {}, kernel: nil)
|
|
29
|
+
raise Error, "ConPTY requires 64-bit Windows Ruby" unless Fiddle::SIZEOF_VOIDP == 8
|
|
30
|
+
|
|
31
|
+
@kernel = kernel || Library.new("kernel32.dll")
|
|
32
|
+
@output, @pending, @closed, @eof = Queue.new, +"".b, false, false
|
|
33
|
+
input_read, @input_write = pipe
|
|
34
|
+
@output_read, output_write = pipe
|
|
35
|
+
console = [0].pack("J")
|
|
36
|
+
check_hresult(function(:CreatePseudoConsole, [I, P, P, U, P], I).call(
|
|
37
|
+
coordinate(columns, rows), input_read, output_write, 0, console
|
|
38
|
+
))
|
|
39
|
+
@console = console.unpack1("J")
|
|
40
|
+
attributes = process_attributes(@console)
|
|
41
|
+
@process, @pid = spawn(command, cwd, env, attributes)
|
|
42
|
+
start_reader
|
|
43
|
+
rescue Fiddle::DLError, LoadError => error
|
|
44
|
+
release
|
|
45
|
+
raise Error, "ConPTY is unavailable: #{error.message}"
|
|
46
|
+
rescue
|
|
47
|
+
release
|
|
48
|
+
raise
|
|
49
|
+
ensure
|
|
50
|
+
begin
|
|
51
|
+
function(:DeleteProcThreadAttributeList, [P], V).call(attributes) if attributes
|
|
52
|
+
rescue StandardError
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
close_handle(input_read)
|
|
56
|
+
close_handle(output_write)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# CommandLineToArgvW/CRT escaping.
|
|
60
|
+
def self.quote_argument(value)
|
|
61
|
+
return value unless value.empty? || value.match?(/[\s"]/)
|
|
62
|
+
|
|
63
|
+
'"' + value.gsub(/(\\*)"/) { Regexp.last_match(1) * 2 + '\\"' }
|
|
64
|
+
.sub(/(\\+)\z/) { Regexp.last_match(1) * 2 } + '"'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def read_available(limit: 65_536)
|
|
68
|
+
raise ArgumentError, "limit must be positive" unless limit.is_a?(Integer) && limit.positive?
|
|
69
|
+
|
|
70
|
+
until @output.empty?
|
|
71
|
+
bytes = @output.pop
|
|
72
|
+
if bytes.nil?
|
|
73
|
+
@eof = true
|
|
74
|
+
break
|
|
75
|
+
end
|
|
76
|
+
@pending << bytes
|
|
77
|
+
end
|
|
78
|
+
return nil if @pending.empty? && @eof
|
|
79
|
+
|
|
80
|
+
@pending.slice!(0, limit)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def pending? = !@pending.empty? || !@output.empty?
|
|
84
|
+
|
|
85
|
+
def write(bytes)
|
|
86
|
+
raise IOError, "terminal closed" if @closed
|
|
87
|
+
|
|
88
|
+
offset = 0
|
|
89
|
+
while offset < bytes.bytesize
|
|
90
|
+
chunk = bytes.byteslice(offset, bytes.bytesize - offset)
|
|
91
|
+
count = [0].pack("I")
|
|
92
|
+
check(function(:WriteFile, [P, P, U, P, P], I, need_gvl: false).call(
|
|
93
|
+
@input_write, chunk, chunk.bytesize, count, 0
|
|
94
|
+
), "WriteFile")
|
|
95
|
+
written = count.unpack1("I")
|
|
96
|
+
raise IOError, "ConPTY write made no progress" if written.zero?
|
|
97
|
+
|
|
98
|
+
offset += written
|
|
99
|
+
end
|
|
100
|
+
offset
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def resize(columns, rows)
|
|
104
|
+
check_hresult(function(:ResizePseudoConsole, [P, I], I).call(@console, coordinate(columns, rows)))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def alive?
|
|
108
|
+
!@closed && function(:WaitForSingleObject, [P, U], U).call(@process, 0) == 258
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def close
|
|
112
|
+
return self if @closed
|
|
113
|
+
|
|
114
|
+
@closed = true
|
|
115
|
+
close_handle(@input_write)
|
|
116
|
+
@input_write = nil
|
|
117
|
+
close_console
|
|
118
|
+
@reader&.join(2)
|
|
119
|
+
close_handle(@output_read)
|
|
120
|
+
close_handle(@process)
|
|
121
|
+
@output_read = @process = nil
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def function(name, arguments, result, need_gvl: true)
|
|
128
|
+
@kernel.fn(name, arguments, result, need_gvl: need_gvl)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def process_attributes(console)
|
|
132
|
+
size = [0].pack("J")
|
|
133
|
+
function(:InitializeProcThreadAttributeList, [P, U, U, P], I).call(0, 1, 0, size)
|
|
134
|
+
attributes = Fiddle::Pointer.malloc(size.unpack1("J"), Fiddle::RUBY_FREE)
|
|
135
|
+
check(function(:InitializeProcThreadAttributeList, [P, U, U, P], I).call(
|
|
136
|
+
attributes, 1, 0, size
|
|
137
|
+
), "InitializeProcThreadAttributeList")
|
|
138
|
+
check(function(:UpdateProcThreadAttribute, [P, U, N, P, N, P, P], I).call(
|
|
139
|
+
attributes, 0, 0x20016, console, Fiddle::SIZEOF_VOIDP, 0, 0
|
|
140
|
+
), "UpdateProcThreadAttribute")
|
|
141
|
+
attributes
|
|
142
|
+
rescue
|
|
143
|
+
safely { function(:DeleteProcThreadAttributeList, [P], V).call(attributes) } if attributes
|
|
144
|
+
raise
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def spawn(command, cwd, env, attributes)
|
|
148
|
+
startup = "\0".b * 112
|
|
149
|
+
startup[0, 4] = [112].pack("I")
|
|
150
|
+
startup[60, 4] = [0x100].pack("I") # STARTF_USESTDHANDLES
|
|
151
|
+
startup[104, 8] = [attributes.to_i].pack("J")
|
|
152
|
+
info = "\0".b * 24
|
|
153
|
+
command ||= ENV.fetch("COMSPEC", "cmd.exe")
|
|
154
|
+
raise ArgumentError, "terminal command required" if command.respond_to?(:empty?) && command.empty?
|
|
155
|
+
|
|
156
|
+
line = if command.is_a?(Array)
|
|
157
|
+
command.map { |part| self.class.quote_argument(part.to_s) }.join(" ")
|
|
158
|
+
else
|
|
159
|
+
command.to_s
|
|
160
|
+
end
|
|
161
|
+
line = wide(line)
|
|
162
|
+
directory = cwd ? wide(File.expand_path(cwd)) : nil
|
|
163
|
+
environment = environment_block(env)
|
|
164
|
+
check(function(:CreateProcessW, [P, P, P, P, I, U, P, P, P, P], I).call(
|
|
165
|
+
0, line, 0, 0, 0, 0x00080400, environment || 0, directory || 0, startup, info
|
|
166
|
+
), "CreateProcessW")
|
|
167
|
+
process, thread, pid = info.unpack("JJI")
|
|
168
|
+
close_handle(thread)
|
|
169
|
+
[process, pid]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def environment_block(overrides)
|
|
173
|
+
return if overrides.empty?
|
|
174
|
+
|
|
175
|
+
environment = ENV.to_h
|
|
176
|
+
overrides.each do |key, value|
|
|
177
|
+
key = key.to_s
|
|
178
|
+
value.nil? ? environment.delete(key) : environment[key] = value.to_s
|
|
179
|
+
end
|
|
180
|
+
(environment.sort_by { |key, _| key.downcase }.map { |key, value| "#{key}=#{value}\0" }.join + "\0")
|
|
181
|
+
.encode("UTF-16LE").b
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def wide(text) = text.encode("UTF-16LE").b + "\0\0"
|
|
185
|
+
|
|
186
|
+
def coordinate(columns, rows)
|
|
187
|
+
unless columns.is_a?(Integer) && rows.is_a?(Integer) && columns.between?(1, 32_767) && rows.between?(1, 32_767)
|
|
188
|
+
raise ArgumentError, "invalid terminal size"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
[columns, rows].pack("s2").unpack1("l")
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def pipe
|
|
195
|
+
read_handle, write_handle = [0].pack("J"), [0].pack("J")
|
|
196
|
+
check(function(:CreatePipe, [P, P, P, U], I).call(read_handle, write_handle, 0, 0), "CreatePipe")
|
|
197
|
+
[read_handle.unpack1("J"), write_handle.unpack1("J")]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def start_reader
|
|
201
|
+
@reader = Thread.new do
|
|
202
|
+
loop do
|
|
203
|
+
bytes, count = "\0".b * 65_536, [0].pack("I")
|
|
204
|
+
ok = function(:ReadFile, [P, P, U, P, P], I, need_gvl: false).call(
|
|
205
|
+
@output_read, bytes, bytes.bytesize, count, 0
|
|
206
|
+
)
|
|
207
|
+
break if ok.zero? || count.unpack1("I").zero?
|
|
208
|
+
|
|
209
|
+
@output << bytes.byteslice(0, count.unpack1("I"))
|
|
210
|
+
end
|
|
211
|
+
ensure
|
|
212
|
+
@output << nil
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def close_console
|
|
217
|
+
return unless @console
|
|
218
|
+
|
|
219
|
+
function(:ClosePseudoConsole, [P], V, need_gvl: false).call(@console)
|
|
220
|
+
@console = nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def release
|
|
224
|
+
safely { close_handle(@input_write) }
|
|
225
|
+
safely { close_console }
|
|
226
|
+
@reader&.join(2)
|
|
227
|
+
safely { close_handle(@output_read) }
|
|
228
|
+
safely { close_handle(@process) }
|
|
229
|
+
@input_write = @output_read = @process = nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def safely
|
|
233
|
+
yield
|
|
234
|
+
rescue StandardError
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def close_handle(handle)
|
|
239
|
+
function(:CloseHandle, [P], I).call(handle) if handle && !handle.zero?
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def check(result, name)
|
|
243
|
+
raise SystemCallError.new(name, Fiddle.last_error) if result.zero?
|
|
244
|
+
|
|
245
|
+
result
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def check_hresult(result)
|
|
249
|
+
raise Error, "ConPTY failure 0x#{(result & 0xffffffff).to_s(16)}" unless (result & 0x80000000).zero?
|
|
250
|
+
|
|
251
|
+
result
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
data/lib/tarazed.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tarazed/version"
|
|
4
|
+
require_relative "tarazed/cell"
|
|
5
|
+
require_relative "tarazed/scrollback"
|
|
6
|
+
require_relative "tarazed/grid"
|
|
7
|
+
require_relative "tarazed/vt"
|
|
8
|
+
require_relative "tarazed/pty"
|
|
9
|
+
|
|
10
|
+
module Tarazed
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
end
|
data/sig/tarazed.rbs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module Tarazed
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
type color = Integer | [Integer, Integer, Integer] | nil
|
|
8
|
+
type attributes = Hash[Symbol, bool | Integer | [Integer, Integer, Integer]]
|
|
9
|
+
type mouse_action = :press | :move | :release
|
|
10
|
+
|
|
11
|
+
class Cell < Struct[untyped]
|
|
12
|
+
attr_accessor text: String
|
|
13
|
+
attr_accessor width: Integer
|
|
14
|
+
attr_accessor foreground: color
|
|
15
|
+
attr_accessor background: color
|
|
16
|
+
attr_accessor attributes: attributes
|
|
17
|
+
attr_accessor hyperlink: String?
|
|
18
|
+
|
|
19
|
+
def self.new: (text: String, width: Integer, ?foreground: color, ?background: color,
|
|
20
|
+
attributes: attributes, ?hyperlink: String?) -> instance
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Scrollback
|
|
24
|
+
include Enumerable[Array[Cell]]
|
|
25
|
+
|
|
26
|
+
attr_reader limit: Integer
|
|
27
|
+
|
|
28
|
+
def initialize: (Integer limit) -> void
|
|
29
|
+
def clear: () -> Array[Array[Cell]]
|
|
30
|
+
def length: () -> Integer
|
|
31
|
+
alias size length
|
|
32
|
+
def []: (Integer index) -> Array[Cell]?
|
|
33
|
+
def each: () -> Enumerator[Array[Cell], Array[Array[Cell]]]
|
|
34
|
+
| () { (Array[Cell]) -> void } -> Array[Array[Cell]]
|
|
35
|
+
def push: (Array[Cell] cells) -> Array[Array[Cell]]?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Grid
|
|
39
|
+
attr_reader columns: Integer
|
|
40
|
+
attr_reader rows: Integer
|
|
41
|
+
attr_reader cells: Array[Array[Cell]]
|
|
42
|
+
attr_reader scrollback: Scrollback
|
|
43
|
+
attr_reader cursor_x: Integer
|
|
44
|
+
attr_reader cursor_y: Integer
|
|
45
|
+
attr_reader scroll_top: Integer
|
|
46
|
+
attr_reader scroll_bottom: Integer
|
|
47
|
+
attr_accessor foreground: color
|
|
48
|
+
attr_accessor background: color
|
|
49
|
+
attr_accessor attributes: attributes
|
|
50
|
+
attr_accessor hyperlink: String?
|
|
51
|
+
attr_accessor autowrap: bool
|
|
52
|
+
attr_accessor insert_mode: bool
|
|
53
|
+
attr_accessor origin_mode: bool
|
|
54
|
+
attr_accessor cursor_visible: bool
|
|
55
|
+
|
|
56
|
+
def initialize: (?columns: Integer, ?rows: Integer, ?scrollback: Integer) -> void
|
|
57
|
+
def reset: () -> void
|
|
58
|
+
def []: (Integer row, ?nil column) -> Array[Cell]?
|
|
59
|
+
| (Integer row, Integer column) -> Cell?
|
|
60
|
+
def alternate?: () -> bool
|
|
61
|
+
def lines: () -> Array[String]
|
|
62
|
+
def text: () -> String
|
|
63
|
+
def put: (String char) -> void
|
|
64
|
+
def put_ascii: (String text) -> void
|
|
65
|
+
def self.width: (String char) -> Integer
|
|
66
|
+
def move: (?x: Integer, ?y: Integer, ?relative: bool) -> void
|
|
67
|
+
def position: (Integer row, Integer column) -> void
|
|
68
|
+
def carriage_return: () -> void
|
|
69
|
+
def backspace: () -> void
|
|
70
|
+
def linefeed: () -> void
|
|
71
|
+
def reverse_index: () -> void
|
|
72
|
+
def tab: (?Integer count, ?backward: bool) -> void
|
|
73
|
+
def tab_set: () -> Array[Integer]
|
|
74
|
+
def tab_clear: (?all: bool) -> void
|
|
75
|
+
def margins: (?Integer top, ?Integer bottom) -> void
|
|
76
|
+
def scroll_up: (?Integer count) -> void
|
|
77
|
+
def scroll_down: (?Integer count) -> void
|
|
78
|
+
def erase_display: (?Integer mode) -> void
|
|
79
|
+
def erase_line: (?Integer mode) -> void
|
|
80
|
+
def erase_characters: (?Integer count) -> void
|
|
81
|
+
def insert_characters: (?Integer count) -> void
|
|
82
|
+
def delete_characters: (?Integer count) -> void
|
|
83
|
+
def insert_lines: (?Integer count) -> void
|
|
84
|
+
def delete_lines: (?Integer count) -> void
|
|
85
|
+
def save_cursor: () -> void
|
|
86
|
+
def restore_cursor: () -> void
|
|
87
|
+
def alternate: (bool enable, ?save: bool) -> void
|
|
88
|
+
def resize: (columns: Integer, rows: Integer) -> void
|
|
89
|
+
def selection: ([Integer, Integer] start, [Integer, Integer] finish, ?history: bool) -> String
|
|
90
|
+
def links: (Integer row) -> Array[{url: String, column: Integer, end_column: Integer}]
|
|
91
|
+
def file_paths: (Integer row, ?cwd: String) -> Array[{path: String, line: Integer, column: Integer}]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class VT
|
|
95
|
+
DEC_GRAPHICS: Hash[String, String]
|
|
96
|
+
|
|
97
|
+
attr_reader grid: Grid
|
|
98
|
+
attr_reader title: String?
|
|
99
|
+
attr_reader cwd: String?
|
|
100
|
+
attr_reader modes: Hash[Integer | Symbol, bool]
|
|
101
|
+
attr_reader replies: Array[String]
|
|
102
|
+
attr_reader bell_count: Integer
|
|
103
|
+
|
|
104
|
+
def initialize: (?Grid grid) ?{ (String) -> void } -> void
|
|
105
|
+
def feed: (String bytes) -> self
|
|
106
|
+
def paste: (String text) -> String
|
|
107
|
+
def mouse: (button: Symbol | Integer | nil, column: Integer, row: Integer, ?action: mouse_action,
|
|
108
|
+
?shift: bool, ?alt: bool, ?control: bool) -> String
|
|
109
|
+
def key: (String | Symbol name, ?shift: bool, ?alt: bool, ?control: bool) -> String
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class PTY
|
|
113
|
+
attr_reader reader: IO?
|
|
114
|
+
attr_reader writer: IO?
|
|
115
|
+
attr_reader pid: Integer
|
|
116
|
+
attr_reader grid: Grid
|
|
117
|
+
attr_reader vt: VT
|
|
118
|
+
attr_reader status: Process::Status?
|
|
119
|
+
attr_reader initial_cwd: String
|
|
120
|
+
attr_reader command_name: String
|
|
121
|
+
|
|
122
|
+
def initialize: (?command: String | Array[String], ?cwd: String, ?columns: Integer, ?rows: Integer,
|
|
123
|
+
?env: Hash[String, String?], ?scrollback: Integer, ?queue_limit_bytes: Integer) -> void
|
|
124
|
+
def read: (?timeout: Numeric, ?max_bytes: Integer, ?max_seconds: Numeric?) -> String?
|
|
125
|
+
def pending?: () -> bool
|
|
126
|
+
def write: (String bytes) -> Integer
|
|
127
|
+
def paste: (String text) -> Integer
|
|
128
|
+
def key: (String | Symbol name, ?shift: bool, ?alt: bool, ?control: bool) -> Integer
|
|
129
|
+
def mouse: (button: Symbol | Integer | nil, column: Integer, row: Integer, ?action: mouse_action,
|
|
130
|
+
?shift: bool, ?alt: bool, ?control: bool) -> Integer
|
|
131
|
+
def resize: (columns: Integer, rows: Integer) -> self
|
|
132
|
+
def signal: (?String | Integer name) -> (Integer | false)
|
|
133
|
+
def alive?: () -> bool
|
|
134
|
+
def busy?: () -> bool
|
|
135
|
+
def foreground_process_name: (?now: Numeric) -> String?
|
|
136
|
+
def close: () -> self
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tarazed
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fiddle
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: unicode-display_width
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.2'
|
|
40
|
+
email:
|
|
41
|
+
- t.yudai92@gmail.com
|
|
42
|
+
executables: []
|
|
43
|
+
extensions: []
|
|
44
|
+
extra_rdoc_files: []
|
|
45
|
+
files:
|
|
46
|
+
- CHANGELOG.md
|
|
47
|
+
- LICENSE.txt
|
|
48
|
+
- README.md
|
|
49
|
+
- docs/adr/000-template.md
|
|
50
|
+
- docs/adr/001-terminal-core-boundary.md
|
|
51
|
+
- docs/adr/README.md
|
|
52
|
+
- lib/tarazed.rb
|
|
53
|
+
- lib/tarazed/cell.rb
|
|
54
|
+
- lib/tarazed/grid.rb
|
|
55
|
+
- lib/tarazed/pty.rb
|
|
56
|
+
- lib/tarazed/scrollback.rb
|
|
57
|
+
- lib/tarazed/version.rb
|
|
58
|
+
- lib/tarazed/vt.rb
|
|
59
|
+
- lib/tarazed/windows/conpty.rb
|
|
60
|
+
- sig/tarazed.rbs
|
|
61
|
+
homepage: https://github.com/noxdea/tarazed
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
source_code_uri: https://github.com/noxdea/tarazed
|
|
66
|
+
changelog_uri: https://github.com/noxdea/tarazed/blob/main/CHANGELOG.md
|
|
67
|
+
allowed_push_host: https://rubygems.org
|
|
68
|
+
rubygems_mfa_required: 'true'
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '3.1'
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 4.0.16
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: A pure Ruby terminal emulator core
|
|
86
|
+
test_files: []
|