add worktree shortcut

This commit is contained in:
2026-06-19 14:24:35 +02:00
parent 08fcb93e01
commit 77fb5e7e11
4 changed files with 62 additions and 3 deletions

View File

@@ -146,6 +146,9 @@ enabled = true
[scrollback] [scrollback]
editor_command = "vi {file}" editor_command = "vi {file}"
[worktree]
relative_worktree_path = "./.worktrees"
[env.override] [env.override]
ZELLIJ_SESSION_NAME = "" ZELLIJ_SESSION_NAME = ""
@@ -163,6 +166,7 @@ previous_tab = "ALT+SHIFT+H"
next_tab = "ALT+SHIFT+L" next_tab = "ALT+SHIFT+L"
open_font_selector = "ALT+T" open_font_selector = "ALT+T"
open_scrollback = "ALT+S" open_scrollback = "ALT+S"
open_worktree = "ALT+W"
``` ```
## Defaults ## Defaults
@@ -178,6 +182,8 @@ open_scrollback = "ALT+S"
- `Alt+Shift+h` / `Alt+Shift+l`: previous / next tab - `Alt+Shift+h` / `Alt+Shift+l`: previous / next tab
- `Alt+t`: open the font selector - `Alt+t`: open the font selector
- `Alt+s`: open the active pane scrollback in `$EDITOR` - `Alt+s`: open the active pane scrollback in `$EDITOR`
- `Alt+w`: edit a worktree name, then run `git worktree add <relative_worktree_path>/<name>`
from the previously focused pane's working directory
- Font default: `JetBrainsMono Nerd Font` - Font default: `JetBrainsMono Nerd Font`
- Kitty graphics protocol parsing is enabled by default - Kitty graphics protocol parsing is enabled by default

View File

@@ -19,6 +19,9 @@ enabled = true
[scrollback] [scrollback]
editor_command = "vi {file}" editor_command = "vi {file}"
[worktree]
relative_worktree_path = "./.worktrees"
[env.override] [env.override]
ZELLIJ_SESSION_NAME = "" ZELLIJ_SESSION_NAME = ""
@@ -28,9 +31,14 @@ navigate_down = "ALT+J"
navigate_up = "ALT+K" navigate_up = "ALT+K"
navigate_right = "ALT+L" navigate_right = "ALT+L"
toggle_floating = "ALT+F" toggle_floating = "ALT+F"
new_floating = "ALT+SHIFT+F" new_pane = "ALT+N"
next_floating = "ALT+F12" next_floating = "ALT+F12"
promote_floating = "ALT+P"
close_pane = "ALT+X" close_pane = "ALT+X"
new_tab = "ALT+A"
previous_tab = "ALT+SHIFT+H"
next_tab = "ALT+SHIFT+L"
open_font_selector = "ALT+T" open_font_selector = "ALT+T"
open_scrollback = "ALT+S" open_scrollback = "ALT+S"
open_worktree = "ALT+W"
paste = "CTRL+SHIFT+V" paste = "CTRL+SHIFT+V"

View File

@@ -29,6 +29,7 @@ public record AppConfig(
double windowHeight, double windowHeight,
boolean kittyGraphics, boolean kittyGraphics,
String scrollbackEditorCommand, String scrollbackEditorCommand,
String worktreeRelativePath,
String closeSignal, String closeSignal,
Map<String, String> envOverride, Map<String, String> envOverride,
Map<String, KeyBinding> keybindings Map<String, KeyBinding> keybindings
@@ -48,6 +49,7 @@ public record AppConfig(
"next_tab", "next_tab",
"open_font_selector", "open_font_selector",
"open_scrollback", "open_scrollback",
"open_worktree",
"paste" "paste"
); );
@@ -72,6 +74,7 @@ public record AppConfig(
doubleValue(document, "window.height", defaults.windowHeight), doubleValue(document, "window.height", defaults.windowHeight),
booleanValue(document, "kitty_graphics.enabled", defaults.kittyGraphics), booleanValue(document, "kitty_graphics.enabled", defaults.kittyGraphics),
stringValue(document, "scrollback.editor_command", defaults.scrollbackEditorCommand), stringValue(document, "scrollback.editor_command", defaults.scrollbackEditorCommand),
stringValue(document, "worktree.relative_worktree_path", defaults.worktreeRelativePath),
closeSignalValue(document, defaults.closeSignal), closeSignalValue(document, defaults.closeSignal),
envOverride(document, defaults.envOverride), envOverride(document, defaults.envOverride),
keybindings(document, defaults) keybindings(document, defaults)
@@ -94,6 +97,7 @@ public record AppConfig(
760.0, 760.0,
true, true,
defaultScrollbackEditorCommand(), defaultScrollbackEditorCommand(),
"./.worktrees",
"SIGTERM", "SIGTERM",
Map.of(), Map.of(),
Map.ofEntries( Map.ofEntries(
@@ -111,6 +115,7 @@ public record AppConfig(
Map.entry("next_tab", KeyBinding.parse("ALT+SHIFT+L")), Map.entry("next_tab", KeyBinding.parse("ALT+SHIFT+L")),
Map.entry("open_font_selector", KeyBinding.parse("ALT+T")), Map.entry("open_font_selector", KeyBinding.parse("ALT+T")),
Map.entry("open_scrollback", KeyBinding.parse("ALT+S")), Map.entry("open_scrollback", KeyBinding.parse("ALT+S")),
Map.entry("open_worktree", KeyBinding.parse("ALT+W")),
Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V")) Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V"))
) )
); );
@@ -128,6 +133,7 @@ public record AppConfig(
windowHeight, windowHeight,
kittyGraphics, kittyGraphics,
scrollbackEditorCommand, scrollbackEditorCommand,
worktreeRelativePath,
closeSignal, closeSignal,
envOverride, envOverride,
keybindings keybindings
@@ -230,6 +236,8 @@ public record AppConfig(
builder.append("enabled = ").append(kittyGraphics).append("\n\n"); builder.append("enabled = ").append(kittyGraphics).append("\n\n");
builder.append("[scrollback]\n"); builder.append("[scrollback]\n");
builder.append("editor_command = ").append(quoted(scrollbackEditorCommand)).append("\n\n"); builder.append("editor_command = ").append(quoted(scrollbackEditorCommand)).append("\n\n");
builder.append("[worktree]\n");
builder.append("relative_worktree_path = ").append(quoted(worktreeRelativePath)).append("\n\n");
builder.append("[env.override]\n"); builder.append("[env.override]\n");
for (Map.Entry<String, String> entry : envOverride.entrySet()) { for (Map.Entry<String, String> entry : envOverride.entrySet()) {
builder.append(entry.getKey()).append(" = ").append(quoted(entry.getValue())).append('\n'); builder.append(entry.getKey()).append(" = ").append(quoted(entry.getValue())).append('\n');

View File

@@ -71,6 +71,7 @@ final class TerminalWindow {
keyActions.put("next_tab", compositor::nextTab); keyActions.put("next_tab", compositor::nextTab);
keyActions.put("open_font_selector", this::openFontSelector); keyActions.put("open_font_selector", this::openFontSelector);
keyActions.put("open_scrollback", this::openScrollbackInEditor); keyActions.put("open_scrollback", this::openScrollbackInEditor);
keyActions.put("open_worktree", this::openWorktreeInEditor);
keyActions.put("paste", this::pasteFromClipboard); keyActions.put("paste", this::pasteFromClipboard);
StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay()); StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay());
@@ -230,13 +231,30 @@ final class TerminalWindow {
// — no shell startup/rc race — and the pane auto-closes when the editor exits. The // — no shell startup/rc race — and the pane auto-closes when the editor exits. The
// trailing rm removes the file (which holds terminal contents) when the editor exits; // trailing rm removes the file (which holds terminal contents) when the editor exits;
// deleteOnExit would leak files for the JVM's whole lifetime in daemon mode. // deleteOnExit would leak files for the JVM's whole lifetime in daemon mode.
compositor.openFloatingPane(scrollbackEditorCommand(file) + "; rm -f " + shellQuote(file.toString())); compositor.openFloatingPane(editorCommand(file) + "; rm -f " + shellQuote(file.toString()));
} catch (IOException ex) { } catch (IOException ex) {
System.err.println("Could not open scrollback in editor: " + ex.getMessage()); System.err.println("Could not open scrollback in editor: " + ex.getMessage());
} }
} }
private String scrollbackEditorCommand(Path file) { private void openWorktreeInEditor() {
// The floating pane command inherits the active pane's cwd at creation time, so the git
// worktree command runs from the pane that was focused before this shortcut opened.
TerminalPane active = compositor.activePane();
if (active == null) {
return;
}
try {
Path file = Files.createTempFile("jprototerm-worktree-", ".txt");
Files.writeString(file, "");
compositor.openFloatingPane(worktreeEditorCommand(file));
} catch (IOException ex) {
System.err.println("Could not open worktree editor: " + ex.getMessage());
}
}
private String editorCommand(Path file) {
String quotedFile = shellQuote(file.toString()); String quotedFile = shellQuote(file.toString());
String command = config.scrollbackEditorCommand(); String command = config.scrollbackEditorCommand();
if (command == null || command.isBlank()) { if (command == null || command.isBlank()) {
@@ -248,6 +266,25 @@ final class TerminalWindow {
return command + " " + quotedFile; return command + " " + quotedFile;
} }
private String worktreeEditorCommand(Path file) {
String quotedFile = shellQuote(file.toString());
String relativePath = config.worktreeRelativePath();
if (relativePath == null || relativePath.isBlank()) {
relativePath = "./.worktrees";
}
return editorCommand(file)
+ "; editor_status=$?"
+ "; name=$(cat " + quotedFile + ")"
+ "; if [ \"$editor_status\" -eq 0 ] && [ -n \"$name\" ]; then"
+ " git worktree add " + shellQuote(relativePath) + "/\"$name\""
+ "; git_status=$?"
+ "; else git_status=$editor_status"
+ "; fi"
+ "; rm -f " + quotedFile
+ "; exit \"$git_status\"";
}
private static String shellQuote(String value) { private static String shellQuote(String value) {
return "'" + value.replace("'", "'\"'\"'") + "'"; return "'" + value.replace("'", "'\"'\"'") + "'";
} }