try-cli 1.9.3 → 1.10.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/README.md +14 -4
- data/VERSION +1 -1
- data/lib/tui.rb +3 -2
- data/try.rb +108 -24
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bc9090ddafc7354bd7d4207e5247e9afd3f52df65e43c1600e21f717c3644cb
|
|
4
|
+
data.tar.gz: cc3fbc4c3868d6d504e326702329fa14d377257d9300c468c07f929f5137c176
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21c62da72659e06ca9614d1ee82f8063f80f13b153e53af39102cdb21e083ab00c46d0d97c42a91cbbe8e0142d5030448660fd9594790bb4da753a0f8314de20
|
|
7
|
+
data.tar.gz: 83376a51f4be2be9fe9dcb4c0de857174fd53d1409ec65b1e1f6cc5d9728a4bcff5bfa800fe180e63f799aab1063c207ab8a6ab11196351715454a3f934819ab
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Ever find yourself with 50 directories named `test`, `test2`, `new-test`, `actua
|
|
|
10
10
|
|
|
11
11
|
**try** is here for your beautifully chaotic mind.
|
|
12
12
|
|
|
13
|
-
# What it does
|
|
13
|
+
# What it does
|
|
14
14
|
|
|
15
15
|

|
|
16
16
|
|
|
@@ -155,6 +155,10 @@ try clone https://github.com/tobi/try.git my-fork
|
|
|
155
155
|
# Shorthand syntax (no need to type 'clone')
|
|
156
156
|
try https://github.com/tobi/try.git
|
|
157
157
|
# Creates: 2025-08-27-tobi-try
|
|
158
|
+
|
|
159
|
+
# Paste a GitHub pull request URL to clone and check out that PR
|
|
160
|
+
try https://github.com/tobi/try/pull/124
|
|
161
|
+
# Creates: 2025-08-27-tobi-try
|
|
158
162
|
```
|
|
159
163
|
|
|
160
164
|
Supported git URI formats:
|
|
@@ -162,8 +166,14 @@ Supported git URI formats:
|
|
|
162
166
|
- `git@github.com:user/repo.git` (SSH GitHub)
|
|
163
167
|
- `https://gitlab.com/user/repo.git` (GitLab)
|
|
164
168
|
- `git@host.com:user/repo.git` (SSH other hosts)
|
|
169
|
+
- `ssh://git@host.com:port/user/repo.git` (SSH other hosts with custom port)
|
|
170
|
+
- `user@host:path/to/repo.git` (nested SCP-style SSH URLs)
|
|
171
|
+
- `https://github.com/user/repo/pull/123` (GitHub pull requests)
|
|
165
172
|
|
|
166
|
-
|
|
173
|
+
A GitHub pull request URL clones the main repository, fetches the PR ref, and
|
|
174
|
+
checks it out in detached HEAD state. The directory name is based on the main
|
|
175
|
+
repository URL, not the `/pull/<number>` suffix. The `.git` suffix is
|
|
176
|
+
automatically removed from URLs when generating directory names.
|
|
167
177
|
|
|
168
178
|
### Keyboard Shortcuts
|
|
169
179
|
|
|
@@ -199,9 +209,9 @@ nix run github:tobi/try init ~/my-tries
|
|
|
199
209
|
```nix
|
|
200
210
|
{
|
|
201
211
|
inputs.try.url = "github:tobi/try";
|
|
202
|
-
|
|
212
|
+
|
|
203
213
|
imports = [ inputs.try.homeManagerModules.default ];
|
|
204
|
-
|
|
214
|
+
|
|
205
215
|
programs.try = {
|
|
206
216
|
enable = true;
|
|
207
217
|
path = "~/experiments"; # optional, defaults to ~/src/tries
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.10.1
|
data/lib/tui.rb
CHANGED
|
@@ -90,14 +90,15 @@ module Tui
|
|
|
90
90
|
module Palette
|
|
91
91
|
HEADER = ANSI.sgr(1, "38;5;114")
|
|
92
92
|
ACCENT = ANSI.sgr(1, "38;5;214")
|
|
93
|
-
HIGHLIGHT = "\e[1;33m" # Bold yellow
|
|
93
|
+
HIGHLIGHT = "\e[1;33m" # Bold yellow remains readable on the controlled selected background
|
|
94
94
|
MUTED = ANSI.fg(245)
|
|
95
95
|
MATCH = ANSI.sgr(1, "38;5;226")
|
|
96
96
|
INPUT_HINT = ANSI.fg(244)
|
|
97
97
|
INPUT_CURSOR_ON = "\e[7m"
|
|
98
98
|
INPUT_CURSOR_OFF = "\e[27m"
|
|
99
99
|
|
|
100
|
-
SELECTED_BG = ANSI.bg(238)
|
|
100
|
+
SELECTED_BG = ANSI.bg(238) # Preserve the dark selected-row appearance
|
|
101
|
+
SELECTED_FG = ANSI.fg(255) # Keep selected content readable on every theme
|
|
101
102
|
DANGER_BG = ANSI.bg(52)
|
|
102
103
|
end
|
|
103
104
|
|
data/try.rb
CHANGED
|
@@ -77,7 +77,7 @@ class TrySelector
|
|
|
77
77
|
STDERR.print("#{Tui::ANSI::ALT_SCREEN_ON}#{Tui::ANSI.set_title("try")}#{Tui::ANSI::CURSOR_BLINK}")
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
@old_winch_handler = Signal.trap('WINCH') { @needs_redraw = true }
|
|
80
|
+
@old_winch_handler = Signal.trap('WINCH') { @needs_redraw = true } if Signal.list.key?('WINCH')
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def restore_terminal
|
|
@@ -389,15 +389,15 @@ class TrySelector
|
|
|
389
389
|
|
|
390
390
|
def render_entry_line(screen, entry, is_selected, width)
|
|
391
391
|
is_marked = @marked_for_deletion.include?(entry[:path])
|
|
392
|
-
# Marked items
|
|
392
|
+
# Marked items keep the danger background; selected rows add a readable foreground.
|
|
393
393
|
background = if is_marked
|
|
394
|
-
Tui::Palette::DANGER_BG
|
|
394
|
+
Tui::Palette::DANGER_BG + (is_selected ? Tui::Palette::SELECTED_FG : "")
|
|
395
395
|
elsif is_selected
|
|
396
|
-
Tui::Palette::SELECTED_BG
|
|
396
|
+
Tui::Palette::SELECTED_BG + Tui::Palette::SELECTED_FG
|
|
397
397
|
end
|
|
398
398
|
|
|
399
399
|
line = screen.body.add_line(background: background)
|
|
400
|
-
line.write << (is_selected ? Tui::Text.highlight("→ ") : " ")
|
|
400
|
+
line.write << (is_selected ? Tui::Text.highlight("→ ") + selected_foreground : " ")
|
|
401
401
|
icon = if is_marked
|
|
402
402
|
emoji("🗑️")
|
|
403
403
|
elsif entry[:is_symlink]
|
|
@@ -407,7 +407,7 @@ class TrySelector
|
|
|
407
407
|
end
|
|
408
408
|
line.write << icon << " "
|
|
409
409
|
|
|
410
|
-
plain_name, rendered_name = formatted_entry_name(entry)
|
|
410
|
+
plain_name, rendered_name = formatted_entry_name(entry, selected: is_selected)
|
|
411
411
|
prefix_width = 5
|
|
412
412
|
meta_text = "#{format_relative_time(entry[:mtime])}, #{format('%.1f', entry[:score])}"
|
|
413
413
|
|
|
@@ -422,13 +422,15 @@ class TrySelector
|
|
|
422
422
|
line.write << display_rendered
|
|
423
423
|
|
|
424
424
|
# Right content is lower layer - will be overwritten by left if they overlap
|
|
425
|
-
line.right.
|
|
425
|
+
line.right.write(is_selected ? meta_text : Tui::Text.dim(meta_text))
|
|
426
426
|
end
|
|
427
427
|
|
|
428
428
|
def render_create_line(screen, is_selected, width)
|
|
429
|
-
background = is_selected
|
|
429
|
+
background = if is_selected
|
|
430
|
+
Tui::Palette::SELECTED_BG + Tui::Palette::SELECTED_FG
|
|
431
|
+
end
|
|
430
432
|
line = screen.body.add_line(background: background)
|
|
431
|
-
line.write << (is_selected ? Tui::Text.highlight("→ ") : " ")
|
|
433
|
+
line.write << (is_selected ? Tui::Text.highlight("→ ") + selected_foreground : " ")
|
|
432
434
|
date_prefix = Time.now.strftime("%Y-%m-%d")
|
|
433
435
|
label = if @input_buffer.empty?
|
|
434
436
|
"📂 Create new: #{date_prefix}-"
|
|
@@ -438,7 +440,7 @@ class TrySelector
|
|
|
438
440
|
line.write << label
|
|
439
441
|
end
|
|
440
442
|
|
|
441
|
-
def formatted_entry_name(entry)
|
|
443
|
+
def formatted_entry_name(entry, selected: false)
|
|
442
444
|
basename = entry[:basename]
|
|
443
445
|
positions = entry[:highlight_positions] || []
|
|
444
446
|
|
|
@@ -447,17 +449,25 @@ class TrySelector
|
|
|
447
449
|
name_part = $2
|
|
448
450
|
date_len = date_part.length + 1 # +1 for the hyphen
|
|
449
451
|
|
|
450
|
-
rendered = Tui::Text.dim(date_part)
|
|
452
|
+
rendered = selected ? date_part : Tui::Text.dim(date_part)
|
|
451
453
|
# Highlight hyphen if it's in positions
|
|
452
|
-
|
|
453
|
-
|
|
454
|
+
hyphen = if positions.include?(10)
|
|
455
|
+
Tui::Text.highlight('-')
|
|
456
|
+
elsif selected
|
|
457
|
+
'-'
|
|
458
|
+
else
|
|
459
|
+
Tui::Text.dim('-')
|
|
460
|
+
end
|
|
461
|
+
rendered += hyphen
|
|
462
|
+
rendered += selected_foreground if selected && positions.include?(10)
|
|
463
|
+
rendered += highlight_with_positions(name_part, positions, date_len, selected: selected)
|
|
454
464
|
["#{date_part}-#{name_part}", rendered]
|
|
455
465
|
else
|
|
456
|
-
[basename, highlight_with_positions(basename, positions, 0)]
|
|
466
|
+
[basename, highlight_with_positions(basename, positions, 0, selected: selected)]
|
|
457
467
|
end
|
|
458
468
|
end
|
|
459
469
|
|
|
460
|
-
def highlight_with_positions(text, positions, offset)
|
|
470
|
+
def highlight_with_positions(text, positions, offset, selected: false)
|
|
461
471
|
pos_set = positions.is_a?(Set) ? positions : positions.to_set
|
|
462
472
|
result = String.new
|
|
463
473
|
chars = text.chars
|
|
@@ -469,6 +479,7 @@ class TrySelector
|
|
|
469
479
|
i += 1
|
|
470
480
|
i += 1 while i < chars.length && pos_set.include?(i + offset)
|
|
471
481
|
result << Tui::Text.highlight(chars[batch_start...i].join)
|
|
482
|
+
result << selected_foreground if selected
|
|
472
483
|
else
|
|
473
484
|
result << chars[i]
|
|
474
485
|
i += 1
|
|
@@ -477,6 +488,10 @@ class TrySelector
|
|
|
477
488
|
result
|
|
478
489
|
end
|
|
479
490
|
|
|
491
|
+
def selected_foreground
|
|
492
|
+
Tui.colors_enabled? ? Tui::Palette::SELECTED_FG : ""
|
|
493
|
+
end
|
|
494
|
+
|
|
480
495
|
# Find the position of the previous word boundary for Ctrl-W deletion.
|
|
481
496
|
# Skips non-alphanumeric chars, then skips alphanumeric chars.
|
|
482
497
|
def word_boundary_backward(buffer, cursor)
|
|
@@ -955,7 +970,7 @@ end
|
|
|
955
970
|
# Main execution with OptionParser subcommands
|
|
956
971
|
if __FILE__ == $0
|
|
957
972
|
|
|
958
|
-
VERSION = "1.
|
|
973
|
+
VERSION = "1.10.1"
|
|
959
974
|
|
|
960
975
|
def print_global_help
|
|
961
976
|
text = <<~HELP
|
|
@@ -984,6 +999,7 @@ if __FILE__ == $0
|
|
|
984
999
|
try Open interactive selector
|
|
985
1000
|
try project Selector with initial filter
|
|
986
1001
|
try clone https://github.com/user/repo
|
|
1002
|
+
try https://github.com/user/repo/pull/123
|
|
987
1003
|
try worktree feature-branch
|
|
988
1004
|
|
|
989
1005
|
Manual mode (without alias):
|
|
@@ -1053,19 +1069,44 @@ if __FILE__ == $0
|
|
|
1053
1069
|
# https://gitlab.com/user/repo or other git hosts
|
|
1054
1070
|
host, user, repo = $1, $2, $3
|
|
1055
1071
|
return { user: user, repo: repo, host: host }
|
|
1056
|
-
elsif uri.match(%r{^git@([^:]+):([^/]+)/(
|
|
1057
|
-
# git@host:user/repo
|
|
1058
|
-
host, user,
|
|
1072
|
+
elsif uri.match(%r{^git@([^:]+):([^/]+)/(.+)})
|
|
1073
|
+
# git@host:user/path/to/repo
|
|
1074
|
+
host, user, path = $1, $2, $3
|
|
1075
|
+
repo = File.basename(path)
|
|
1076
|
+
return { user: user, repo: repo, host: host }
|
|
1077
|
+
elsif uri.match(%r{^ssh://[^@/]+@([^/]+)/([^/]+)/(.+)})
|
|
1078
|
+
# ssh://user@host:port/user/repo
|
|
1079
|
+
host, user, path = $1, $2, $3
|
|
1080
|
+
repo = File.basename(path)
|
|
1081
|
+
return { user: user, repo: repo, host: host }
|
|
1082
|
+
elsif uri.match(%r{^([^@/:]+)@([^:]+):(.+)})
|
|
1083
|
+
# SCP-style SSH: user@host:path/to/repo
|
|
1084
|
+
user, host, path = $1, $2, $3
|
|
1085
|
+
repo = File.basename(path)
|
|
1059
1086
|
return { user: user, repo: repo, host: host }
|
|
1060
1087
|
else
|
|
1061
1088
|
return nil
|
|
1062
1089
|
end
|
|
1063
1090
|
end
|
|
1064
1091
|
|
|
1092
|
+
def github_pr_details(uri)
|
|
1093
|
+
return nil unless uri
|
|
1094
|
+
|
|
1095
|
+
match = uri.match(%r{\Ahttps?://(?:www\.)?github\.com/([^/]+)/([^/]+)/pull/(\d+)/?\z})
|
|
1096
|
+
return nil unless match
|
|
1097
|
+
|
|
1098
|
+
{
|
|
1099
|
+
user: match[1],
|
|
1100
|
+
repo: match[2].sub(/\.git\z/, ''),
|
|
1101
|
+
pr_id: match[3],
|
|
1102
|
+
git_uri: "https://github.com/#{match[1]}/#{match[2].sub(/\.git\z/, '')}.git"
|
|
1103
|
+
}
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1065
1106
|
def generate_clone_directory_name(git_uri, custom_name = nil)
|
|
1066
1107
|
return custom_name if custom_name && !custom_name.empty?
|
|
1067
1108
|
|
|
1068
|
-
parsed = parse_git_uri(git_uri)
|
|
1109
|
+
parsed = github_pr_details(git_uri) || parse_git_uri(git_uri)
|
|
1069
1110
|
return nil unless parsed
|
|
1070
1111
|
|
|
1071
1112
|
date_prefix = Time.now.strftime("%Y-%m-%d")
|
|
@@ -1166,7 +1207,12 @@ if __FILE__ == $0
|
|
|
1166
1207
|
exit 1
|
|
1167
1208
|
end
|
|
1168
1209
|
|
|
1169
|
-
|
|
1210
|
+
path = File.join(tries_path, dir_name)
|
|
1211
|
+
if pr = github_pr_details(git_uri)
|
|
1212
|
+
script_clone_pr(path, pr[:git_uri], pr[:pr_id])
|
|
1213
|
+
else
|
|
1214
|
+
script_clone(path, git_uri)
|
|
1215
|
+
end
|
|
1170
1216
|
end
|
|
1171
1217
|
|
|
1172
1218
|
def cmd_init!(args, tries_path)
|
|
@@ -1355,7 +1401,11 @@ if __FILE__ == $0
|
|
|
1355
1401
|
exit 1
|
|
1356
1402
|
end
|
|
1357
1403
|
full_path = File.join(tries_path, dir_name)
|
|
1358
|
-
|
|
1404
|
+
if pr = github_pr_details(git_uri)
|
|
1405
|
+
return script_clone_pr(full_path, pr[:git_uri], pr[:pr_id])
|
|
1406
|
+
else
|
|
1407
|
+
return script_clone(full_path, git_uri)
|
|
1408
|
+
end
|
|
1359
1409
|
end
|
|
1360
1410
|
|
|
1361
1411
|
# Regular interactive selector
|
|
@@ -1408,8 +1458,31 @@ if __FILE__ == $0
|
|
|
1408
1458
|
end
|
|
1409
1459
|
end
|
|
1410
1460
|
|
|
1461
|
+
# Emit best-effort terminal manager rename commands. Keep these as the final
|
|
1462
|
+
# commands so a missing or broken optional CLI never prevents the directory
|
|
1463
|
+
# change from completing.
|
|
1464
|
+
def terminal_rename_commands(path)
|
|
1465
|
+
name = File.basename(path).sub(/\A\d{4}-\d{2}-\d{2}-/, '')
|
|
1466
|
+
label = "try: #{name}"
|
|
1467
|
+
|
|
1468
|
+
if ENV['HERDR_ENV'] == '1' && ENV['HERDR_PANE_ID'] && !ENV['HERDR_PANE_ID'].empty?
|
|
1469
|
+
commands = [
|
|
1470
|
+
"command -v herdr >/dev/null 2>&1 && herdr pane report-metadata #{q(ENV['HERDR_PANE_ID'])} --source try --title #{q(label)} >/dev/null 2>&1 || true"
|
|
1471
|
+
]
|
|
1472
|
+
if ENV['HERDR_PANE_ID'].match?(/:p1\z/) && ENV['HERDR_WORKSPACE_ID'] && !ENV['HERDR_WORKSPACE_ID'].empty?
|
|
1473
|
+
commands << "command -v herdr >/dev/null 2>&1 && herdr workspace rename #{q(ENV['HERDR_WORKSPACE_ID'])} #{q(label)} >/dev/null 2>&1 || true"
|
|
1474
|
+
end
|
|
1475
|
+
commands
|
|
1476
|
+
elsif (ENV['CMUX_SOCKET_PATH'] && !ENV['CMUX_SOCKET_PATH'].empty?) ||
|
|
1477
|
+
(ENV['CMUX_BUNDLE_ID'] && !ENV['CMUX_BUNDLE_ID'].empty?)
|
|
1478
|
+
["command -v cmux >/dev/null 2>&1 && cmux rename-tab #{q(label)} >/dev/null 2>&1 || true"]
|
|
1479
|
+
else
|
|
1480
|
+
[]
|
|
1481
|
+
end
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1411
1484
|
def script_cd(path)
|
|
1412
|
-
["touch #{q(path)}", "echo #{q(path)}", "cd #{q(path)}"]
|
|
1485
|
+
["touch #{q(path)}", "echo #{q(path)}", "cd #{q(path)}"] + terminal_rename_commands(path)
|
|
1413
1486
|
end
|
|
1414
1487
|
|
|
1415
1488
|
def script_mkdir_cd(path)
|
|
@@ -1420,6 +1493,17 @@ if __FILE__ == $0
|
|
|
1420
1493
|
["mkdir -p #{q(path)}", "echo #{q("Using git clone to create this trial from #{uri}.")}", "git clone '#{uri}' #{q(path)}"] + script_cd(path)
|
|
1421
1494
|
end
|
|
1422
1495
|
|
|
1496
|
+
def script_clone_pr(path, uri, pr_id)
|
|
1497
|
+
ref = "pull/#{pr_id}/head"
|
|
1498
|
+
[
|
|
1499
|
+
"mkdir -p #{q(path)}",
|
|
1500
|
+
"echo #{q("Using git clone to create this trial from #{uri} PR ##{pr_id}.")}",
|
|
1501
|
+
"git clone #{q(uri)} #{q(path)}",
|
|
1502
|
+
"git -C #{q(path)} fetch origin #{q(ref)}",
|
|
1503
|
+
"git -C #{q(path)} checkout --detach FETCH_HEAD"
|
|
1504
|
+
] + script_cd(path)
|
|
1505
|
+
end
|
|
1506
|
+
|
|
1423
1507
|
def script_worktree(path, repo = nil)
|
|
1424
1508
|
r = repo ? q(repo) : nil
|
|
1425
1509
|
worktree_cmd = if r
|
|
@@ -1463,7 +1547,7 @@ if __FILE__ == $0
|
|
|
1463
1547
|
"mv #{q(old_name)} #{q(new_name)}",
|
|
1464
1548
|
"echo #{q(new_path)}",
|
|
1465
1549
|
"cd #{q(new_path)}"
|
|
1466
|
-
]
|
|
1550
|
+
] + terminal_rename_commands(new_path)
|
|
1467
1551
|
end
|
|
1468
1552
|
|
|
1469
1553
|
# Return a unique directory name under tries_path by appending -2, -3, ... if needed
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: try-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobi Lutke
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A CLI tool for managing experimental projects. Creates dated directories
|
|
14
14
|
for your tries, with fuzzy search and easy navigation.
|