Compare commits
9 Commits
7367b2f778
...
thinner-sy
| Author | SHA1 | Date | |
|---|---|---|---|
| 86dd79d5b1 | |||
| 2c4b89f8f7 | |||
| cff4fba81c | |||
| 5a50ce62f3 | |||
| 27b7ba6904 | |||
| 7b3a370371 | |||
| 4e2ed7f99f | |||
| b8a4f52609 | |||
| c922968376 |
15
README.md
15
README.md
@@ -151,6 +151,11 @@ editor_command = "vi {file}"
|
|||||||
|
|
||||||
[worktree]
|
[worktree]
|
||||||
relative_worktree_path = "./.worktrees"
|
relative_worktree_path = "./.worktrees"
|
||||||
|
split_regex = ","
|
||||||
|
# One of: "none", "cd", "create_panes", "create_panes_floating".
|
||||||
|
post_create_action = "none"
|
||||||
|
commands = []
|
||||||
|
sync_panes = false
|
||||||
|
|
||||||
[env.override]
|
[env.override]
|
||||||
ZELLIJ_SESSION_NAME = ""
|
ZELLIJ_SESSION_NAME = ""
|
||||||
@@ -188,8 +193,14 @@ paste = "CTRL+SHIFT+V"
|
|||||||
- `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>`
|
- `Alt+w`: edit one or more worktree names, split by `worktree.split_regex`, then run
|
||||||
from the previously focused pane's working directory
|
`git worktree add <relative_worktree_path>/<name>` for each name from the previously focused
|
||||||
|
pane's working directory. `worktree.post_create_action` can then do nothing with `none`, `cd` the
|
||||||
|
previously active pane to the last created worktree, create one tiled pane per worktree with
|
||||||
|
`create_panes`, or create one floating pane per worktree with `create_panes_floating`.
|
||||||
|
`worktree.commands`, for example `["npm install", "git status"]`, can run commands in created
|
||||||
|
panes; commands are assigned in order and repeat when fewer commands than panes are created.
|
||||||
|
`worktree.sync_panes = true` syncs those created panes after the configured commands are sent.
|
||||||
- `Alt+y`: enter pane-sync selection mode, commit the selection, or stop an active pane sync
|
- `Alt+y`: enter pane-sync selection mode, commit the selection, or stop an active pane sync
|
||||||
- `Space`: toggle the focused pane in the sync set while pane-sync selection mode is active
|
- `Space`: toggle the focused pane in the sync set while pane-sync selection mode is active
|
||||||
- Once committed, input typed or pasted into any synced pane is mirrored to the other synced panes
|
- Once committed, input typed or pasted into any synced pane is mirrored to the other synced panes
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ editor_command = "vi {file}"
|
|||||||
|
|
||||||
[worktree]
|
[worktree]
|
||||||
relative_worktree_path = "./.worktrees"
|
relative_worktree_path = "./.worktrees"
|
||||||
|
split_regex = ","
|
||||||
|
# One of: "none", "cd", "create_panes", "create_panes_floating".
|
||||||
|
post_create_action = "none"
|
||||||
|
commands = []
|
||||||
|
sync_panes = false
|
||||||
|
|
||||||
[env.override]
|
[env.override]
|
||||||
ZELLIJ_SESSION_NAME = ""
|
ZELLIJ_SESSION_NAME = ""
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ public record AppConfig(
|
|||||||
boolean kittyGraphics,
|
boolean kittyGraphics,
|
||||||
String scrollbackEditorCommand,
|
String scrollbackEditorCommand,
|
||||||
String worktreeRelativePath,
|
String worktreeRelativePath,
|
||||||
|
String worktreeSplitRegex,
|
||||||
|
String worktreePostCreateAction,
|
||||||
|
List<String> worktreeCommands,
|
||||||
|
boolean worktreeSyncPanes,
|
||||||
String closeSignal,
|
String closeSignal,
|
||||||
Map<String, String> envOverride,
|
Map<String, String> envOverride,
|
||||||
Map<String, KeyBinding> keybindings
|
Map<String, KeyBinding> keybindings
|
||||||
@@ -77,6 +81,10 @@ public record AppConfig(
|
|||||||
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),
|
stringValue(document, "worktree.relative_worktree_path", defaults.worktreeRelativePath),
|
||||||
|
stringValue(document, "worktree.split_regex", defaults.worktreeSplitRegex),
|
||||||
|
stringValue(document, "worktree.post_create_action", defaults.worktreePostCreateAction),
|
||||||
|
stringListValue(document, "worktree.commands", defaults.worktreeCommands),
|
||||||
|
booleanValue(document, "worktree.sync_panes", defaults.worktreeSyncPanes),
|
||||||
closeSignalValue(document, defaults.closeSignal),
|
closeSignalValue(document, defaults.closeSignal),
|
||||||
envOverride(document, defaults.envOverride),
|
envOverride(document, defaults.envOverride),
|
||||||
keybindings(document, defaults)
|
keybindings(document, defaults)
|
||||||
@@ -100,6 +108,10 @@ public record AppConfig(
|
|||||||
true,
|
true,
|
||||||
defaultScrollbackEditorCommand(),
|
defaultScrollbackEditorCommand(),
|
||||||
"./.worktrees",
|
"./.worktrees",
|
||||||
|
",",
|
||||||
|
"none",
|
||||||
|
List.of(),
|
||||||
|
false,
|
||||||
"SIGTERM",
|
"SIGTERM",
|
||||||
Map.of(),
|
Map.of(),
|
||||||
Map.ofEntries(
|
Map.ofEntries(
|
||||||
@@ -138,6 +150,10 @@ public record AppConfig(
|
|||||||
kittyGraphics,
|
kittyGraphics,
|
||||||
scrollbackEditorCommand,
|
scrollbackEditorCommand,
|
||||||
worktreeRelativePath,
|
worktreeRelativePath,
|
||||||
|
worktreeSplitRegex,
|
||||||
|
worktreePostCreateAction,
|
||||||
|
worktreeCommands,
|
||||||
|
worktreeSyncPanes,
|
||||||
closeSignal,
|
closeSignal,
|
||||||
envOverride,
|
envOverride,
|
||||||
keybindings
|
keybindings
|
||||||
@@ -241,7 +257,11 @@ public record AppConfig(
|
|||||||
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("[worktree]\n");
|
||||||
builder.append("relative_worktree_path = ").append(quoted(worktreeRelativePath)).append("\n\n");
|
builder.append("relative_worktree_path = ").append(quoted(worktreeRelativePath)).append('\n');
|
||||||
|
builder.append("split_regex = ").append(quoted(worktreeSplitRegex)).append('\n');
|
||||||
|
builder.append("post_create_action = ").append(quoted(worktreePostCreateAction)).append('\n');
|
||||||
|
builder.append("commands = ").append(quotedList(worktreeCommands)).append('\n');
|
||||||
|
builder.append("sync_panes = ").append(worktreeSyncPanes).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');
|
||||||
|
|||||||
@@ -188,6 +188,19 @@ public final class Compositor {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void syncPanes(List<TerminalPane> panes) {
|
||||||
|
paneSyncSelectMode = false;
|
||||||
|
paneSyncSelection.clear();
|
||||||
|
paneSyncPanes.clear();
|
||||||
|
for (TerminalPane pane : panes) {
|
||||||
|
if (pane != null) {
|
||||||
|
paneSyncPanes.add(pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prunePaneSyncState();
|
||||||
|
layoutVersion++;
|
||||||
|
}
|
||||||
|
|
||||||
public void toggleFloating() {
|
public void toggleFloating() {
|
||||||
mutateCurrentTab(() -> currentTab().toggleFloating());
|
mutateCurrentTab(() -> currentTab().toggleFloating());
|
||||||
}
|
}
|
||||||
@@ -196,6 +209,24 @@ public final class Compositor {
|
|||||||
mutateCurrentTab(() -> currentTab().createPane());
|
mutateCurrentTab(() -> currentTab().createPane());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TerminalPane createTiledPane(String workingDirectory) {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TerminalPane pane = currentTab().createTiledPane(workingDirectory);
|
||||||
|
layoutVersion++;
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalPane createFloatingPaneInDirectory(String workingDirectory) {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TerminalPane pane = currentTab().createFloatingPaneInDirectory(workingDirectory);
|
||||||
|
layoutVersion++;
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a floating pane running {@code command} directly (auto-closing when it exits), makes it
|
* Opens a floating pane running {@code command} directly (auto-closing when it exits), makes it
|
||||||
* active, and returns it (null when no tab exists).
|
* active, and returns it (null when no tab exists).
|
||||||
@@ -499,17 +530,22 @@ public final class Compositor {
|
|||||||
|
|
||||||
gc.save();
|
gc.save();
|
||||||
try {
|
try {
|
||||||
gc.setLineWidth(4.0);
|
// Draw a thin sync border inset just inside the pane's own edge border, so the
|
||||||
|
// renderer's active (blue) / inactive border stays visible and you can still tell
|
||||||
|
// which synced pane is focused.
|
||||||
|
double lineWidth = 2.0;
|
||||||
|
double inset = 2.0 + lineWidth / 2.0; // clear the renderer's 2px active border
|
||||||
|
gc.setLineWidth(lineWidth);
|
||||||
gc.setStroke(paneSyncSelectMode ? PANE_SYNC_SELECT_BORDER : PANE_SYNC_COMMITTED_BORDER);
|
gc.setStroke(paneSyncSelectMode ? PANE_SYNC_SELECT_BORDER : PANE_SYNC_COMMITTED_BORDER);
|
||||||
for (TerminalPane pane : panes) {
|
for (TerminalPane pane : panes) {
|
||||||
if (!highlighted.contains(pane)) {
|
if (!highlighted.contains(pane)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
gc.save();
|
gc.save();
|
||||||
double x = Math.round(pane.x()) + 2.0;
|
double x = Math.round(pane.x()) + inset;
|
||||||
double y = Math.round(pane.y()) + 2.0;
|
double y = Math.round(pane.y()) + inset;
|
||||||
double width = Math.max(0.0, pane.width() - 4.0);
|
double width = Math.max(0.0, pane.width() - 2.0 * inset);
|
||||||
double height = Math.max(0.0, pane.height() - 4.0);
|
double height = Math.max(0.0, pane.height() - 2.0 * inset);
|
||||||
TerminalRenderer.clip(gc, Math.round(pane.x()), Math.round(pane.y()), pane.width(), pane.height(), pane.clip());
|
TerminalRenderer.clip(gc, Math.round(pane.x()), Math.round(pane.y()), pane.width(), pane.height(), pane.clip());
|
||||||
gc.strokeRect(x, y, width, height);
|
gc.strokeRect(x, y, width, height);
|
||||||
gc.restore();
|
gc.restore();
|
||||||
|
|||||||
@@ -237,12 +237,21 @@ final class Tab implements AutoCloseable {
|
|||||||
if (floatingVisible) {
|
if (floatingVisible) {
|
||||||
createFloatingPane();
|
createFloatingPane();
|
||||||
} else {
|
} else {
|
||||||
TerminalPane pane = openPane(false);
|
createTiledPane(paneWorkingDirectory());
|
||||||
tiled.add(pane);
|
|
||||||
setActive(pane);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TerminalPane createTiledPane(String workingDirectory) {
|
||||||
|
TerminalPane pane = openPane(false, workingDirectory);
|
||||||
|
tiled.add(pane);
|
||||||
|
setActive(pane);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalPane createFloatingPaneInDirectory(String workingDirectory) {
|
||||||
|
return addFloating(openPane(true, workingDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
void nextFloatingPane() {
|
void nextFloatingPane() {
|
||||||
if (floating.isEmpty()) {
|
if (floating.isEmpty()) {
|
||||||
createFloatingPane();
|
createFloatingPane();
|
||||||
@@ -334,7 +343,7 @@ final class Tab implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createFloatingPane() {
|
private void createFloatingPane() {
|
||||||
addFloating(openPane(true));
|
addFloating(openPane(true, paneWorkingDirectory()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -386,9 +395,13 @@ final class Tab implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TerminalPane openPane(boolean asFloating) {
|
private TerminalPane openPane(boolean asFloating) {
|
||||||
|
return openPane(asFloating, paneWorkingDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalPane openPane(boolean asFloating, String workingDirectory) {
|
||||||
double[] size = paneSize(asFloating);
|
double[] size = paneSize(asFloating);
|
||||||
return register(TerminalPane.create(
|
return register(TerminalPane.create(
|
||||||
config, metrics, this::markContentChanged, size[0], size[1], paneWorkingDirectory()));
|
config, metrics, this::markContentChanged, size[0], size[1], workingDirectory));
|
||||||
}
|
}
|
||||||
|
|
||||||
private double[] paneSize(boolean asFloating) {
|
private double[] paneSize(boolean asFloating) {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import javafx.application.Platform;
|
|||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
import javafx.scene.shape.Shape;
|
import javafx.scene.shape.Shape;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -42,8 +44,8 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
|||||||
private RenderStateSnapshot cachedSnapshot;
|
private RenderStateSnapshot cachedSnapshot;
|
||||||
private volatile ShellSession session;
|
private volatile ShellSession session;
|
||||||
// Run once (on the FX thread) when this pane's process exits on its own, so the owning tab can
|
// Run once (on the FX thread) when this pane's process exits on its own, so the owning tab can
|
||||||
// remove it. Set by the Tab that creates the pane; null until then.
|
// remove it and command panes can run follow-up actions.
|
||||||
private Runnable onExit;
|
private final List<Runnable> onExitHandlers = new ArrayList<>();
|
||||||
private boolean exited;
|
private boolean exited;
|
||||||
// Clip region for rendering (rect minus the panes covering this one), set at layout time;
|
// Clip region for rendering (rect minus the panes covering this one), set at layout time;
|
||||||
// null means clip to the plain bounds. See RenderTarget#clip().
|
// null means clip to the plain bounds. See RenderTarget#clip().
|
||||||
@@ -125,12 +127,20 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
|||||||
|
|
||||||
/** Sets the callback run when this pane's process exits on its own (see {@link #handleSessionExit}). */
|
/** Sets the callback run when this pane's process exits on its own (see {@link #handleSessionExit}). */
|
||||||
public void setOnExit(Runnable onExit) {
|
public void setOnExit(Runnable onExit) {
|
||||||
this.onExit = onExit;
|
onExitHandlers.clear();
|
||||||
|
addOnExit(onExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a callback run when this pane's process exits on its own (see {@link #handleSessionExit}). */
|
||||||
|
public void addOnExit(Runnable onExit) {
|
||||||
|
if (onExit != null) {
|
||||||
|
onExitHandlers.add(onExit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the shell reader thread when the pty stream ends without us closing it (the
|
* Called from the shell reader thread when the pty stream ends without us closing it (the
|
||||||
* process exited). Hops to the FX thread and fires {@link #onExit} once, so tab/compositor
|
* process exited). Hops to the FX thread and fires the exit handlers once, so tab/compositor
|
||||||
* mutation happens on the thread that owns the layout.
|
* mutation happens on the thread that owns the layout.
|
||||||
*/
|
*/
|
||||||
void handleSessionExit() {
|
void handleSessionExit() {
|
||||||
@@ -139,8 +149,8 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exited = true;
|
exited = true;
|
||||||
if (onExit != null) {
|
for (Runnable handler : List.copyOf(onExitHandlers)) {
|
||||||
onExit.run();
|
handler.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ import javafx.stage.Stage;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -287,9 +289,15 @@ final class TerminalWindow {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Path file = Files.createTempFile("jprototerm-worktree-", ".txt");
|
Path file = Files.createTempFile("jprototerm-worktree-", ".txt");
|
||||||
|
Path createdFile = Files.createTempFile("jprototerm-worktree-created-", ".txt");
|
||||||
Files.writeString(file, "");
|
Files.writeString(file, "");
|
||||||
|
|
||||||
compositor.openFloatingPane(worktreeEditorCommand(file));
|
TerminalPane commandPane = compositor.openFloatingPane(worktreeEditorCommand(file, createdFile));
|
||||||
|
if (commandPane != null) {
|
||||||
|
commandPane.addOnExit(() -> runPostWorktreeAction(active, createdFile));
|
||||||
|
} else {
|
||||||
|
Files.deleteIfExists(createdFile);
|
||||||
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
System.err.println("Could not create worktree from editor input: " + ex.getMessage());
|
System.err.println("Could not create worktree from editor input: " + ex.getMessage());
|
||||||
}
|
}
|
||||||
@@ -307,25 +315,112 @@ final class TerminalWindow {
|
|||||||
return command + " " + quotedFile;
|
return command + " " + quotedFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String worktreeEditorCommand(Path file) {
|
private String worktreeEditorCommand(Path file, Path createdFile) {
|
||||||
String quotedFile = shellQuote(file.toString());
|
String quotedFile = shellQuote(file.toString());
|
||||||
|
String quotedCreatedFile = shellQuote(createdFile.toString());
|
||||||
String relativePath = config.worktreeRelativePath();
|
String relativePath = config.worktreeRelativePath();
|
||||||
if (relativePath == null || relativePath.isBlank()) {
|
if (relativePath == null || relativePath.isBlank()) {
|
||||||
relativePath = "./.worktrees";
|
relativePath = "./.worktrees";
|
||||||
}
|
}
|
||||||
|
String splitRegex = config.worktreeSplitRegex();
|
||||||
|
if (splitRegex == null || splitRegex.isBlank()) {
|
||||||
|
splitRegex = ",";
|
||||||
|
}
|
||||||
|
|
||||||
return editorCommand(file)
|
return editorCommand(file)
|
||||||
+ "; editor_status=$?"
|
+ "; editor_status=$?"
|
||||||
+ "; name=$(cat " + quotedFile + ")"
|
+ "; git_status=$editor_status"
|
||||||
+ "; if [ \"$editor_status\" -eq 0 ] && [ -n \"$name\" ]; then"
|
+ "; if [ \"$editor_status\" -eq 0 ]; then"
|
||||||
+ " git worktree add " + shellQuote(relativePath) + "/\"$name\""
|
+ " if names_file=$(mktemp); then"
|
||||||
+ "; git_status=$?"
|
+ " if awk -v re=" + shellQuote(splitRegex)
|
||||||
+ "; else git_status=$editor_status"
|
+ " '{ text = text $0 \"\\n\" }"
|
||||||
|
+ " END { n = split(text, names, re); for (i = 1; i <= n; i++)"
|
||||||
|
+ " { name = names[i]; sub(/^[[:space:]]+/, \"\", name);"
|
||||||
|
+ " sub(/[[:space:]]+$/, \"\", name); if (name != \"\") print name; } }'"
|
||||||
|
+ " " + quotedFile + " > \"$names_file\"; then"
|
||||||
|
+ " git_status=0"
|
||||||
|
+ "; while IFS= read -r name; do"
|
||||||
|
+ " worktree_path=" + shellQuote(relativePath) + "/\"$name\""
|
||||||
|
+ "; git worktree add \"$worktree_path\""
|
||||||
|
+ " || { git_status=$?; break; }"
|
||||||
|
+ "; created_path=$(cd \"$worktree_path\" && pwd -P)"
|
||||||
|
+ " || { git_status=$?; break; }"
|
||||||
|
+ "; printf '%s\\n' \"$created_path\" >> " + quotedCreatedFile
|
||||||
|
+ " || { git_status=$?; break; }"
|
||||||
|
+ "; done < \"$names_file\""
|
||||||
|
+ "; else git_status=$?"
|
||||||
|
+ "; fi"
|
||||||
|
+ "; rm -f \"$names_file\""
|
||||||
|
+ "; else git_status=$?"
|
||||||
|
+ "; fi"
|
||||||
+ "; fi"
|
+ "; fi"
|
||||||
+ "; rm -f " + quotedFile
|
+ "; rm -f " + quotedFile
|
||||||
+ "; exit \"$git_status\"";
|
+ "; exit \"$git_status\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runPostWorktreeAction(TerminalPane lastActivePane, Path createdFile) {
|
||||||
|
List<String> worktreePaths = readCreatedWorktreePaths(createdFile);
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(createdFile);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.err.println("Could not remove worktree result file " + createdFile + ": " + ex.getMessage());
|
||||||
|
}
|
||||||
|
if (worktreePaths.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String action = config.worktreePostCreateAction();
|
||||||
|
if (action == null || action.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.trim().toLowerCase(Locale.ROOT)) {
|
||||||
|
case "none" -> { }
|
||||||
|
case "cd" -> lastActivePane.send("cd " + shellQuote(worktreePaths.get(worktreePaths.size() - 1)) + "\r");
|
||||||
|
case "create_panes" -> createWorktreePanes(worktreePaths, false);
|
||||||
|
case "create_panes_floating" -> createWorktreePanes(worktreePaths, true);
|
||||||
|
default -> System.err.println("Unknown worktree.post_create_action '" + action + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createWorktreePanes(List<String> worktreePaths, boolean floating) {
|
||||||
|
List<String> commands = config.worktreeCommands();
|
||||||
|
List<TerminalPane> createdPanes = new ArrayList<>();
|
||||||
|
for (int i = 0; i < worktreePaths.size(); i++) {
|
||||||
|
TerminalPane pane = floating
|
||||||
|
? compositor.createFloatingPaneInDirectory(worktreePaths.get(i))
|
||||||
|
: compositor.createTiledPane(worktreePaths.get(i));
|
||||||
|
if (pane != null) {
|
||||||
|
createdPanes.add(pane);
|
||||||
|
}
|
||||||
|
if (pane != null && !commands.isEmpty()) {
|
||||||
|
String command = commands.get(i % commands.size());
|
||||||
|
if (command != null && !command.isBlank()) {
|
||||||
|
pane.send(command + "\r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.worktreeSyncPanes() && !createdPanes.isEmpty()) {
|
||||||
|
compositor.syncPanes(createdPanes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> readCreatedWorktreePaths(Path createdFile) {
|
||||||
|
try {
|
||||||
|
List<String> paths = new ArrayList<>();
|
||||||
|
for (String line : Files.readAllLines(createdFile)) {
|
||||||
|
String path = line.trim();
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
paths.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.err.println("Could not read created worktree paths from " + createdFile + ": " + ex.getMessage());
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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