tarazed 0.1.0 → 0.2.1
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 +11 -0
- data/README.md +71 -8
- data/assets/shell-integration/tarazed.bash +44 -0
- data/assets/shell-integration/tarazed.fish +27 -0
- data/assets/shell-integration/tarazed.zsh +36 -0
- data/docs/adr/001-terminal-core-boundary.md +2 -2
- data/docs/adr/002-shell-integration.md +27 -0
- data/docs/adr/README.md +3 -0
- data/lib/tarazed/grid.rb +17 -5
- data/lib/tarazed/pty.rb +6 -4
- data/lib/tarazed/scrollback.rb +8 -2
- data/lib/tarazed/session.rb +21 -0
- data/lib/tarazed/shell_integration.rb +18 -0
- data/lib/tarazed/version.rb +1 -1
- data/lib/tarazed/vt.rb +95 -6
- data/lib/tarazed/windows/conpty.rb +30 -2
- data/lib/tarazed.rb +45 -0
- data/sig/tarazed.rbs +50 -3
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7770f7ce8eac23698c2c294317d339ac24135b86b9f8203ee43f63229c1c4d4
|
|
4
|
+
data.tar.gz: d3aecd412f31417f961d10e07b683c07d2feeea3d5596b8d6218fde0a01e117d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c6164054701b25e94dac45e3cd5359126f591d450a9ad1c817b9e2537c3117f77286c23354cc9a1b5585d7e991ca919f29085f5fcbab1d414305e97358f0e2a
|
|
7
|
+
data.tar.gz: 660218a61bf1017d59d90219d9beb040847ccaa8154b2f5dabdca23a0dff72d30a92e4537619a32808dec6d9ea65d64b5c67281e618f389d7978d3ab9c38d7d5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.2.1 - 2026-09-16
|
|
6
|
+
|
|
7
|
+
- Report and cache natural Windows ConPTY process exit status through the cross-platform `PTY#status` API.
|
|
8
|
+
|
|
9
|
+
## 0.2.0 - 2026-09-16
|
|
10
|
+
|
|
11
|
+
- Track OSC 133 command boundaries, input, output rows, exit status, and OSC 7 working directories in bounded history.
|
|
12
|
+
- Add a pump-oriented `Tarazed::Session` API and shell integration snippets for bash, zsh, and fish.
|
|
13
|
+
|
|
3
14
|
## 0.1.0 - 2026-09-16
|
|
4
15
|
|
|
5
16
|
- Initial release.
|
data/README.md
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">Tarazed</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Pure Ruby terminal emulator core</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://rubygems.org/gems/tarazed"><img src="https://img.shields.io/gem/v/tarazed.svg?colorB=319e8c" alt="Gem Version"></a>
|
|
9
|
+
<a href="https://rubygems.org/gems/tarazed"><img src="https://img.shields.io/gem/dt/tarazed.svg" alt="Downloads"></a>
|
|
10
|
+
<img src="https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg" alt="Ruby Version">
|
|
11
|
+
<a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="#features">Features</a> ·
|
|
16
|
+
<a href="#installation">Installation</a> ·
|
|
17
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
18
|
+
<a href="#pty-sessions">PTY Sessions</a> ·
|
|
19
|
+
<a href="#command-history">Command History</a> ·
|
|
20
|
+
<a href="#shell-integration">Shell Integration</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
---
|
|
2
24
|
|
|
3
25
|
Tarazed is a pure Ruby terminal emulator core. It provides the
|
|
4
26
|
terminal cell grid, scrollback, VT parser, keyboard/mouse encoding, and a
|
|
5
27
|
POSIX or Windows ConPTY session without depending on an editor or UI toolkit.
|
|
6
28
|
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- Unicode-aware cell grid with bounded scrollback
|
|
32
|
+
- Chunk-safe VT parsing for terminal controls, input, links, and selection
|
|
33
|
+
- Keyboard, mouse, bracketed-paste, and terminal-reply encoding
|
|
34
|
+
- POSIX PTY and 64-bit Windows ConPTY sessions behind one API
|
|
35
|
+
- OSC 133 command history and OSC 7 working-directory tracking
|
|
36
|
+
- Packaged shell integration for bash, zsh, and fish
|
|
37
|
+
|
|
7
38
|
## Installation
|
|
8
39
|
|
|
9
40
|
```bash
|
|
@@ -12,7 +43,9 @@ bundle add tarazed
|
|
|
12
43
|
|
|
13
44
|
Or run `gem install tarazed`.
|
|
14
45
|
|
|
15
|
-
|
|
46
|
+
Tarazed supports Ruby 3.1 and later.
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
16
49
|
|
|
17
50
|
Feed arbitrary byte chunks into a `VT`; incomplete UTF-8 and control sequences
|
|
18
51
|
are retained until the next chunk.
|
|
@@ -26,6 +59,8 @@ terminal.feed("\e[32mready\e[0m\r\n")
|
|
|
26
59
|
puts screen.text
|
|
27
60
|
```
|
|
28
61
|
|
|
62
|
+
## PTY Sessions
|
|
63
|
+
|
|
29
64
|
`Tarazed::PTY` owns a child process and feeds its output into the same grid.
|
|
30
65
|
It uses a POSIX PTY on macOS and Linux and ConPTY on 64-bit Windows:
|
|
31
66
|
|
|
@@ -33,18 +68,46 @@ It uses a POSIX PTY on macOS and Linux and ConPTY on 64-bit Windows:
|
|
|
33
68
|
terminal = Tarazed::PTY.new(command: ["/bin/sh"], columns: 80, rows: 24)
|
|
34
69
|
terminal.write("printf 'hello\\n'\r")
|
|
35
70
|
terminal.read(timeout: 0.1)
|
|
71
|
+
puts terminal.status.exitstatus if !terminal.alive? && terminal.status
|
|
36
72
|
terminal.close
|
|
37
73
|
```
|
|
38
74
|
|
|
39
75
|
ConPTY requires a supported 64-bit Windows release and does not fall back to a
|
|
40
|
-
pipe-only console.
|
|
41
|
-
and `
|
|
76
|
+
pipe-only console. After natural process exit, `status` provides `exited?`,
|
|
77
|
+
`exitstatus`, `success?`, and `termsig` on every platform.
|
|
78
|
+
|
|
79
|
+
## Command History
|
|
80
|
+
|
|
81
|
+
`Tarazed::Session` adds a pump-oriented API and bounded OSC 133 command
|
|
82
|
+
history. Command rows and output ranges use stable absolute history rows, and
|
|
83
|
+
OSC 7 updates the session working directory:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
session = Tarazed::Session.new(command: ["/bin/bash"], columns: 80, rows: 24)
|
|
87
|
+
session.pump(timeout: 0.05)
|
|
88
|
+
session.commands.each do |command|
|
|
89
|
+
puts "#{command.exit_status}: #{command.input} (#{command.cwd})"
|
|
90
|
+
end
|
|
91
|
+
session.close
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Shell Integration
|
|
95
|
+
|
|
96
|
+
Shell integration snippets for bash, zsh, and fish are packaged with the gem.
|
|
97
|
+
An embedding application can inject one into a new interactive shell with
|
|
98
|
+
`Tarazed::ShellIntegration.read(:bash)` (or `:zsh` / `:fish`). The snippets
|
|
99
|
+
emit prompt, input, execution, and completion markers without changing the
|
|
100
|
+
visible prompt.
|
|
42
101
|
|
|
43
102
|
## Development
|
|
44
103
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
104
|
+
```bash
|
|
105
|
+
bundle install
|
|
106
|
+
bundle exec rake test
|
|
107
|
+
bundle exec rbs -I sig validate
|
|
108
|
+
BUDGET=1 bundle exec rake bench
|
|
109
|
+
gem build --strict tarazed.gemspec
|
|
110
|
+
```
|
|
48
111
|
|
|
49
112
|
## Contributing
|
|
50
113
|
|
|
@@ -52,4 +115,4 @@ Bug reports and pull requests are welcome at https://github.com/noxdea/tarazed.
|
|
|
52
115
|
|
|
53
116
|
## License
|
|
54
117
|
|
|
55
|
-
|
|
118
|
+
Tarazed is available under the [MIT License](LICENSE.txt).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Tarazed shell integration for bash 3.2 and newer.
|
|
2
|
+
if [[ -z ${TARAZED_SHELL_INTEGRATION-} ]]; then
|
|
3
|
+
TARAZED_SHELL_INTEGRATION=1
|
|
4
|
+
|
|
5
|
+
__tarazed_urlencode() {
|
|
6
|
+
local LC_ALL=C value=$1 output= character code hex index=0
|
|
7
|
+
while (( index < ${#value} )); do
|
|
8
|
+
character=${value:index:1}
|
|
9
|
+
case $character in
|
|
10
|
+
[a-zA-Z0-9.~_/-]) output=$output$character ;;
|
|
11
|
+
*)
|
|
12
|
+
printf -v code '%d' "'$character"
|
|
13
|
+
printf -v hex '%%%02X' "$((code & 255))"
|
|
14
|
+
output=$output$hex
|
|
15
|
+
;;
|
|
16
|
+
esac
|
|
17
|
+
(( index += 1 ))
|
|
18
|
+
done
|
|
19
|
+
printf '%s' "$output"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
__tarazed_prompt_command() {
|
|
23
|
+
local command_status=$1
|
|
24
|
+
printf '\e]133;D;%s\a\e]7;file://%s%s\e\\\e]133;A\e\\' \
|
|
25
|
+
"$command_status" "${HOSTNAME-}" "$(__tarazed_urlencode "$PWD")"
|
|
26
|
+
__tarazed_command_active=0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
PROMPT_COMMAND="__tarazed_prompt_status=\$?${PROMPT_COMMAND:+;$PROMPT_COMMAND};__tarazed_prompt_command \"\$__tarazed_prompt_status\""
|
|
30
|
+
PS1="${PS1-}\[\e]133;B\a\]"
|
|
31
|
+
|
|
32
|
+
if (( BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4) )); then
|
|
33
|
+
PS0="${PS0-}\[\e]133;C\a\]"
|
|
34
|
+
else
|
|
35
|
+
__tarazed_command_active=1
|
|
36
|
+
__tarazed_preexec() {
|
|
37
|
+
if (( ! __tarazed_command_active )); then
|
|
38
|
+
__tarazed_command_active=1
|
|
39
|
+
printf '\e]133;C\e\\'
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
trap '__tarazed_preexec' DEBUG
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Tarazed shell integration for fish.
|
|
2
|
+
if not set -q TARAZED_SHELL_INTEGRATION
|
|
3
|
+
set -g TARAZED_SHELL_INTEGRATION 1
|
|
4
|
+
|
|
5
|
+
function __tarazed_cwd
|
|
6
|
+
set -l encoded (string escape --style=url -- $PWD)
|
|
7
|
+
set encoded (string replace -r '^%2[Ff]' '' -- $encoded)
|
|
8
|
+
printf '\e]7;file:///%s\e\\' $encoded
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
function __tarazed_preexec --on-event fish_preexec
|
|
12
|
+
printf '\e]133;C\e\\'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
function __tarazed_postexec --on-event fish_postexec
|
|
16
|
+
set -l command_status $status
|
|
17
|
+
printf '\e]133;D;%s\a' $command_status
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
functions -c fish_prompt __tarazed_original_fish_prompt
|
|
21
|
+
function fish_prompt
|
|
22
|
+
__tarazed_cwd
|
|
23
|
+
printf '\e]133;A\e\\'
|
|
24
|
+
__tarazed_original_fish_prompt
|
|
25
|
+
printf '\e]133;B\e\\'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Tarazed shell integration for zsh.
|
|
2
|
+
if [[ -z ${TARAZED_SHELL_INTEGRATION-} ]]; then
|
|
3
|
+
typeset -g TARAZED_SHELL_INTEGRATION=1
|
|
4
|
+
|
|
5
|
+
__tarazed_urlencode() {
|
|
6
|
+
local LC_ALL=C value=$1 output= character code hex
|
|
7
|
+
integer index=1
|
|
8
|
+
while (( index <= ${#value} )); do
|
|
9
|
+
character=${value[index]}
|
|
10
|
+
case $character in
|
|
11
|
+
[a-zA-Z0-9.~_/-]) output+=$character ;;
|
|
12
|
+
*)
|
|
13
|
+
printf -v code '%d' "'$character"
|
|
14
|
+
printf -v hex '%%%02X' "$((code & 255))"
|
|
15
|
+
output+=$hex
|
|
16
|
+
;;
|
|
17
|
+
esac
|
|
18
|
+
(( index += 1 ))
|
|
19
|
+
done
|
|
20
|
+
print -rn -- "$output"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
__tarazed_precmd() {
|
|
24
|
+
local command_status=$?
|
|
25
|
+
printf '\e]133;D;%s\a\e]7;file://%s%s\e\\\e]133;A\e\\' \
|
|
26
|
+
"$command_status" "${HOST-}" "$(__tarazed_urlencode "$PWD")"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
__tarazed_preexec() {
|
|
30
|
+
printf '\e]133;C\e\\'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
precmd_functions=(__tarazed_precmd ${precmd_functions:#__tarazed_precmd})
|
|
34
|
+
preexec_functions=(__tarazed_preexec ${preexec_functions:#__tarazed_preexec})
|
|
35
|
+
PS1="${PS1-}"$'%{\e]133;B\a%}'
|
|
36
|
+
fi
|
|
@@ -17,8 +17,8 @@ and Linux or a Fiddle-based ConPTY backend on 64-bit Windows. It depends
|
|
|
17
17
|
directly on `unicode-display_width`; scrollback uses a bounded Array because
|
|
18
18
|
the default 10,000-row limit does not justify a persistent tree dependency.
|
|
19
19
|
|
|
20
|
-
Higher-level screen
|
|
21
|
-
|
|
20
|
+
Higher-level screen and parser APIs remain outside the extracted API. Session
|
|
21
|
+
and shell integration are deferred to ADR 002.
|
|
22
22
|
|
|
23
23
|
## Consequences
|
|
24
24
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ADR 002: Derive shell history from terminal protocols
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-09-15
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Embedders need command boundaries, exit status, and working directories without
|
|
9
|
+
parsing shell prompts or depending on a particular shell. Terminal output is an
|
|
10
|
+
untrusted, chunked byte stream, and retained history must stay bounded.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
The existing VT parser consumes OSC 133 and OSC 7 alongside other bounded OSC
|
|
15
|
+
sequences. Completed commands are immutable values retained in a count-bounded
|
|
16
|
+
history. Rows are the grid's cumulative history coordinates, so later
|
|
17
|
+
scrollback eviction does not change recorded ranges. Malformed markers and
|
|
18
|
+
working-directory values do not enter the model.
|
|
19
|
+
|
|
20
|
+
`Session` remains a thin PTY facade. Shell-specific behavior stays in packaged
|
|
21
|
+
bash, zsh, and fish snippets instead of the emulator core.
|
|
22
|
+
|
|
23
|
+
## Consequences
|
|
24
|
+
|
|
25
|
+
Applications can navigate command output consistently on POSIX and ConPTY.
|
|
26
|
+
Command input is reconstructed from retained terminal cells and is therefore
|
|
27
|
+
limited by scrollback and a 64 KiB per-command cap.
|
data/docs/adr/README.md
CHANGED
data/lib/tarazed/grid.rb
CHANGED
|
@@ -33,6 +33,8 @@ module Tarazed
|
|
|
33
33
|
def alternate? = !@alternate.nil?
|
|
34
34
|
def lines = cells.map { |row| row.map(&:text).join.rstrip }
|
|
35
35
|
def text = lines.join("\n")
|
|
36
|
+
def history_row = scrollback.total + cursor_y
|
|
37
|
+
def wrap_pending? = @wrap_pending
|
|
36
38
|
|
|
37
39
|
def put(char)
|
|
38
40
|
width = self.class.width(char)
|
|
@@ -45,6 +47,7 @@ module Tarazed
|
|
|
45
47
|
width = [self.class.width(joined), columns].min
|
|
46
48
|
if previous_x + width > columns && autowrap
|
|
47
49
|
cells[cursor_y][previous_x] = blank
|
|
50
|
+
mark_wrapped(cells[cursor_y])
|
|
48
51
|
carriage_return
|
|
49
52
|
linefeed
|
|
50
53
|
return put(joined)
|
|
@@ -66,6 +69,7 @@ module Tarazed
|
|
|
66
69
|
width = 1 if width > columns
|
|
67
70
|
if @wrap_pending || (width == 2 && cursor_x == columns - 1)
|
|
68
71
|
if autowrap
|
|
72
|
+
mark_wrapped(cells[cursor_y])
|
|
69
73
|
@cursor_x = 0
|
|
70
74
|
linefeed
|
|
71
75
|
elsif width == 2
|
|
@@ -104,6 +108,8 @@ module Tarazed
|
|
|
104
108
|
end
|
|
105
109
|
row.concat(Array.new(columns - row.length) { blank })
|
|
106
110
|
end
|
|
111
|
+
@cells.each_with_index { |row, index| mark_wrapped(row) if index < @cells.length - 1 }
|
|
112
|
+
scrollback.advance(complete_rows - retained_complete_rows)
|
|
107
113
|
@cursor_x = remainder.zero? ? columns - 1 : remainder
|
|
108
114
|
@cursor_y = rows - 1
|
|
109
115
|
@wrap_pending = remainder.zero?
|
|
@@ -283,10 +289,11 @@ module Tarazed
|
|
|
283
289
|
validate_dimensions(columns, rows)
|
|
284
290
|
previous_columns = @columns
|
|
285
291
|
@columns, @rows = columns, rows
|
|
292
|
+
primary = alternate? ? @alternate : cells
|
|
286
293
|
[cells, @alternate].compact.each do |screen|
|
|
287
294
|
while screen.length > rows
|
|
288
295
|
removed = screen.shift
|
|
289
|
-
scrollback.push(removed)
|
|
296
|
+
scrollback.push(removed) if screen.equal?(primary)
|
|
290
297
|
@cursor_y -= 1 if screen.equal?(cells)
|
|
291
298
|
end
|
|
292
299
|
screen << blank_row while screen.length < rows
|
|
@@ -307,13 +314,17 @@ module Tarazed
|
|
|
307
314
|
def selection(start, finish, history: false)
|
|
308
315
|
source = history ? scrollback.to_a + cells : cells
|
|
309
316
|
start, finish = finish, start if ([start[1], start[0]] <=> [finish[1], finish[0]]) == 1
|
|
310
|
-
(start[1]..finish[1]).map do |row|
|
|
311
|
-
next "" unless source[row]
|
|
317
|
+
parts = (start[1]..finish[1]).map do |row|
|
|
318
|
+
next ["", false] unless source[row]
|
|
312
319
|
|
|
313
320
|
first = row == start[1] ? start[0] : 0
|
|
314
321
|
last = row == finish[1] ? finish[0] : columns
|
|
315
|
-
source[row][first...last].to_a.map(&:text).join.rstrip
|
|
316
|
-
end
|
|
322
|
+
[source[row][first...last].to_a.map(&:text).join.rstrip, source[row].instance_variable_get(:@wrapped)]
|
|
323
|
+
end
|
|
324
|
+
parts.each_with_index.each_with_object(+"") do |((part, _), index), text|
|
|
325
|
+
text << "\n" if index.positive? && !parts[index - 1][1]
|
|
326
|
+
text << part
|
|
327
|
+
end
|
|
317
328
|
end
|
|
318
329
|
|
|
319
330
|
def links(row)
|
|
@@ -359,6 +370,7 @@ module Tarazed
|
|
|
359
370
|
|
|
360
371
|
def blank = Cell.new(text: " ", width: 1, foreground: foreground, background: background, attributes: {}.freeze)
|
|
361
372
|
def blank_row = Array.new(columns) { blank }
|
|
373
|
+
def mark_wrapped(row) = row.instance_variable_set(:@wrapped, true)
|
|
362
374
|
|
|
363
375
|
def clear_wide(row, column)
|
|
364
376
|
return unless column.between?(0, columns - 1)
|
data/lib/tarazed/pty.rb
CHANGED
|
@@ -5,10 +5,10 @@ require "shellwords"
|
|
|
5
5
|
|
|
6
6
|
module Tarazed
|
|
7
7
|
class PTY
|
|
8
|
-
attr_reader :reader, :writer, :pid, :grid, :vt, :
|
|
8
|
+
attr_reader :reader, :writer, :pid, :grid, :vt, :initial_cwd, :command_name
|
|
9
9
|
|
|
10
10
|
def initialize(command: ENV.fetch("SHELL", "/bin/sh"), cwd: Dir.pwd, columns: 80, rows: 24, env: {},
|
|
11
|
-
scrollback: 10_000, queue_limit_bytes: 8_388_608)
|
|
11
|
+
scrollback: 10_000, queue_limit_bytes: 8_388_608, command_history_limit: 1_000, on_command: nil)
|
|
12
12
|
unless queue_limit_bytes.is_a?(Integer) && queue_limit_bytes.positive?
|
|
13
13
|
raise ArgumentError, "terminal queue limit must be a positive integer"
|
|
14
14
|
end
|
|
@@ -21,7 +21,7 @@ module Tarazed
|
|
|
21
21
|
command = nil if command == "/bin/sh"
|
|
22
22
|
@native = Windows::ConPTY.new(command: command, cwd: cwd, columns: columns, rows: rows, env: env)
|
|
23
23
|
@pid = @native.pid
|
|
24
|
-
@vt = VT.new(grid) { |bytes| write(bytes) }
|
|
24
|
+
@vt = VT.new(grid, command_limit: command_history_limit, on_command: on_command) { |bytes| write(bytes) }
|
|
25
25
|
return
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -34,7 +34,7 @@ module Tarazed
|
|
|
34
34
|
@reader.binmode
|
|
35
35
|
@writer.binmode
|
|
36
36
|
@writer.sync = true
|
|
37
|
-
@vt = VT.new(grid) { |bytes| write(bytes) }
|
|
37
|
+
@vt = VT.new(grid, command_limit: command_history_limit, on_command: on_command) { |bytes| write(bytes) }
|
|
38
38
|
resize(columns: columns, rows: rows)
|
|
39
39
|
@queue, @queue_bytes, @queue_limit_bytes = [], 0, queue_limit_bytes
|
|
40
40
|
@queue_lock, @queue_ready = Mutex.new, ConditionVariable.new
|
|
@@ -124,6 +124,8 @@ module Tarazed
|
|
|
124
124
|
@foreground_name = nil
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
+
def status = @native ? @native.status : @status
|
|
128
|
+
|
|
127
129
|
def signal(name = "INT")
|
|
128
130
|
return @native.write("\x03") if @native && name == "INT"
|
|
129
131
|
if @native
|
data/lib/tarazed/scrollback.rb
CHANGED
|
@@ -4,12 +4,13 @@ module Tarazed
|
|
|
4
4
|
class Scrollback
|
|
5
5
|
include Enumerable
|
|
6
6
|
|
|
7
|
-
attr_reader :limit
|
|
7
|
+
attr_reader :limit, :total
|
|
8
8
|
|
|
9
9
|
def initialize(limit)
|
|
10
10
|
raise ArgumentError, "scrollback limit must be nonnegative" unless limit.is_a?(Integer) && limit >= 0
|
|
11
11
|
|
|
12
12
|
@limit = limit
|
|
13
|
+
@total = 0
|
|
13
14
|
clear
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -20,10 +21,15 @@ module Tarazed
|
|
|
20
21
|
def each(&block) = block ? @rows.each(&block) : enum_for(__method__)
|
|
21
22
|
|
|
22
23
|
def push(cells)
|
|
24
|
+
advance(1)
|
|
23
25
|
return if limit.zero?
|
|
24
26
|
|
|
25
27
|
@rows.shift if @rows.length == limit
|
|
26
|
-
|
|
28
|
+
row = cells.map { |cell| cell.dup.tap { |copy| copy.text = copy.text.dup.freeze }.freeze }
|
|
29
|
+
row.instance_variable_set(:@wrapped, true) if cells.instance_variable_get(:@wrapped)
|
|
30
|
+
@rows << row.freeze
|
|
27
31
|
end
|
|
32
|
+
|
|
33
|
+
def advance(count) = @total += count
|
|
28
34
|
end
|
|
29
35
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarazed
|
|
4
|
+
class Session < PTY
|
|
5
|
+
def initialize(command:, env: {}, cwd: Dir.pwd, columns: 80, rows: 24, scrollback_limit: 10_000,
|
|
6
|
+
command_history_limit: 1_000, queue_limit_bytes: 8_388_608, on_command: nil)
|
|
7
|
+
super(command: command, env: env, cwd: cwd, columns: columns, rows: rows, scrollback: scrollback_limit,
|
|
8
|
+
command_history_limit: command_history_limit, queue_limit_bytes: queue_limit_bytes, on_command: on_command)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def screen = grid
|
|
12
|
+
def commands = vt.commands
|
|
13
|
+
def cwd = vt.cwd || initial_cwd
|
|
14
|
+
def input(bytes) = write(bytes)
|
|
15
|
+
|
|
16
|
+
def pump(timeout: 0)
|
|
17
|
+
bytes = read(timeout: timeout)
|
|
18
|
+
!bytes.nil? && !bytes.empty?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarazed
|
|
4
|
+
module ShellIntegration
|
|
5
|
+
NAMES = {bash: "tarazed.bash", zsh: "tarazed.zsh", fish: "tarazed.fish"}.freeze
|
|
6
|
+
private_constant :NAMES
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def path(shell)
|
|
11
|
+
name = File.basename(shell.to_s).to_sym
|
|
12
|
+
file = NAMES.fetch(name) { raise ArgumentError, "unsupported shell: #{shell}" }
|
|
13
|
+
File.expand_path("../../assets/shell-integration/#{file}", __dir__)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read(shell) = File.binread(path(shell))
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/tarazed/version.rb
CHANGED
data/lib/tarazed/vt.rb
CHANGED
|
@@ -11,9 +11,17 @@ module Tarazed
|
|
|
11
11
|
|
|
12
12
|
attr_reader :grid, :title, :cwd, :modes, :replies, :bell_count
|
|
13
13
|
|
|
14
|
-
def initialize(grid = Grid.new, &reply)
|
|
14
|
+
def initialize(grid = Grid.new, command_limit: 1_000, on_command: nil, &reply)
|
|
15
|
+
unless command_limit.is_a?(Integer) && command_limit >= 0
|
|
16
|
+
raise ArgumentError, "command history limit must be a nonnegative integer"
|
|
17
|
+
end
|
|
18
|
+
|
|
15
19
|
@grid = grid
|
|
16
20
|
@reply = reply
|
|
21
|
+
@on_command = on_command
|
|
22
|
+
@command_limit = command_limit
|
|
23
|
+
@commands = []
|
|
24
|
+
@next_command_id = 1
|
|
17
25
|
@replies = []
|
|
18
26
|
@input = +"".b
|
|
19
27
|
@state = :ground
|
|
@@ -24,6 +32,8 @@ module Tarazed
|
|
|
24
32
|
@bell_count = 0
|
|
25
33
|
end
|
|
26
34
|
|
|
35
|
+
def commands = @commands.dup.freeze
|
|
36
|
+
|
|
27
37
|
# The parser retains incomplete UTF-8 and control sequences between reads.
|
|
28
38
|
def feed(bytes)
|
|
29
39
|
@input << bytes.b
|
|
@@ -413,22 +423,101 @@ module Tarazed
|
|
|
413
423
|
end
|
|
414
424
|
|
|
415
425
|
def osc(sequence)
|
|
416
|
-
|
|
426
|
+
text = sequence.dup.force_encoding(Encoding::UTF_8)
|
|
427
|
+
valid_utf8 = text.valid_encoding?
|
|
428
|
+
command, payload = text.scrub.split(";", 2)
|
|
417
429
|
return unless payload
|
|
418
430
|
|
|
419
431
|
case command
|
|
420
432
|
when "0", "2" then @title = payload
|
|
421
|
-
when "7"
|
|
422
|
-
uri = URI.parse(payload)
|
|
423
|
-
@cwd = URI.decode_uri_component(uri.path) if uri.scheme == "file" && uri.path&.start_with?("/")
|
|
433
|
+
when "7" then update_cwd(payload) if valid_utf8
|
|
424
434
|
when "8"
|
|
425
435
|
_, url = payload.split(";", 2)
|
|
426
436
|
grid.hyperlink = url.nil? || url.empty? ? nil : url.freeze
|
|
437
|
+
when "133" then shell_marker(payload)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def update_cwd(payload)
|
|
442
|
+
match = /\Afile:\/\/([A-Za-z0-9._~%\-:\[\]]*)(\/.*)\z/.match(payload)
|
|
443
|
+
return unless match
|
|
444
|
+
|
|
445
|
+
path = URI.decode_uri_component(match[2])
|
|
446
|
+
return unless path.valid_encoding? && !path.match?(/[\x00-\x1f\x7f]/)
|
|
447
|
+
|
|
448
|
+
if Gem.win_platform?
|
|
449
|
+
drive_path = /\A\/[A-Za-z]:\//.match?(path)
|
|
450
|
+
path = path.delete_prefix("/") if drive_path
|
|
451
|
+
path = "//#{match[1]}#{path}" if !drive_path && !match[1].empty?
|
|
427
452
|
end
|
|
428
|
-
|
|
453
|
+
@cwd = path.freeze
|
|
454
|
+
rescue ArgumentError
|
|
429
455
|
nil
|
|
430
456
|
end
|
|
431
457
|
|
|
458
|
+
def shell_marker(payload)
|
|
459
|
+
case payload
|
|
460
|
+
when "A"
|
|
461
|
+
@pending_command = {id: @next_command_id, prompt_row: grid.history_row}
|
|
462
|
+
@next_command_id += 1
|
|
463
|
+
when "B"
|
|
464
|
+
if @pending_command && !@pending_command[:started_at]
|
|
465
|
+
@pending_command[:input_start] ||= grid.wrap_pending? ? [0, grid.history_row + 1] :
|
|
466
|
+
[grid.cursor_x, grid.history_row]
|
|
467
|
+
end
|
|
468
|
+
when "C"
|
|
469
|
+
start_command if @pending_command && !@pending_command[:started_at]
|
|
470
|
+
else
|
|
471
|
+
finish_command(payload.delete_prefix("D;")) if @pending_command && payload.start_with?("D;")
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def start_command
|
|
476
|
+
@pending_command[:input] = command_input(@pending_command[:input_start])
|
|
477
|
+
@pending_command[:output_start] = grid.history_row
|
|
478
|
+
@pending_command[:started_at] = Time.now.freeze
|
|
479
|
+
@pending_command[:cwd] = cwd&.dup&.freeze
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def finish_command(value)
|
|
483
|
+
return unless @pending_command[:started_at]
|
|
484
|
+
return unless /\A\d{1,10}\z/.match?(value)
|
|
485
|
+
|
|
486
|
+
status = value.to_i
|
|
487
|
+
return if status > 0xffffffff
|
|
488
|
+
|
|
489
|
+
pending = @pending_command
|
|
490
|
+
finished_at = Time.now.freeze
|
|
491
|
+
output_range = pending[:output_start] && (pending[:output_start]..grid.history_row).freeze
|
|
492
|
+
command = Command.new(id: pending[:id], prompt_row: pending[:prompt_row], input: (pending[:input] || "").freeze,
|
|
493
|
+
output_range: output_range, exit_status: status, started_at: pending[:started_at], finished_at: finished_at,
|
|
494
|
+
cwd: pending[:cwd] || cwd)
|
|
495
|
+
@pending_command = nil
|
|
496
|
+
@commands.shift if @commands.length == @command_limit && @command_limit.positive?
|
|
497
|
+
@commands << command if @command_limit.positive?
|
|
498
|
+
@on_command&.call(command)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def command_input(start)
|
|
502
|
+
return "" unless start
|
|
503
|
+
return "" if start[1] > grid.history_row
|
|
504
|
+
|
|
505
|
+
base = grid.scrollback.total - grid.scrollback.length
|
|
506
|
+
first = [start[1] - base, 0].max
|
|
507
|
+
last = grid.history_row - base
|
|
508
|
+
return "" if last.negative?
|
|
509
|
+
|
|
510
|
+
finish = grid.wrap_pending? ? grid.columns : grid.cursor_x
|
|
511
|
+
first_column = start[1] < base ? 0 : start[0]
|
|
512
|
+
input = grid.selection([first_column, first], [finish, last], history: true).sub(/\n+\z/, "")
|
|
513
|
+
if input.bytesize > 65_536
|
|
514
|
+
# ponytail: terminal cells are the source of truth; add explicit command metadata if 64 KiB commands matter.
|
|
515
|
+
input = input.byteslice(0, 65_536)
|
|
516
|
+
input = input.byteslice(0, input.bytesize - 1) until input.valid_encoding?
|
|
517
|
+
end
|
|
518
|
+
input
|
|
519
|
+
end
|
|
520
|
+
|
|
432
521
|
def dcs(sequence)
|
|
433
522
|
return unless sequence.start_with?("$q")
|
|
434
523
|
|
|
@@ -21,8 +21,23 @@ module Tarazed
|
|
|
21
21
|
class ConPTY
|
|
22
22
|
P, I, U, N, V = Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_UINT, Fiddle::TYPE_SIZE_T,
|
|
23
23
|
Fiddle::TYPE_VOID
|
|
24
|
+
STILL_ACTIVE = 259
|
|
24
25
|
private_constant :P, :I, :U, :N, :V
|
|
25
26
|
|
|
27
|
+
class Status
|
|
28
|
+
attr_reader :exitstatus
|
|
29
|
+
|
|
30
|
+
def initialize(exitstatus)
|
|
31
|
+
@exitstatus = exitstatus
|
|
32
|
+
freeze
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def exited? = true
|
|
36
|
+
def success? = exitstatus.zero?
|
|
37
|
+
def termsig = nil
|
|
38
|
+
end
|
|
39
|
+
private_constant :Status
|
|
40
|
+
|
|
26
41
|
attr_reader :pid
|
|
27
42
|
|
|
28
43
|
def initialize(command: nil, columns: 80, rows: 24, cwd: nil, env: {}, kernel: nil)
|
|
@@ -104,13 +119,17 @@ module Tarazed
|
|
|
104
119
|
check_hresult(function(:ResizePseudoConsole, [P, I], I).call(@console, coordinate(columns, rows)))
|
|
105
120
|
end
|
|
106
121
|
|
|
107
|
-
def
|
|
108
|
-
|
|
122
|
+
def status
|
|
123
|
+
refresh_status unless @closed
|
|
124
|
+
@status
|
|
109
125
|
end
|
|
110
126
|
|
|
127
|
+
def alive? = !@closed && !status
|
|
128
|
+
|
|
111
129
|
def close
|
|
112
130
|
return self if @closed
|
|
113
131
|
|
|
132
|
+
safely { refresh_status }
|
|
114
133
|
@closed = true
|
|
115
134
|
close_handle(@input_write)
|
|
116
135
|
@input_write = nil
|
|
@@ -128,6 +147,15 @@ module Tarazed
|
|
|
128
147
|
@kernel.fn(name, arguments, result, need_gvl: need_gvl)
|
|
129
148
|
end
|
|
130
149
|
|
|
150
|
+
def refresh_status
|
|
151
|
+
return @status if @status
|
|
152
|
+
|
|
153
|
+
code = [0].pack("I")
|
|
154
|
+
check(function(:GetExitCodeProcess, [P, P], I).call(@process, code), "GetExitCodeProcess")
|
|
155
|
+
value = code.unpack1("I")
|
|
156
|
+
@status = Status.new(value) unless value == STILL_ACTIVE
|
|
157
|
+
end
|
|
158
|
+
|
|
131
159
|
def process_attributes(console)
|
|
132
160
|
size = [0].pack("J")
|
|
133
161
|
function(:InitializeProcThreadAttributeList, [P, U, U, P], I).call(0, 1, 0, size)
|
data/lib/tarazed.rb
CHANGED
|
@@ -9,4 +9,49 @@ require_relative "tarazed/pty"
|
|
|
9
9
|
|
|
10
10
|
module Tarazed
|
|
11
11
|
class Error < StandardError; end
|
|
12
|
+
|
|
13
|
+
module Value
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def define(*members)
|
|
17
|
+
return Data.define(*members) if defined?(Data)
|
|
18
|
+
|
|
19
|
+
Struct.new(*members) do
|
|
20
|
+
members.each { |member| undef_method("#{member}=") }
|
|
21
|
+
|
|
22
|
+
def initialize(*values, **keywords)
|
|
23
|
+
if keywords.empty?
|
|
24
|
+
raise ArgumentError, "wrong number of arguments" unless values.length == self.class.members.length
|
|
25
|
+
|
|
26
|
+
super(*values)
|
|
27
|
+
else
|
|
28
|
+
raise ArgumentError, "cannot mix positional and keyword arguments" unless values.empty?
|
|
29
|
+
|
|
30
|
+
missing = self.class.members - keywords.keys
|
|
31
|
+
unknown = keywords.keys - self.class.members
|
|
32
|
+
raise ArgumentError, "missing keyword: #{missing.first.inspect}" unless missing.empty?
|
|
33
|
+
raise ArgumentError, "unknown keyword: #{unknown.first.inspect}" unless unknown.empty?
|
|
34
|
+
|
|
35
|
+
super(*self.class.members.map { |member| keywords.fetch(member) })
|
|
36
|
+
end
|
|
37
|
+
freeze
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def with(**changes)
|
|
41
|
+
return self if changes.empty?
|
|
42
|
+
|
|
43
|
+
unknown = changes.keys - self.class.members
|
|
44
|
+
raise ArgumentError, "unknown keyword: #{unknown.first.inspect}" unless unknown.empty?
|
|
45
|
+
|
|
46
|
+
self.class.new(**to_h.merge(changes))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Command = Value.define(:id, :prompt_row, :input, :output_range, :exit_status, :started_at, :finished_at, :cwd)
|
|
53
|
+
private_constant :Value
|
|
12
54
|
end
|
|
55
|
+
|
|
56
|
+
require_relative "tarazed/shell_integration"
|
|
57
|
+
require_relative "tarazed/session"
|
data/sig/tarazed.rbs
CHANGED
|
@@ -8,6 +8,24 @@ module Tarazed
|
|
|
8
8
|
type attributes = Hash[Symbol, bool | Integer | [Integer, Integer, Integer]]
|
|
9
9
|
type mouse_action = :press | :move | :release
|
|
10
10
|
|
|
11
|
+
class Command
|
|
12
|
+
attr_reader id: Integer
|
|
13
|
+
attr_reader prompt_row: Integer
|
|
14
|
+
attr_reader input: String
|
|
15
|
+
attr_reader output_range: Range[Integer]?
|
|
16
|
+
attr_reader exit_status: Integer
|
|
17
|
+
attr_reader started_at: Time?
|
|
18
|
+
attr_reader finished_at: Time
|
|
19
|
+
attr_reader cwd: String?
|
|
20
|
+
|
|
21
|
+
def self.new: (Integer id, Integer prompt_row, String input, Range[Integer]? output_range, Integer exit_status,
|
|
22
|
+
Time? started_at, Time finished_at, String? cwd) -> instance
|
|
23
|
+
| (id: Integer, prompt_row: Integer, input: String, output_range: Range[Integer]?,
|
|
24
|
+
exit_status: Integer, started_at: Time?, finished_at: Time, cwd: String?) -> instance
|
|
25
|
+
def with: (?id: Integer, ?prompt_row: Integer, ?input: String, ?output_range: Range[Integer]?,
|
|
26
|
+
?exit_status: Integer, ?started_at: Time?, ?finished_at: Time, ?cwd: String?) -> Command
|
|
27
|
+
end
|
|
28
|
+
|
|
11
29
|
class Cell < Struct[untyped]
|
|
12
30
|
attr_accessor text: String
|
|
13
31
|
attr_accessor width: Integer
|
|
@@ -24,6 +42,7 @@ module Tarazed
|
|
|
24
42
|
include Enumerable[Array[Cell]]
|
|
25
43
|
|
|
26
44
|
attr_reader limit: Integer
|
|
45
|
+
attr_reader total: Integer
|
|
27
46
|
|
|
28
47
|
def initialize: (Integer limit) -> void
|
|
29
48
|
def clear: () -> Array[Array[Cell]]
|
|
@@ -33,6 +52,7 @@ module Tarazed
|
|
|
33
52
|
def each: () -> Enumerator[Array[Cell], Array[Array[Cell]]]
|
|
34
53
|
| () { (Array[Cell]) -> void } -> Array[Array[Cell]]
|
|
35
54
|
def push: (Array[Cell] cells) -> Array[Array[Cell]]?
|
|
55
|
+
def advance: (Integer count) -> Integer
|
|
36
56
|
end
|
|
37
57
|
|
|
38
58
|
class Grid
|
|
@@ -60,6 +80,8 @@ module Tarazed
|
|
|
60
80
|
def alternate?: () -> bool
|
|
61
81
|
def lines: () -> Array[String]
|
|
62
82
|
def text: () -> String
|
|
83
|
+
def history_row: () -> Integer
|
|
84
|
+
def wrap_pending?: () -> bool
|
|
63
85
|
def put: (String char) -> void
|
|
64
86
|
def put_ascii: (String text) -> void
|
|
65
87
|
def self.width: (String char) -> Integer
|
|
@@ -101,7 +123,8 @@ module Tarazed
|
|
|
101
123
|
attr_reader replies: Array[String]
|
|
102
124
|
attr_reader bell_count: Integer
|
|
103
125
|
|
|
104
|
-
def initialize: (?Grid grid) ?{ (String) -> void } -> void
|
|
126
|
+
def initialize: (?Grid grid, ?command_limit: Integer, ?on_command: ^(Command) -> untyped) ?{ (String) -> void } -> void
|
|
127
|
+
def commands: () -> Array[Command]
|
|
105
128
|
def feed: (String bytes) -> self
|
|
106
129
|
def paste: (String text) -> String
|
|
107
130
|
def mouse: (button: Symbol | Integer | nil, column: Integer, row: Integer, ?action: mouse_action,
|
|
@@ -109,18 +132,26 @@ module Tarazed
|
|
|
109
132
|
def key: (String | Symbol name, ?shift: bool, ?alt: bool, ?control: bool) -> String
|
|
110
133
|
end
|
|
111
134
|
|
|
135
|
+
interface _PTYStatus
|
|
136
|
+
def exited?: () -> bool
|
|
137
|
+
def exitstatus: () -> Integer?
|
|
138
|
+
def success?: () -> bool?
|
|
139
|
+
def termsig: () -> Integer?
|
|
140
|
+
end
|
|
141
|
+
|
|
112
142
|
class PTY
|
|
113
143
|
attr_reader reader: IO?
|
|
114
144
|
attr_reader writer: IO?
|
|
115
145
|
attr_reader pid: Integer
|
|
116
146
|
attr_reader grid: Grid
|
|
117
147
|
attr_reader vt: VT
|
|
118
|
-
attr_reader status:
|
|
148
|
+
attr_reader status: _PTYStatus?
|
|
119
149
|
attr_reader initial_cwd: String
|
|
120
150
|
attr_reader command_name: String
|
|
121
151
|
|
|
122
152
|
def initialize: (?command: String | Array[String], ?cwd: String, ?columns: Integer, ?rows: Integer,
|
|
123
|
-
?env: Hash[String, String?], ?scrollback: Integer, ?queue_limit_bytes: Integer
|
|
153
|
+
?env: Hash[String, String?], ?scrollback: Integer, ?queue_limit_bytes: Integer,
|
|
154
|
+
?command_history_limit: Integer, ?on_command: ^(Command) -> untyped) -> void
|
|
124
155
|
def read: (?timeout: Numeric, ?max_bytes: Integer, ?max_seconds: Numeric?) -> String?
|
|
125
156
|
def pending?: () -> bool
|
|
126
157
|
def write: (String bytes) -> Integer
|
|
@@ -136,4 +167,20 @@ module Tarazed
|
|
|
136
167
|
def close: () -> self
|
|
137
168
|
end
|
|
138
169
|
|
|
170
|
+
class Session < PTY
|
|
171
|
+
def initialize: (command: String | Array[String], ?env: Hash[String, String?], ?cwd: String,
|
|
172
|
+
?columns: Integer, ?rows: Integer, ?scrollback_limit: Integer, ?command_history_limit: Integer,
|
|
173
|
+
?queue_limit_bytes: Integer, ?on_command: ^(Command) -> untyped) -> void
|
|
174
|
+
def screen: () -> Grid
|
|
175
|
+
def commands: () -> Array[Command]
|
|
176
|
+
def cwd: () -> String
|
|
177
|
+
def input: (String bytes) -> Integer
|
|
178
|
+
def pump: (?timeout: Numeric) -> bool
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
module ShellIntegration
|
|
182
|
+
def self.path: (String | Symbol shell) -> String
|
|
183
|
+
def self.read: (String | Symbol shell) -> String
|
|
184
|
+
end
|
|
185
|
+
|
|
139
186
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tarazed
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
@@ -46,14 +46,20 @@ files:
|
|
|
46
46
|
- CHANGELOG.md
|
|
47
47
|
- LICENSE.txt
|
|
48
48
|
- README.md
|
|
49
|
+
- assets/shell-integration/tarazed.bash
|
|
50
|
+
- assets/shell-integration/tarazed.fish
|
|
51
|
+
- assets/shell-integration/tarazed.zsh
|
|
49
52
|
- docs/adr/000-template.md
|
|
50
53
|
- docs/adr/001-terminal-core-boundary.md
|
|
54
|
+
- docs/adr/002-shell-integration.md
|
|
51
55
|
- docs/adr/README.md
|
|
52
56
|
- lib/tarazed.rb
|
|
53
57
|
- lib/tarazed/cell.rb
|
|
54
58
|
- lib/tarazed/grid.rb
|
|
55
59
|
- lib/tarazed/pty.rb
|
|
56
60
|
- lib/tarazed/scrollback.rb
|
|
61
|
+
- lib/tarazed/session.rb
|
|
62
|
+
- lib/tarazed/shell_integration.rb
|
|
57
63
|
- lib/tarazed/version.rb
|
|
58
64
|
- lib/tarazed/vt.rb
|
|
59
65
|
- lib/tarazed/windows/conpty.rb
|
|
@@ -80,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
80
86
|
- !ruby/object:Gem::Version
|
|
81
87
|
version: '0'
|
|
82
88
|
requirements: []
|
|
83
|
-
rubygems_version:
|
|
89
|
+
rubygems_version: 3.6.9
|
|
84
90
|
specification_version: 4
|
|
85
91
|
summary: A pure Ruby terminal emulator core
|
|
86
92
|
test_files: []
|