mirror of
https://github.com/xpipe-io/xpipe.git
synced 2025-04-19 02:33:39 +00:00
Fixes
This commit is contained in:
parent
e22b66f2a0
commit
120a463d88
9 changed files with 191 additions and 55 deletions
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.util.CommandSupport;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellScript;
|
||||
import io.xpipe.core.process.TerminalInitScriptConfig;
|
||||
|
@ -27,16 +28,20 @@ public class ScreenTerminalMultiplexer implements TerminalMultiplexer {
|
|||
@Override
|
||||
public ShellScript launchScriptExternal(ShellControl control, String command, TerminalInitScriptConfig config)
|
||||
throws Exception {
|
||||
// Screen has a limit of 100 chars for commands
|
||||
var effectiveCommand = command.length() > 90 ? ScriptHelper.createExecScript(control, command).toString() : command;
|
||||
return ShellScript.lines("screen -S xpipe -X screen -t \"" + escape(config.getDisplayName(), true) + "\" "
|
||||
+ escape(command, false));
|
||||
+ escape(effectiveCommand, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellScript launchScriptSession(ShellControl control, String command, TerminalInitScriptConfig config)
|
||||
throws Exception {
|
||||
// Screen has a limit of 100 chars for commands
|
||||
var effectiveCommand = command.length() > 90 ? ScriptHelper.createExecScript(control, command).toString() : command;
|
||||
return ShellScript.lines(
|
||||
"for scr in $(screen -ls | grep xpipe | awk '{print $1}'); do screen -S $scr -X quit; done",
|
||||
"screen -S xpipe -t \"" + escape(config.getDisplayName(), true) + "\" " + escape(command, false));
|
||||
"screen -S xpipe -t \"" + escape(config.getDisplayName(), true) + "\" " + escape(effectiveCommand, false));
|
||||
}
|
||||
|
||||
private String escape(String s, boolean quotes) {
|
||||
|
|
|
@ -18,7 +18,10 @@ public class TerminalDockModel {
|
|||
|
||||
public synchronized void trackTerminal(ControllableTerminalSession terminal) {
|
||||
terminalInstances.add(terminal);
|
||||
terminal.alwaysInFront();
|
||||
// The main window always loses focus when the terminal is opened,
|
||||
// so only put it in front
|
||||
// If we refocus the main window, it will get put always in front then
|
||||
terminal.frontOfMainWindow();
|
||||
if (viewBounds != null) {
|
||||
terminal.updatePosition(viewBounds);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class TmuxTerminalMultiplexer implements TerminalMultiplexer {
|
|||
"tmux kill-session -t xpipe",
|
||||
"tmux new-session -d -s xpipe",
|
||||
"tmux rename-window \"" + escape(config.getDisplayName(), true) + "\"",
|
||||
"tmux send-keys -t xpipe '" + escape(command, false) + "' Enter",
|
||||
"tmux send-keys -t xpipe '" + escape(command, false) + ";exit' Enter",
|
||||
"tmux attach -d -t xpipe");
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ public class ZellijTerminalMultiplexer implements TerminalMultiplexer {
|
|||
return ShellScript.lines(
|
||||
"zellij attach --create-background xpipe",
|
||||
"zellij -s xpipe action new-tab --name \"" + escape(config.getDisplayName(), false, true) + "\"",
|
||||
"zellij -s xpipe action write-chars -- " + escape(command, true, true),
|
||||
"zellij -s xpipe action write-chars -- " + escape(command, true, true) + "\\;exit",
|
||||
"zellij -s xpipe action write 10");
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public class ZellijTerminalMultiplexer implements TerminalMultiplexer {
|
|||
return ShellScript.lines(
|
||||
"zellij delete-session -f xpipe",
|
||||
"zellij attach --create-background xpipe",
|
||||
"zellij -s xpipe run --name \"" + escape(config.getDisplayName(), false, true) + "\" -- "
|
||||
"zellij -s xpipe run -c --name \"" + escape(config.getDisplayName(), false, true) + "\" -- "
|
||||
+ escape(command, false, false),
|
||||
"zellij attach xpipe");
|
||||
}
|
||||
|
|
68
app/src/main/java/io/xpipe/app/util/CommandDialog.java
Normal file
68
app/src/main/java/io/xpipe/app/util/CommandDialog.java
Normal file
|
@ -0,0 +1,68 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.core.process.CommandControl;
|
||||
import io.xpipe.core.process.ProcessOutputException;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommandDialog {
|
||||
|
||||
public static void runAsyncAndShow(CommandControl cmd) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
run(cmd);
|
||||
});
|
||||
}
|
||||
|
||||
private static void run(CommandControl cmd) {
|
||||
String out;
|
||||
try {
|
||||
out = cmd.readStdoutOrThrow();
|
||||
if (out.isEmpty()) {
|
||||
out = "<empty>";
|
||||
}
|
||||
|
||||
if (out.length() > 10000) {
|
||||
var counter = new AtomicInteger();
|
||||
var start = out.lines()
|
||||
.filter(s -> {
|
||||
counter.incrementAndGet();
|
||||
return true;
|
||||
})
|
||||
.limit(100)
|
||||
.collect(Collectors.joining("\n"));
|
||||
var notShownLines = counter.get() - 100;
|
||||
if (notShownLines > 0) {
|
||||
out = start + "\n\n... " + notShownLines + " more lines";
|
||||
} else {
|
||||
out = start;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ProcessOutputException e) {
|
||||
out = e.getMessage();
|
||||
} catch (Throwable t) {
|
||||
out = ExceptionUtils.getStackTrace(t);
|
||||
}
|
||||
|
||||
String finalOut = out;
|
||||
var modal = ModalOverlay.of(
|
||||
"commandOutput",
|
||||
Comp.of(() -> {
|
||||
var text = new TextArea(finalOut);
|
||||
text.setWrapText(true);
|
||||
text.setEditable(false);
|
||||
text.setPrefRowCount(Math.max(8, (int)
|
||||
finalOut.lines().count()));
|
||||
var sp = new StackPane(text);
|
||||
return sp;
|
||||
})
|
||||
.prefWidth(650));
|
||||
modal.show();
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.ext.ShellStore;
|
|||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.app.util.CommandDialog;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.core.process.ShellTtyState;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
|
@ -63,7 +64,7 @@ public class RunScriptActionMenu implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public LabelGraphic getIcon(DataStoreEntryRef<ShellStore> store) {
|
||||
return new LabelGraphic.IconGraphic("mdi2d-desktop-mac");
|
||||
return new LabelGraphic.IconGraphic("mdi2c-code-greater-than");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,7 +88,7 @@ public class RunScriptActionMenu implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2d-desktop-mac";
|
||||
return "mdi2c-code-greater-than";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -108,6 +109,78 @@ public class RunScriptActionMenu implements ActionProvider {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Value
|
||||
private static class HubRunActionProvider implements ActionProvider {
|
||||
|
||||
ScriptHierarchy hierarchy;
|
||||
|
||||
@Value
|
||||
private class Action implements ActionProvider.Action {
|
||||
|
||||
DataStoreEntryRef<ShellStore> shellStore;
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
var sc = shellStore.getStore().getOrStartSession();
|
||||
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
|
||||
var cmd = sc.command(script);
|
||||
CommandDialog.runAsyncAndShow(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
|
||||
return new LeafDataStoreCallSite<ShellStore>() {
|
||||
@Override
|
||||
public Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
|
||||
return AppI18n.observable("runInConnectionHub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LabelGraphic getIcon(DataStoreEntryRef<ShellStore> store) {
|
||||
return new LabelGraphic.IconGraphic("mdi2d-desktop-mac");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchDataStoreCallSite<ShellStore> getBatchDataStoreCallSite() {
|
||||
return new BatchDataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName() {
|
||||
return AppI18n.observable("runInConnectionHub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon() {
|
||||
return "mdi2d-desktop-mac";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getApplicableClass() {
|
||||
return ShellStore.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return new Action(store);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class BackgroundRunActionProvider implements ActionProvider {
|
||||
|
||||
|
@ -215,7 +288,7 @@ public class RunScriptActionMenu implements ActionProvider {
|
|||
@Override
|
||||
public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) {
|
||||
return List.of(
|
||||
new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
|
||||
new TerminalRunActionProvider(hierarchy), new HubRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -284,7 +357,7 @@ public class RunScriptActionMenu implements ActionProvider {
|
|||
public List<? extends ActionProvider> getChildren(List<DataStoreEntryRef<ShellStore>> batch) {
|
||||
if (hierarchy.isLeaf()) {
|
||||
return List.of(
|
||||
new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
|
||||
new TerminalRunActionProvider(hierarchy), new HubRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
|
||||
}
|
||||
|
||||
return hierarchy.getChildren().stream()
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.browser.file.BrowserEntry;
|
|||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.CommandDialog;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
|
@ -61,6 +62,31 @@ public abstract class MultiExecuteAction implements BrowserBranchAction {
|
|||
return AppPrefs.get().terminalType().getValue() != null;
|
||||
}
|
||||
},
|
||||
new BrowserLeafAction() {
|
||||
|
||||
@Override
|
||||
public void execute(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
model.withShell(
|
||||
pc -> {
|
||||
for (BrowserEntry entry : entries) {
|
||||
var c = createCommand(pc, model, entry);
|
||||
if (c == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cmd = pc.command(c);
|
||||
CommandDialog.runAsyncAndShow(cmd);
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName(
|
||||
BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return AppI18n.observable("runInFileBrowser");
|
||||
}
|
||||
},
|
||||
new BrowserLeafAction() {
|
||||
|
||||
@Override
|
||||
|
@ -85,7 +111,7 @@ public abstract class MultiExecuteAction implements BrowserBranchAction {
|
|||
@Override
|
||||
public ObservableValue<String> getName(
|
||||
BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return AppI18n.observable("executeInBackground");
|
||||
return AppI18n.observable("runSilent");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import io.xpipe.app.comp.base.ModalOverlay;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.CommandDialog;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ProcessOutputException;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
@ -82,48 +83,7 @@ public abstract class MultiExecuteSelectionAction implements BrowserBranchAction
|
|||
}
|
||||
|
||||
var cmd = pc.command(c);
|
||||
String out;
|
||||
try {
|
||||
out = cmd.readStdoutOrThrow();
|
||||
if (out.isEmpty()) {
|
||||
out = "<empty>";
|
||||
}
|
||||
|
||||
if (out.length() > 10000) {
|
||||
var counter = new AtomicInteger();
|
||||
var start = out.lines()
|
||||
.filter(s -> {
|
||||
counter.incrementAndGet();
|
||||
return true;
|
||||
})
|
||||
.limit(100)
|
||||
.collect(Collectors.joining("\n"));
|
||||
var notShownLines = counter.get() - 100;
|
||||
if (notShownLines > 0) {
|
||||
out = start + "\n\n... " + notShownLines + " more lines";
|
||||
} else {
|
||||
out = start;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ProcessOutputException e) {
|
||||
out = e.getMessage();
|
||||
}
|
||||
|
||||
String finalOut = out;
|
||||
var modal = ModalOverlay.of(
|
||||
"commandOutput",
|
||||
Comp.of(() -> {
|
||||
var text = new TextArea(finalOut);
|
||||
text.setWrapText(true);
|
||||
text.setEditable(false);
|
||||
text.setPrefRowCount(Math.max(8, (int)
|
||||
finalOut.lines().count()));
|
||||
var sp = new StackPane(text);
|
||||
return sp;
|
||||
})
|
||||
.prefWidth(650));
|
||||
modal.show();
|
||||
CommandDialog.runAsyncAndShow(cmd);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
@ -131,7 +91,7 @@ public abstract class MultiExecuteSelectionAction implements BrowserBranchAction
|
|||
@Override
|
||||
public ObservableValue<String> getName(
|
||||
BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return AppI18n.observable("runCommand");
|
||||
return AppI18n.observable("runInFileBrowser");
|
||||
}
|
||||
},
|
||||
new BrowserLeafAction() {
|
||||
|
|
3
lang/strings/translations_en.properties
generated
3
lang/strings/translations_en.properties
generated
|
@ -1333,7 +1333,8 @@ iconDirectory=Icon directory
|
|||
addUnsupportedKexMethod=Add unsupported key exchange method
|
||||
addUnsupportedKexMethodDescription=Allow the key exchange method to be used for this connection
|
||||
runSilent=silently in background
|
||||
runCommand=in file browser
|
||||
runInFileBrowser=in file browser
|
||||
runInConnectionHub=in connection hub
|
||||
commandOutput=Command output
|
||||
iconSourceDeletionTitle=Delete icon source
|
||||
iconSourceDeletionContent=Do you want to delete this icon source and all associated icons with it?
|
||||
|
|
Loading…
Add table
Reference in a new issue