Compare commits
4 Commits
08fcb93e01
...
c922968376
| Author | SHA1 | Date | |
|---|---|---|---|
| c922968376 | |||
| 64263ee3fb | |||
| f621366b0c | |||
| 77fb5e7e11 |
@@ -78,6 +78,9 @@ After that, a bare `jprototerm` connects to the daemon and opens a window in the
|
|||||||
directory. If no daemon is running, `jprototerm` falls back to a standalone in-process window
|
directory. If no daemon is running, `jprototerm` falls back to a standalone in-process window
|
||||||
(today's behavior), so it always works.
|
(today's behavior), so it always works.
|
||||||
|
|
||||||
|
For development testing, use `jprototerm --standalone` to skip the daemon even when one is
|
||||||
|
running.
|
||||||
|
|
||||||
To start the daemon automatically with your graphical session, enable the bundled **user**
|
To start the daemon automatically with your graphical session, enable the bundled **user**
|
||||||
service (it's a user service, not a system one, because X11 needs a display — which only
|
service (it's a user service, not a system one, because X11 needs a display — which only
|
||||||
exists after you log in):
|
exists after you log in):
|
||||||
@@ -146,6 +149,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 +169,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"
|
||||||
|
create_worktree = "ALT+W"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
@@ -178,6 +185,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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
create_worktree = "ALT+W"
|
||||||
paste = "CTRL+SHIFT+V"
|
paste = "CTRL+SHIFT+V"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
"create_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("create_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');
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ package com.gregor.jprototerm;
|
|||||||
* Entry point and mode dispatch. A bare invocation is a thin client: it hands the request to a
|
* Entry point and mode dispatch. A bare invocation is a thin client: it hands the request to a
|
||||||
* running {@link Daemon}, or, if none is reachable, opens a single standalone window in this process
|
* running {@link Daemon}, or, if none is reachable, opens a single standalone window in this process
|
||||||
* (today's behavior). {@code --daemon} runs the long-lived server that hosts every window in one
|
* (today's behavior). {@code --daemon} runs the long-lived server that hosts every window in one
|
||||||
* JVM, so client launches skip cold JVM/JavaFX/GL startup.
|
* JVM, so client launches skip cold JVM/JavaFX/GL startup. {@code --standalone} skips daemon client
|
||||||
|
* mode and always opens an in-process window, which is useful while testing development builds.
|
||||||
*/
|
*/
|
||||||
public final class Main {
|
public final class Main {
|
||||||
private Main() {
|
private Main() {
|
||||||
@@ -14,15 +15,19 @@ public final class Main {
|
|||||||
// Match the renderer order the app was tuned for; honor an explicit override if present.
|
// Match the renderer order the app was tuned for; honor an explicit override if present.
|
||||||
System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw"));
|
System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw"));
|
||||||
|
|
||||||
|
boolean standalone = false;
|
||||||
for (String arg : args) {
|
for (String arg : args) {
|
||||||
if (arg.equals("--daemon")) {
|
if (arg.equals("--daemon")) {
|
||||||
Daemon.run();
|
Daemon.run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (arg.equals("--standalone")) {
|
||||||
|
standalone = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String workingDirectory = System.getProperty("user.dir");
|
String workingDirectory = System.getProperty("user.dir");
|
||||||
if (Daemon.tryClient(workingDirectory)) {
|
if (!standalone && Daemon.tryClient(workingDirectory)) {
|
||||||
return; // a running daemon opened the window
|
return; // a running daemon opened the window
|
||||||
}
|
}
|
||||||
// No daemon reachable: fall back to a standalone window; the JVM exits when it closes.
|
// No daemon reachable: fall back to a standalone window; the JVM exits when it closes.
|
||||||
|
|||||||
@@ -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("create_worktree", this::createWorktreeInEditor);
|
||||||
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 createWorktreeInEditor() {
|
||||||
|
// 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 create worktree from editor input: " + 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("'", "'\"'\"'") + "'";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user