yildun 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: afdbd196693c266f7c4edf951aa39d8c2e750f15bccf1d500a0c00c7e94ea748
4
+ data.tar.gz: 5489e541a3cfbd946bef1441a03a7638b33f17ea02cef8669169c17ca6a25df3
5
+ SHA512:
6
+ metadata.gz: bd3d9e22dfb587805f15b3e72149ad9513bf5090b22eee491ee25fa1cf2091140ade6936f1199adb2e997f103d4f787c16d4488f9aa8ca84f180f10fa96672ac
7
+ data.tar.gz: 4d6e49b08f6527baa7ee4caa78d66389219abf3d95477064776c2e4b84ead079dfa8b7ce8deb61957d5ef4e5c6f54280eedfb8e99900d0497517f71ce34c0686
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-09-21
4
+
5
+ - Initial release.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yudai Takada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Yildun
2
+
3
+ Yildun (δ Ursae Minoris, from Turkish *yıldız*, “star”) is a pure Ruby
4
+ terminal application shell backed by the Tarazed terminal emulator.
5
+
6
+ It provides profiles, tabs, pane state, keymaps, scrollback search, OSC 8
7
+ links, and a deterministic headless mode for integration tests. It does not
8
+ embed an SSH client, multiplexer, JavaScript engine, or WebView.
9
+
10
+ ## Installation
11
+
12
+ ```sh
13
+ gem install yildun
14
+ ```
15
+
16
+ ## Headless sessions
17
+
18
+ ```sh
19
+ yildun --headless --command 'printf "hello\\n"'
20
+ ```
21
+
22
+ Configuration is read from `$XDG_CONFIG_HOME/yildun/settings.jsonc` (or
23
+ `~/.config/yildun/settings.jsonc`). Settings are fail-safe: malformed files
24
+ raise a clear error instead of silently changing the shell profile.
25
+
26
+ ## Development
27
+
28
+ ```sh
29
+ bundle install
30
+ bundle exec rake test
31
+ bundle exec rbs -I sig validate
32
+ ```
@@ -0,0 +1,16 @@
1
+ # 001: Cell rendering stays outside the element tree
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Decision
8
+
9
+ Yildun keeps terminal cells in Tarazed's grid and exposes them to a dedicated
10
+ view adapter. It does not turn every cell into a general UI element.
11
+
12
+ ## Consequences
13
+
14
+ The terminal can apply damage updates without rebuilding an element tree. A
15
+ future Zaniah window may consume the same grid while the headless executable
16
+ remains usable without a graphics backend.
@@ -0,0 +1,16 @@
1
+ # 002: Shell integration is a first-class feature
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Decision
8
+
9
+ Yildun exposes Tarazed command records, failed-command filtering, cwd
10
+ tracking, and previous/next command navigation instead of treating the PTY as
11
+ an opaque byte stream.
12
+
13
+ ## Consequences
14
+
15
+ The application can attach command navigation and output actions to a future
16
+ window without duplicating OSC 133 parsing.
data/exe/yildun ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+ require "yildun"
6
+
7
+ options = {columns: 80, rows: 24, timeout: 0.2}
8
+ OptionParser.new do |parser|
9
+ parser.banner = "Usage: yildun [options]"
10
+ parser.on("--command COMMAND", "shell command") { |value| options[:command] = value }
11
+ parser.on("--columns N", Integer) { |value| options[:columns] = value }
12
+ parser.on("--rows N", Integer) { |value| options[:rows] = value }
13
+ parser.on("--timeout SECONDS", Float) { |value| options[:timeout] = value }
14
+ parser.on("--headless", "print the terminal screen and exit") { options[:headless] = true }
15
+ parser.on("--script PATH", "send script contents to the shell") { |value| options[:script] = value }
16
+ parser.on("--expect PATH", "compare the headless screen with a file") { |value| options[:expect] = value }
17
+ parser.on("--version") { puts Yildun::VERSION; exit }
18
+ end.parse!
19
+
20
+ settings = Yildun::Settings.load
21
+ profile = options[:command] ? Yildun::Profile.new("command", [options[:command]], {}, Dir.pwd) : Yildun.profile(settings)
22
+ begin
23
+ app = Yildun::App.new(settings: settings, profile: profile, columns: options[:columns], rows: options[:rows])
24
+ terminal = app.active_terminal
25
+ terminal.input(File.binread(options[:script])) if options[:script]
26
+
27
+ if options[:headless]
28
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + options[:timeout]
29
+ terminal.pump(timeout: 0.05) while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline && terminal.session.alive?
30
+ output = terminal.screen.text
31
+ if options[:expect]
32
+ expected = File.read(options[:expect]).strip
33
+ abort "headless output did not match #{options[:expect]}" unless output.strip == expected
34
+ end
35
+ puts output
36
+ elsif STDIN.tty? && STDOUT.tty?
37
+ Yildun::Console.new(terminal).run
38
+ else
39
+ warn "Yildun needs a TTY for interactive mode; use --headless for scripted sessions."
40
+ end
41
+ ensure
42
+ app&.active_tab&.close
43
+ end
data/lib/yildun/app.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ class App
5
+ attr_reader :settings, :tabs, :active_index
6
+
7
+ def initialize(settings: Settings.new, profile: nil, columns: 80, rows: 24)
8
+ @settings, @tabs, @active_index = settings, [], 0
9
+ open_tab(profile: profile, columns: columns, rows: rows)
10
+ end
11
+
12
+ def active_tab = @tabs.fetch(@active_index)
13
+ def active_terminal = active_tab.terminal
14
+
15
+ def open_tab(profile: nil, cwd: Dir.pwd, columns: 80, rows: 24)
16
+ profile ||= Yildun.profile(@settings)
17
+ terminal = Terminal.new(command: profile.command, env: profile.env, cwd: profile.cwd || cwd,
18
+ columns: columns, rows: rows, scrollback_limit: @settings.fetch("scrollback_limit"))
19
+ @tabs << Tab.new(terminal, title: profile.name)
20
+ @active_index = @tabs.length - 1
21
+ active_tab
22
+ end
23
+
24
+ def open_tab_here(profile: nil, columns: 80, rows: 24)
25
+ open_tab(profile: profile, cwd: active_terminal.cwd, columns: columns, rows: rows)
26
+ end
27
+
28
+ def close_active
29
+ active_tab.close
30
+ @tabs.delete_at(@active_index)
31
+ return @active_index = 0 if @tabs.empty?
32
+
33
+ @active_index = [@active_index, @tabs.length - 1].min
34
+ end
35
+
36
+ def next_tab
37
+ @active_index = (@active_index + 1) % @tabs.length if @tabs.any?
38
+ end
39
+
40
+ def previous_tab
41
+ @active_index = (@active_index - 1) % @tabs.length if @tabs.any?
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/console"
4
+
5
+ module Yildun
6
+ class Console
7
+ def initialize(terminal, input: $stdin, output: $stdout)
8
+ @terminal, @input, @output = terminal, input, output
9
+ end
10
+
11
+ def run
12
+ return render unless @input.tty? && @output.tty? && @input.respond_to?(:raw)
13
+
14
+ @input.raw do
15
+ @output.write("\e[?25l")
16
+ loop do
17
+ resize
18
+ @terminal.pump(timeout: 0.02)
19
+ render
20
+ break unless @terminal.session.alive?
21
+ next unless IO.select([@input], nil, nil, 0.02)
22
+
23
+ @terminal.input(@input.read_nonblock(4096))
24
+ rescue IO::WaitReadable
25
+ retry
26
+ rescue EOFError, Errno::EIO
27
+ break
28
+ end
29
+ ensure
30
+ @output.write("\e[0m\e[?25h\e[H\e[2J")
31
+ end
32
+ end
33
+
34
+ def render
35
+ @output.write("\e[H")
36
+ @terminal.screen.lines.each { |line| @output.write("\e[2K#{line}\r\n") }
37
+ @output.flush
38
+ end
39
+
40
+ private
41
+
42
+ def resize
43
+ rows, columns = @output.winsize
44
+ @terminal.resize(columns: columns, rows: rows)
45
+ rescue SystemCallError, NoMethodError
46
+ nil
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless defined?(Data)
4
+ Data = Struct
5
+ def Data.define(*members)
6
+ Struct.new(*members) do
7
+ members.each { |member| undef_method("#{member}=") }
8
+ define_method(:initialize) { |*values| super(*values).freeze }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ class Keymap
5
+ def initialize(bindings = {})
6
+ @bindings = bindings.to_h.transform_keys(&:to_s).freeze
7
+ end
8
+
9
+ def command(key) = @bindings[key.to_s]
10
+ def include?(key) = @bindings.key?(key.to_s)
11
+ def to_h = @bindings
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ module Links
5
+ module_function
6
+
7
+ def at(screen, row, column, cwd: Dir.pwd)
8
+ screen.links(row).find { |link| column.between?(link[:column], link[:end_column] - 1) } ||
9
+ screen.file_paths(row, cwd: cwd).find { |link| column == link[:column] }
10
+ end
11
+
12
+ def open(url, opener: nil)
13
+ raise ArgumentError, "only http(s) and mailto links are allowed" unless url.to_s.match?(/\A(?:https?|mailto):/i)
14
+ return opener.call(url) if opener
15
+ command = RUBY_PLATFORM.match?(/darwin/) ? "open" : "xdg-open"
16
+ Process.spawn(command, url.to_s, out: File::NULL, err: File::NULL)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ class Pane
5
+ attr_reader :children, :orientation
6
+
7
+ def initialize(tab = nil, orientation: :leaf)
8
+ @children, @orientation = tab ? [tab] : [], orientation
9
+ end
10
+
11
+ def leaf? = @orientation == :leaf
12
+ def split(tab, orientation: :horizontal)
13
+ if leaf? && @children.empty?
14
+ @children << (tab.is_a?(Pane) ? tab : Pane.new(tab))
15
+ return self
16
+ end
17
+
18
+ if leaf?
19
+ existing = @children.map { |child| child.is_a?(Pane) ? child : Pane.new(child) }
20
+ @children = existing
21
+ end
22
+ @orientation = orientation
23
+ @children << (tab.is_a?(Pane) ? tab : Pane.new(tab))
24
+ self
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ Profile = Data.define(:name, :command, :env, :cwd)
5
+
6
+ module_function
7
+
8
+ def profile(settings, name = settings.fetch("default_profile"))
9
+ value = settings.profile(name)
10
+ Profile.new(name.to_s, Array(value.fetch("command")), value.fetch("env", {}), value["cwd"])
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ class Search
5
+ def initialize(terminal) = @terminal = terminal
6
+ def call(query, regex: false) = @terminal.screen.search(query, regex: regex)
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Yildun
7
+ class SessionState
8
+ def self.save(path, tabs:)
9
+ FileUtils.mkdir_p(File.dirname(path))
10
+ File.write(path, JSON.pretty_generate("tabs" => tabs.map { |tab| {"title" => tab.title, "cwd" => tab.terminal.cwd} }))
11
+ end
12
+
13
+ def self.load(path)
14
+ return [] unless File.file?(path)
15
+ JSON.parse(File.read(path)).fetch("tabs", [])
16
+ rescue JSON::ParserError
17
+ []
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Yildun
6
+ class Settings
7
+ DEFAULTS = {
8
+ "font" => {"family" => "JetBrains Mono", "size" => 13, "ligatures" => true, "line_height" => 1.2},
9
+ "theme" => "tokyo-night",
10
+ "cursor" => {"shape" => "block", "blink" => true},
11
+ "background_opacity" => 1.0,
12
+ "scrollback_limit" => 10_000,
13
+ "shell_integration" => true,
14
+ "profiles" => {"default" => {"command" => [ENV.fetch("SHELL", "sh"), "-l"], "env" => {}}},
15
+ "default_profile" => "default",
16
+ "links" => {"file_line" => {"pattern" => "([\\w./-]+):(\\d+)", "action" => "open_editor"}},
17
+ "keymap" => {"cmd+t" => "tab.new", "cmd+d" => "pane.split_right", "cmd+f" => "search.open"}
18
+ }.freeze
19
+
20
+ attr_reader :values
21
+
22
+ def initialize(values = {})
23
+ @values = deep_merge(DEFAULTS, stringify_keys(values)).freeze
24
+ end
25
+
26
+ def [](key) = @values.fetch(key.to_s)
27
+ def fetch(key, default = nil) = @values.fetch(key.to_s, default)
28
+ def profile(name = @values.fetch("default_profile")) = @values.fetch("profiles").fetch(name.to_s)
29
+
30
+ def self.load(path = default_path)
31
+ return new unless File.file?(path)
32
+ new(parse_jsonc(File.read(path, encoding: "UTF-8")))
33
+ rescue JSON::ParserError, EncodingError => error
34
+ raise ArgumentError, "invalid settings: #{error.message}"
35
+ end
36
+
37
+ def self.default_path
38
+ root = ENV["XDG_CONFIG_HOME"] || File.join(Dir.home, ".config")
39
+ File.join(root, "yildun", "settings.jsonc")
40
+ end
41
+
42
+ def self.parse_jsonc(text)
43
+ chars = text.to_s.chars
44
+ output = +""
45
+ quote = false
46
+ escaped = false
47
+ line_comment = false
48
+ block_comment = false
49
+ index = 0
50
+ while index < chars.length
51
+ char = chars[index]
52
+ if line_comment
53
+ line_comment = false if char == "\n"
54
+ output << char if char == "\n"
55
+ elsif block_comment
56
+ if char == "*" && chars[index + 1] == "/"
57
+ block_comment = false
58
+ index += 1
59
+ else
60
+ output << "\n" if char == "\n"
61
+ end
62
+ elsif quote
63
+ output << char
64
+ if escaped
65
+ escaped = false
66
+ elsif char == "\\"
67
+ escaped = true
68
+ elsif char == '"'
69
+ quote = false
70
+ end
71
+ elsif char == '"'
72
+ quote = true
73
+ output << char
74
+ elsif char == "/" && chars[index + 1] == "/"
75
+ line_comment = true
76
+ index += 1
77
+ elsif char == "/" && chars[index + 1] == "*"
78
+ block_comment = true
79
+ index += 1
80
+ else
81
+ output << char
82
+ end
83
+ index += 1
84
+ end
85
+
86
+ JSON.parse(remove_trailing_commas(output))
87
+ end
88
+
89
+ def self.remove_trailing_commas(text)
90
+ chars = text.chars
91
+ output = +""
92
+ quote = false
93
+ escaped = false
94
+ chars.each_with_index do |char, index|
95
+ if quote
96
+ output << char
97
+ if escaped
98
+ escaped = false
99
+ elsif char == "\\"
100
+ escaped = true
101
+ elsif char == '"'
102
+ quote = false
103
+ end
104
+ next
105
+ end
106
+ if char == '"'
107
+ quote = true
108
+ output << char
109
+ elsif char == "," && chars[(index + 1)..].drop_while { |item| item.match?(/\s/) }.first.to_s.match?(/[}\]]/)
110
+ next
111
+ else
112
+ output << char
113
+ end
114
+ end
115
+ output
116
+ end
117
+
118
+ private
119
+
120
+ def stringify_keys(value)
121
+ case value
122
+ when Hash then value.to_h { |key, item| [key.to_s, stringify_keys(item)] }
123
+ when Array then value.map { |item| stringify_keys(item) }
124
+ else value
125
+ end
126
+ end
127
+
128
+ def deep_merge(left, right)
129
+ left.merge(right) do |_key, old, value|
130
+ old.is_a?(Hash) && value.is_a?(Hash) ? deep_merge(old, value) : value
131
+ end
132
+ end
133
+ end
134
+ end
data/lib/yildun/tab.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ class Tab
5
+ attr_reader :title, :terminal
6
+
7
+ def initialize(terminal, title: nil)
8
+ @terminal, @title = terminal, (title || terminal.cwd)
9
+ end
10
+
11
+ def close = @terminal.close
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ tarazed_path = ENV["TARAZED_PATH"]
4
+ tarazed_root = File.expand_path("..", __dir__)
5
+ tarazed_path ? require(File.expand_path("lib/tarazed", File.expand_path(tarazed_path, tarazed_root))) : require("tarazed")
6
+
7
+ module Yildun
8
+ class Terminal
9
+ attr_reader :session
10
+
11
+ def initialize(command:, env: {}, cwd: Dir.pwd, columns: 80, rows: 24, scrollback_limit: 10_000, session: nil)
12
+ @session = session || Tarazed::Session.new(command: command, env: env, cwd: cwd, columns: columns, rows: rows,
13
+ scrollback_limit: scrollback_limit)
14
+ end
15
+
16
+ def screen = @session.screen
17
+ def cwd = @session.cwd
18
+ def commands = @session.commands
19
+ def failed_commands = commands.select { |command| command.exit_status != 0 }
20
+ def input(bytes) = @session.input(bytes)
21
+ def paste(text) = @session.paste(text)
22
+ def key(name, **modifiers) = @session.key(name, **modifiers)
23
+ def mouse(**event) = @session.mouse(**event)
24
+ def pump(timeout: 0) = @session.pump(timeout: timeout)
25
+ def resize(columns:, rows:) = @session.resize(columns: columns, rows: rows)
26
+ def focus(active:) = @session.vt.focus(active: active)
27
+ def search(query, regex: false)
28
+ return screen.search(query, regex: regex) if screen.respond_to?(:search)
29
+
30
+ matcher = regex ? Regexp.new(query.to_s) : query.to_s
31
+ screen.lines.each_with_index.filter_map do |text, index|
32
+ match = regex ? matcher.match(text) : text.index(matcher)
33
+ next unless match
34
+
35
+ range = match.is_a?(MatchData) ? match.begin(0)...match.end(0) : match...(match + matcher.length)
36
+ {index: index, text: text, range: range}.freeze
37
+ end.freeze
38
+ rescue RegexpError => error
39
+ raise ArgumentError, "invalid search pattern: #{error.message}"
40
+ end
41
+
42
+ def previous_command
43
+ @command_index = [(@command_index || commands.length) - 1, 0].max
44
+ commands[@command_index]
45
+ end
46
+
47
+ def next_command
48
+ @command_index = [(@command_index || -1) + 1, commands.length - 1].min
49
+ commands[@command_index]
50
+ end
51
+
52
+ def last_command = commands.last
53
+
54
+ def command_output(command)
55
+ range = command&.output_range
56
+ return "" unless range
57
+
58
+ screen.selection([0, range.begin], [screen.columns, range.end], history: true)
59
+ end
60
+
61
+ alias copy_command_output command_output
62
+
63
+ def selection(start, finish, history: false) = screen.selection(start, finish, history: history)
64
+
65
+ def search_commands(query, regex: false)
66
+ matcher = regex ? Regexp.new(query.to_s) : query.to_s.downcase
67
+ commands.select do |command|
68
+ input = command.input.to_s
69
+ regex ? matcher.match?(input) : input.downcase.include?(matcher)
70
+ end
71
+ rescue RegexpError => error
72
+ raise ArgumentError, "invalid search pattern: #{error.message}"
73
+ end
74
+
75
+ def close = @session.close
76
+ end
77
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ Theme = Data.define(:name, :background, :foreground, :cursor, :selection)
5
+ THEMES = {
6
+ "tokyo-night" => Theme.new("tokyo-night", "#1a1b26", "#c0caf5", "#c0caf5", "#33467c"),
7
+ "default" => Theme.new("default", "#111318", "#dddddd", "#ffffff", "#334455")
8
+ }.freeze
9
+
10
+ module_function
11
+
12
+ def theme(name) = THEMES.fetch(name.to_s, THEMES.fetch("default"))
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yildun
4
+ VERSION = "0.1.0"
5
+ end
data/lib/yildun.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "yildun/version"
4
+ require_relative "yildun/data_compat"
5
+ require_relative "yildun/settings"
6
+ require_relative "yildun/keymap"
7
+ require_relative "yildun/theme"
8
+ require_relative "yildun/profile"
9
+ require_relative "yildun/search"
10
+ require_relative "yildun/links"
11
+ require_relative "yildun/terminal"
12
+ require_relative "yildun/console"
13
+ require_relative "yildun/tab"
14
+ require_relative "yildun/pane"
15
+ require_relative "yildun/session"
16
+ require_relative "yildun/app"
data/sig/yildun.rbs ADDED
@@ -0,0 +1,86 @@
1
+ module Yildun
2
+ VERSION: String
3
+ class Settings
4
+ attr_reader values: Hash[String, untyped]
5
+ def initialize: (?Hash[untyped, untyped] values) -> void
6
+ def []: (String key) -> untyped
7
+ def fetch: (String key, ?untyped default) -> untyped
8
+ def profile: (?String name) -> Hash[String, untyped]
9
+ def self.load: (?String path) -> Settings
10
+ def self.default_path: () -> String
11
+ def self.parse_jsonc: (String text) -> Hash[untyped, untyped]
12
+ end
13
+ class Keymap
14
+ def initialize: (?Hash[untyped, untyped] bindings) -> void
15
+ def command: (String key) -> String?
16
+ def include?: (String key) -> bool
17
+ end
18
+ class Terminal
19
+ attr_reader session: untyped
20
+ def initialize: (command: String | Array[String], ?env: Hash[String, String], ?cwd: String, ?columns: Integer, ?rows: Integer, ?scrollback_limit: Integer, ?session: untyped) -> void
21
+ def screen: () -> untyped
22
+ def cwd: () -> String
23
+ def commands: () -> Array[untyped]
24
+ def failed_commands: () -> Array[untyped]
25
+ def input: (String bytes) -> untyped
26
+ def paste: (String text) -> untyped
27
+ def key: (String name, **bool modifiers) -> untyped
28
+ def mouse: (**untyped event) -> untyped
29
+ def pump: (?timeout: Numeric) -> bool
30
+ def resize: (columns: Integer, rows: Integer) -> untyped
31
+ def focus: (active: bool) -> String
32
+ def search: (String query, ?regex: bool) -> Array[Hash[Symbol, untyped]]
33
+ def previous_command: () -> untyped
34
+ def next_command: () -> untyped
35
+ def last_command: () -> untyped
36
+ def command_output: (untyped command) -> String
37
+ alias copy_command_output command_output
38
+ def selection: (Array[Integer] start, Array[Integer] finish, ?history: bool) -> String
39
+ def search_commands: (String query, ?regex: bool) -> Array[untyped]
40
+ def close: () -> untyped
41
+ end
42
+
43
+ class Console
44
+ def initialize: (untyped terminal, ?input: untyped, ?output: untyped) -> void
45
+ def run: () -> untyped
46
+ def render: () -> untyped
47
+ end
48
+
49
+ class Pane
50
+ attr_reader children: Array[untyped]
51
+ attr_reader orientation: Symbol
52
+ def initialize: (?untyped tab, ?orientation: Symbol) -> void
53
+ def leaf?: () -> bool
54
+ def split: (untyped tab, ?orientation: Symbol) -> Pane
55
+ end
56
+
57
+ class Search
58
+ def initialize: (untyped terminal) -> void
59
+ def call: (String query, ?regex: bool) -> untyped
60
+ end
61
+
62
+ module Links
63
+ def self.at: (untyped screen, Integer row, Integer column, ?cwd: String) -> Hash[Symbol, untyped]?
64
+ def self.open: (String url, ?opener: ^(String) -> untyped) -> untyped
65
+ end
66
+
67
+ class SessionState
68
+ def self.save: (String path, tabs: Array[untyped]) -> untyped
69
+ def self.load: (String path) -> Array[Hash[String, untyped]]
70
+ end
71
+ class Tab
72
+ attr_reader title: String
73
+ attr_reader terminal: Terminal
74
+ end
75
+ class App
76
+ attr_reader settings: Settings
77
+ attr_reader tabs: Array[Tab]
78
+ attr_reader active_index: Integer
79
+ def initialize: (?settings: Settings, ?profile: untyped, ?columns: Integer, ?rows: Integer) -> void
80
+ def active_tab: () -> Tab
81
+ def active_terminal: () -> Terminal
82
+ def open_tab: (?profile: untyped, ?cwd: String, ?columns: Integer, ?rows: Integer) -> Tab
83
+ def open_tab_here: (?profile: untyped, ?columns: Integer, ?rows: Integer) -> Tab
84
+ def close_active: () -> untyped
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yildun
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: tarazed
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.2.6
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '0.3'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 0.2.6
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '0.3'
32
+ description: A terminal application shell with tabs, profiles, search, links, and
33
+ Tarazed-backed sessions.
34
+ email:
35
+ - t.yudai92@gmail.com
36
+ executables:
37
+ - yildun
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - LICENSE.txt
43
+ - README.md
44
+ - docs/adr/001-cell-rendering.md
45
+ - docs/adr/002-shell-integration.md
46
+ - exe/yildun
47
+ - lib/yildun.rb
48
+ - lib/yildun/app.rb
49
+ - lib/yildun/console.rb
50
+ - lib/yildun/data_compat.rb
51
+ - lib/yildun/keymap.rb
52
+ - lib/yildun/links.rb
53
+ - lib/yildun/pane.rb
54
+ - lib/yildun/profile.rb
55
+ - lib/yildun/search.rb
56
+ - lib/yildun/session.rb
57
+ - lib/yildun/settings.rb
58
+ - lib/yildun/tab.rb
59
+ - lib/yildun/terminal.rb
60
+ - lib/yildun/theme.rb
61
+ - lib/yildun/version.rb
62
+ - sig/yildun.rbs
63
+ homepage: https://github.com/noxdea/yildun
64
+ licenses:
65
+ - MIT
66
+ metadata:
67
+ source_code_uri: https://github.com/noxdea/yildun
68
+ changelog_uri: https://github.com/noxdea/yildun/blob/main/CHANGELOG.md
69
+ allowed_push_host: https://rubygems.org
70
+ rubygems_mfa_required: 'true'
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '3.1'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubygems_version: 4.0.16
86
+ specification_version: 4
87
+ summary: A pure Ruby terminal application built on Tarazed
88
+ test_files: []