diff --git a/.gradle/9.4.1/checksums/checksums.lock b/.gradle/9.4.1/checksums/checksums.lock index 161de06..c0e6cae 100644 Binary files a/.gradle/9.4.1/checksums/checksums.lock and b/.gradle/9.4.1/checksums/checksums.lock differ diff --git a/.gradle/9.4.1/executionHistory/executionHistory.bin b/.gradle/9.4.1/executionHistory/executionHistory.bin index a02dae7..b5389f5 100644 Binary files a/.gradle/9.4.1/executionHistory/executionHistory.bin and b/.gradle/9.4.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/9.4.1/executionHistory/executionHistory.lock b/.gradle/9.4.1/executionHistory/executionHistory.lock index f60a6bf..ffbd1cb 100644 Binary files a/.gradle/9.4.1/executionHistory/executionHistory.lock and b/.gradle/9.4.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/9.4.1/fileHashes/fileHashes.bin b/.gradle/9.4.1/fileHashes/fileHashes.bin index d7b6ec4..c9949a8 100644 Binary files a/.gradle/9.4.1/fileHashes/fileHashes.bin and b/.gradle/9.4.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/9.4.1/fileHashes/fileHashes.lock b/.gradle/9.4.1/fileHashes/fileHashes.lock index 7040cb8..fc51bc1 100644 Binary files a/.gradle/9.4.1/fileHashes/fileHashes.lock and b/.gradle/9.4.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/9.4.1/fileHashes/resourceHashesCache.bin b/.gradle/9.4.1/fileHashes/resourceHashesCache.bin index 0c7030b..2b8e79c 100644 Binary files a/.gradle/9.4.1/fileHashes/resourceHashesCache.bin and b/.gradle/9.4.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index f866744..f41612d 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/README.md b/README.md index a9269bb..0fb3853 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,9 @@ height = 760 [kitty_graphics] enabled = true +[scrollback] +editor_command = "vi {file}" + [keybindings] navigate_left = "ALT+H" navigate_down = "ALT+J" @@ -63,6 +66,7 @@ new_floating = "ALT+SHIFT+F" next_floating = "ALT+F12" close_pane = "ALT+X" open_font_selector = "ALT+T" +open_scrollback = "ALT+S" ``` ## Defaults @@ -73,5 +77,6 @@ open_font_selector = "ALT+T" - `Alt+F12`: cycle floating panes - `Alt+x`: close the active floating pane - `Alt+t`: open the font selector +- `Alt+s`: open the active pane scrollback in `$EDITOR` - Font default: `JetBrainsMono Nerd Font` - Kitty graphics protocol parsing is enabled by default diff --git a/config.example.toml b/config.example.toml index 4f262c9..8e172df 100644 --- a/config.example.toml +++ b/config.example.toml @@ -13,6 +13,9 @@ height = 760 [kitty_graphics] enabled = true +[scrollback] +editor_command = "vi {file}" + [keybindings] navigate_left = "ALT+H" navigate_down = "ALT+J" @@ -23,3 +26,4 @@ new_floating = "ALT+SHIFT+F" next_floating = "ALT+F12" close_pane = "ALT+X" open_font_selector = "ALT+T" +open_scrollback = "ALT+S" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index af6f1e1..745a494 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -25,6 +25,7 @@ public record AppConfig( double windowWidth, double windowHeight, boolean kittyGraphics, + String scrollbackEditorCommand, Map keybindings ) { private static final List KEYBINDING_KEYS = List.of( @@ -36,7 +37,8 @@ public record AppConfig( "new_floating", "next_floating", "close_pane", - "open_font_selector" + "open_font_selector", + "open_scrollback" ); public static AppConfig load() { @@ -59,6 +61,7 @@ public record AppConfig( doubleValue(document, "window.width", defaults.windowWidth), doubleValue(document, "window.height", defaults.windowHeight), booleanValue(document, "kitty_graphics.enabled", defaults.kittyGraphics), + stringValue(document, "scrollback.editor_command", defaults.scrollbackEditorCommand), keybindings(document, defaults) ); } catch (TomlException ex) { @@ -78,6 +81,7 @@ public record AppConfig( 1200.0, 760.0, true, + defaultScrollbackEditorCommand(), Map.of( "navigate_left", KeyBinding.parse("ALT+H"), "navigate_down", KeyBinding.parse("ALT+J"), @@ -87,7 +91,8 @@ public record AppConfig( "new_floating", KeyBinding.parse("ALT+SHIFT+F"), "next_floating", KeyBinding.parse("ALT+F12"), "close_pane", KeyBinding.parse("ALT+X"), - "open_font_selector", KeyBinding.parse("ALT+T") + "open_font_selector", KeyBinding.parse("ALT+T"), + "open_scrollback", KeyBinding.parse("ALT+S") ) ); } @@ -103,6 +108,7 @@ public record AppConfig( windowWidth, windowHeight, kittyGraphics, + scrollbackEditorCommand, keybindings ); } @@ -123,6 +129,14 @@ public record AppConfig( return "/bin/bash"; } + private static String defaultScrollbackEditorCommand() { + String editor = System.getenv("EDITOR"); + if (editor == null || editor.isBlank()) { + editor = "vi"; + } + return editor.trim() + " {file}"; + } + private static Map keybindings(TomlTable table, AppConfig defaults) { Map parsed = new LinkedHashMap<>(); for (String key : KEYBINDING_KEYS) { @@ -167,6 +181,8 @@ public record AppConfig( builder.append("height = ").append(trimDouble(windowHeight)).append("\n\n"); builder.append("[kitty_graphics]\n"); builder.append("enabled = ").append(kittyGraphics).append("\n\n"); + builder.append("[scrollback]\n"); + builder.append("editor_command = ").append(quoted(scrollbackEditorCommand)).append("\n\n"); builder.append("[keybindings]\n"); for (String key : KEYBINDING_KEYS) { KeyBinding binding = keybindings.get(key); diff --git a/src/main/java/com/gregor/jprototerm/Main.java b/src/main/java/com/gregor/jprototerm/Main.java index c2ac771..a4c8816 100644 --- a/src/main/java/com/gregor/jprototerm/Main.java +++ b/src/main/java/com/gregor/jprototerm/Main.java @@ -15,6 +15,10 @@ import javafx.scene.layout.StackPane; import javafx.scene.text.Font; import javafx.stage.Stage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + public final class Main extends Application { private TerminalWorkspace workspace; private TerminalCanvasView terminalView; @@ -79,6 +83,9 @@ public final class Main extends Application { } else if (config.keybindings().get("open_font_selector").matches(event)) { openFontSelector(); event.consume(); + } else if (config.keybindings().get("open_scrollback").matches(event)) { + openScrollbackInEditor(); + event.consume(); } else { String encoded = KeyEncoder.encode(event); if (encoded != null) { @@ -143,6 +150,34 @@ public final class Main extends Application { }); } + private void openScrollbackInEditor() { + try { + Path file = Files.createTempFile("jprototerm-scrollback-", ".txt"); + Files.writeString(file, workspace.activePane().scrollbackText()); + file.toFile().deleteOnExit(); + + workspace.activePane().send(scrollbackEditorCommand(file) + "\r"); + } catch (IOException ex) { + System.err.println("Could not open scrollback in editor: " + ex.getMessage()); + } + } + + private String scrollbackEditorCommand(Path file) { + String quotedFile = shellQuote(file.toString()); + String command = config.scrollbackEditorCommand(); + if (command == null || command.isBlank()) { + command = "vi {file}"; + } + if (command.contains("{file}")) { + return command.replace("{file}", quotedFile); + } + return command + " " + quotedFile; + } + + private static String shellQuote(String value) { + return "'" + value.replace("'", "'\"'\"'") + "'"; + } + public static void main(String[] args) { System.setProperty("prism.order", System.getProperty("prism.order", "es2,sw")); launch(Main.class, args); diff --git a/src/main/java/com/gregor/jprototerm/TerminalPane.java b/src/main/java/com/gregor/jprototerm/TerminalPane.java index 3166f10..16fb371 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPane.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPane.java @@ -115,6 +115,12 @@ public final class TerminalPane implements AutoCloseable { return renderSnapshot.get(); } + public String scrollbackText() { + synchronized (terminal) { + return terminal.text(); + } + } + public long renderVersion() { return renderVersion; }