mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Merge branch better-commands
This commit is contained in:
parent
10ef67a6ec
commit
d3c173dbcd
18 changed files with 185 additions and 237 deletions
|
@ -2,7 +2,6 @@ package io.xpipe.app.browser.action;
|
|||
|
||||
import io.xpipe.app.browser.BrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -13,9 +12,7 @@ public abstract class ExecuteApplicationAction implements LeafAction, Applicatio
|
|||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
|
||||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
for (BrowserEntry entry : entries) {
|
||||
var command = detach()
|
||||
? ApplicationHelper.createDetachCommand(sc, createCommand(model, entry))
|
||||
: createCommand(model, entry);
|
||||
var command = createCommand(model, entry);
|
||||
try (var cc = sc.command(command)
|
||||
.withWorkingDirectory(model.getCurrentDirectory().getPath())
|
||||
.start()) {
|
||||
|
@ -23,19 +20,11 @@ public abstract class ExecuteApplicationAction implements LeafAction, Applicatio
|
|||
}
|
||||
}
|
||||
|
||||
if (detach() && refresh()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (refresh()) {
|
||||
model.refreshSync();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean refresh() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package io.xpipe.app.browser.action;
|
|||
import io.xpipe.app.browser.BrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.TerminalLauncher;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
@ -12,7 +12,7 @@ import java.util.List;
|
|||
|
||||
public abstract class MultiExecuteAction implements BranchAction {
|
||||
|
||||
protected abstract String createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry);
|
||||
protected abstract CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry);
|
||||
|
||||
@Override
|
||||
public List<LeafAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
|
@ -56,9 +56,7 @@ public abstract class MultiExecuteAction implements BranchAction {
|
|||
model.withShell(
|
||||
pc -> {
|
||||
for (BrowserEntry entry : entries) {
|
||||
var cmd = ApplicationHelper.createDetachCommand(
|
||||
pc, createCommand(pc, model, entry));
|
||||
pc.command(cmd)
|
||||
pc.command(createCommand(pc, model, entry))
|
||||
.withWorkingDirectory(model.getCurrentDirectory()
|
||||
.getPath())
|
||||
.execute();
|
||||
|
@ -71,27 +69,6 @@ public abstract class MultiExecuteAction implements BranchAction {
|
|||
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return "in background";
|
||||
}
|
||||
},
|
||||
new LeafAction() {
|
||||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
model.withShell(
|
||||
pc -> {
|
||||
for (BrowserEntry entry : entries) {
|
||||
pc.command(createCommand(pc, model, entry))
|
||||
.withWorkingDirectory(model.getCurrentDirectory()
|
||||
.getPath())
|
||||
.execute();
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return "wait for completion";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public class ErrorEvent {
|
|||
return EVENT_BASES.remove(t).description(msg);
|
||||
}
|
||||
|
||||
return builder().throwable(t).description(msg);
|
||||
return builder().throwable(t).description(msg + (t.getMessage() != null ? "\n\n" + t.getMessage() : ""));
|
||||
}
|
||||
|
||||
public static ErrorEventBuilder fromMessage(String msg) {
|
||||
|
|
|
@ -11,7 +11,6 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.PasswordLockSecretValue;
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
|
@ -503,7 +502,7 @@ public class AppPrefs {
|
|||
return null;
|
||||
}
|
||||
|
||||
return ApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key);
|
||||
return ExternalApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key);
|
||||
}
|
||||
|
||||
@Value
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ExternalApplicationHelper {
|
||||
|
||||
public static String replaceFileArgument(String format, String variable, String file) {
|
||||
// Support for legacy variables that were not upper case
|
||||
variable = variable.toUpperCase(Locale.ROOT);
|
||||
format = format.replace("$" + variable.toLowerCase(Locale.ROOT), "$" + variable.toUpperCase(Locale.ROOT));
|
||||
|
||||
var fileString = file.contains(" ") ? "\"" + file + "\"" : file;
|
||||
// Check if the variable is already quoted
|
||||
return format.replace("\"$" + variable + "\"", fileString).replace("$" + variable, fileString);
|
||||
}
|
||||
|
||||
public static void startAsync(CommandBuilder b) throws Exception {
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var cmd = sc.getShellDialect().launchAsnyc(b);
|
||||
TrackEvent.withDebug("Executing local application")
|
||||
.tag("command", b.buildFull(sc))
|
||||
.tag("adjusted", cmd.buildFull(sc))
|
||||
.handle();
|
||||
try (var c = sc.command(cmd).start()) {
|
||||
c.discardOrThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,11 @@ package io.xpipe.app.prefs;
|
|||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.CommandSupport;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
@ -95,10 +94,12 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
public abstract static class PathApplication extends ExternalApplicationType {
|
||||
|
||||
protected final String executable;
|
||||
protected final boolean explicityAsync;
|
||||
|
||||
public PathApplication(String id, String executable) {
|
||||
public PathApplication(String id, String executable, boolean explicityAsync) {
|
||||
super(id);
|
||||
this.executable = executable;
|
||||
this.explicityAsync = explicityAsync;
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
|
@ -110,32 +111,21 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
}
|
||||
}
|
||||
|
||||
protected void launch(String title, String args) throws Exception {
|
||||
protected void launch(String title, CommandBuilder args) throws Exception {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
if (!ApplicationHelper.isInPath(pc, executable)) {
|
||||
if (!CommandSupport.isInPath(pc, executable)) {
|
||||
throw ErrorEvent.expected(
|
||||
new IOException(
|
||||
"Executable " + executable
|
||||
+ " not found in PATH. Either add it to the PATH and refresh the environment by restarting XPipe, or specify an absolute executable path using the custom terminal setting."));
|
||||
}
|
||||
|
||||
if (ShellDialects.isPowershell(pc)) {
|
||||
var cmd = CommandBuilder.of()
|
||||
.add("Start-Process", "-FilePath")
|
||||
.addFile(executable)
|
||||
.add("-ArgumentList")
|
||||
.add(pc.getShellDialect().literalArgument(args));
|
||||
pc.executeSimpleCommand(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
var toExecute = executable + " " + args;
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
toExecute = "start \"" + title + "\" " + toExecute;
|
||||
args.add(0, executable);
|
||||
if (explicityAsync) {
|
||||
ExternalApplicationHelper.startAsync(args);
|
||||
} else {
|
||||
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown";
|
||||
pc.executeSimpleCommand(args);
|
||||
}
|
||||
pc.executeSimpleCommand(toExecute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.prefs;
|
|||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
@ -106,9 +106,8 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
var format =
|
||||
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ApplicationHelper.executeLocalApplication(
|
||||
CommandBuilder.of().add(ApplicationHelper.replaceFileArgument(format, "FILE", file.toString())),
|
||||
true);
|
||||
ExternalApplicationHelper.startAsync(
|
||||
CommandBuilder.of().add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -200,33 +199,28 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
throw new IOException("Application " + applicationName + ".app not found");
|
||||
}
|
||||
|
||||
ApplicationHelper.executeLocalApplication(
|
||||
ExternalApplicationHelper.startAsync(
|
||||
CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addFile(execFile.orElseThrow().toString())
|
||||
.addFile(file.toString()),
|
||||
false);
|
||||
.addFile(file.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
class GenericPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
|
||||
|
||||
private final boolean detach;
|
||||
|
||||
public GenericPathType(String id, String command, boolean detach) {
|
||||
super(id, command);
|
||||
this.detach = detach;
|
||||
public GenericPathType(String id, String command, boolean explicityAsync) {
|
||||
super(id, command, explicityAsync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(Path file) throws Exception {
|
||||
ApplicationHelper.executeLocalApplication(
|
||||
CommandBuilder.of().add(executable).addFile(file.toString()), detach);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable() {
|
||||
return true;
|
||||
var builder = CommandBuilder.of().addFile(executable).addFile(file.toString());
|
||||
if (explicityAsync) {
|
||||
ExternalApplicationHelper.startAsync(builder);
|
||||
} else {
|
||||
LocalShell.getShell().executeSimpleCommand(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,7 +242,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
public WindowsType(String id, String executable, boolean detach) {
|
||||
super(id, executable);
|
||||
this.detach = detach;
|
||||
this.detach = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -262,9 +256,12 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
}
|
||||
}
|
||||
|
||||
Optional<Path> finalLocation = location;
|
||||
ApplicationHelper.executeLocalApplication(
|
||||
CommandBuilder.of().addFile(finalLocation.get().toString()).addFile(file.toString()), detach);
|
||||
var builder = CommandBuilder.of().addFile(location.get().toString()).addFile(file.toString());
|
||||
if (detach) {
|
||||
ExternalApplicationHelper.startAsync(builder);
|
||||
} else {
|
||||
LocalShell.getShell().executeSimpleCommand(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,10 @@ package io.xpipe.app.terminal;
|
|||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.storage.DataStoreColor;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.MacOsPermissions;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.*;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import lombok.Getter;
|
||||
|
@ -23,7 +21,7 @@ import java.util.function.Supplier;
|
|||
|
||||
public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||
|
||||
ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe") {
|
||||
ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -45,7 +43,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
ExternalTerminalType POWERSHELL = new SimplePathType("app.powershell", "powershell") {
|
||||
ExternalTerminalType POWERSHELL = new SimplePathType("app.powershell", "powershell", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -73,14 +71,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
var base64 = Base64.getEncoder()
|
||||
.encodeToString(configuration
|
||||
.getDialectLaunchCommand()
|
||||
.buildCommandBase(sc)
|
||||
.buildBase(sc)
|
||||
.getBytes(StandardCharsets.UTF_16LE));
|
||||
return "\"" + base64 + "\"";
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ExternalTerminalType PWSH = new SimplePathType("app.pwsh", "pwsh") {
|
||||
ExternalTerminalType PWSH = new SimplePathType("app.pwsh", "pwsh", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -100,14 +98,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.add(sc -> {
|
||||
// Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850
|
||||
var c = "$env:PSModulePath=\"\";"
|
||||
+ configuration.getDialectLaunchCommand().buildCommandBase(sc);
|
||||
+ configuration.getDialectLaunchCommand().buildBase(sc);
|
||||
var base64 = Base64.getEncoder().encodeToString(c.getBytes(StandardCharsets.UTF_16LE));
|
||||
return "\"" + base64 + "\"";
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacritty", "alacritty") {
|
||||
ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacritty", "alacritty", false) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -127,12 +125,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addQuoted("colors.primary.background='%s'"
|
||||
.formatted(configuration.getColor().toHexString()));
|
||||
}
|
||||
|
||||
// Alacritty is bugged and will not accept arguments with spaces even if they are correctly passed/escaped
|
||||
// So this will not work when the script file has spaces
|
||||
return b.add("-t")
|
||||
.addQuoted(configuration.getCleanTitle())
|
||||
.add("-e")
|
||||
.add("cmd")
|
||||
.add("/c")
|
||||
.addQuoted(configuration.getScriptFile().toString().replaceAll(" ", "^$0"));
|
||||
.add(configuration.getDialectLaunchCommand());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabby", "Tabby") {
|
||||
|
@ -145,11 +144,20 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
@Override
|
||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
||||
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
|
||||
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
||||
// It also freezes with any other input than .bat files, why?
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.add("run")
|
||||
.addFile(configuration.getScriptFile())
|
||||
.discardOutput());
|
||||
}
|
||||
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.add("run")
|
||||
.addFile(configuration.getScriptFile())
|
||||
.add(configuration.getDialectLaunchCommand())
|
||||
.discardOutput());
|
||||
}
|
||||
|
||||
|
@ -183,9 +191,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
|
||||
@Override
|
||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
||||
ApplicationHelper.executeLocalApplication(
|
||||
CommandBuilder.of().addFile(file.toString()).add("start").addFile(configuration.getScriptFile()),
|
||||
true);
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(file.toString()).add("start").add(configuration.getDialectLaunchCommand()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -199,7 +205,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return launcherDir.map(Path::of);
|
||||
}
|
||||
};
|
||||
ExternalTerminalType WEZ_LINUX = new SimplePathType("app.wezterm", "wezterm-gui") {
|
||||
ExternalTerminalType WEZ_LINUX = new SimplePathType("app.wezterm", "wezterm-gui", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -211,7 +217,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return CommandBuilder.of().add("start").addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal") {
|
||||
ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -221,7 +227,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
ApplicationHelper.checkIsInPath(
|
||||
CommandSupport.isInPathOrThrow(
|
||||
pc, executable, toTranslatedString().getValue(), null);
|
||||
|
||||
var toExecute = CommandBuilder.of()
|
||||
|
@ -229,15 +235,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addQuoted(configuration.getColoredTitle())
|
||||
.add("--")
|
||||
.addFile(configuration.getScriptFile())
|
||||
.buildString(pc);
|
||||
// In order to fix this bug which also affects us:
|
||||
// https://askubuntu.com/questions/1148475/launching-gnome-terminal-from-vscode
|
||||
toExecute = "GNOME_TERMINAL_SCREEN=\"\" nohup " + toExecute + " </dev/null &>/dev/null & disown";
|
||||
pc.executeSimpleCommand(toExecute);
|
||||
// In order to fix this bug which also affects us:
|
||||
// https://askubuntu.com/questions/1148475/launching-gnome-terminal-from-vscode
|
||||
.envrironment("GNOME_TERMINAL_SCREEN", sc -> "");
|
||||
}
|
||||
}
|
||||
};
|
||||
ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole") {
|
||||
ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -257,7 +261,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return CommandBuilder.of().add("--new-tab", "-e").addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal") {
|
||||
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -273,7 +277,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal") {
|
||||
ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -285,7 +289,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return CommandBuilder.of().add("--new-tab").add("-e").addFile(configuration.getColoredTitle());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix") {
|
||||
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -301,7 +305,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator") {
|
||||
ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -318,7 +322,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.add("--new-tab");
|
||||
}
|
||||
};
|
||||
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology") {
|
||||
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -335,7 +339,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term") {
|
||||
ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -351,7 +355,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake") {
|
||||
ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -368,7 +372,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType ALACRITTY_LINUX = new SimplePathType("app.alacritty", "alacritty") {
|
||||
ExternalTerminalType ALACRITTY_LINUX = new SimplePathType("app.alacritty", "alacritty", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -389,7 +393,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda") {
|
||||
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -401,7 +405,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return CommandBuilder.of().add("-c").addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm") {
|
||||
ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -417,7 +421,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal") {
|
||||
ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -429,7 +433,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return CommandBuilder.of().add("-C").addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal") {
|
||||
ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal", true) {
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
|
@ -576,10 +580,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.resolve("wezterm-gui")
|
||||
.toString())
|
||||
.add("start")
|
||||
.addFile(configuration.getScriptFile())
|
||||
.buildString(LocalShell.getShell());
|
||||
c = ApplicationHelper.createDetachCommand(LocalShell.getShell(), c);
|
||||
LocalShell.getShell().executeSimpleCommand(c);
|
||||
.add(configuration.getDialectLaunchCommand());
|
||||
ExternalApplicationHelper.startAsync(c);
|
||||
}
|
||||
};
|
||||
ExternalTerminalType KITTY_MACOS = new MacOsType("app.kitty", "kitty") {
|
||||
|
@ -768,7 +770,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
|
||||
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
|
||||
try (var pc = LocalShell.getShell()) {
|
||||
var toExecute = ApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile().toString());
|
||||
var toExecute = ExternalApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile().toString());
|
||||
// We can't be sure whether the command is blocking or not, so always make it not blocking
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
toExecute = "start \"" + configuration.getCleanTitle() + "\" " + toExecute;
|
||||
|
@ -795,21 +797,21 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
@Getter
|
||||
abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalTerminalType {
|
||||
|
||||
public PathCheckType(String id, String executable) {
|
||||
super(id, executable);
|
||||
public PathCheckType(String id, String executable, boolean explicitAsync) {
|
||||
super(id, executable, explicitAsync);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
abstract class SimplePathType extends PathCheckType {
|
||||
|
||||
public SimplePathType(String id, String executable) {
|
||||
super(id, executable);
|
||||
public SimplePathType(String id, String executable, boolean explicitAsync) {
|
||||
super(id, executable, explicitAsync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var args = toCommand(configuration).buildCommandBase(LocalShell.getShell());
|
||||
var args = toCommand(configuration);
|
||||
launch(configuration.getColoredTitle(), args);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.terminal;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.CommandSupport;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -44,8 +44,8 @@ public class KittyTerminalType {
|
|||
private static boolean prepare() throws Exception {
|
||||
var socket = getSocket();
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
ApplicationHelper.checkIsInPath(sc, "kitty", "Kitty", null);
|
||||
ApplicationHelper.checkIsInPath(sc, "socat", "socat", null);
|
||||
CommandSupport.isInPathOrThrow(sc, "kitty", "Kitty", null);
|
||||
CommandSupport.isInPathOrThrow(sc, "socat", "socat", null);
|
||||
|
||||
if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) {
|
||||
return false;
|
||||
|
|
|
@ -11,7 +11,7 @@ import java.nio.file.Path;
|
|||
public class WindowsTerminalType {
|
||||
|
||||
public static final ExternalTerminalType WINDOWS_TERMINAL =
|
||||
new ExternalTerminalType.SimplePathType("app.windowsTerminal", "wt.exe") {
|
||||
new ExternalTerminalType.SimplePathType("app.windowsTerminal", "wt.exe", false) {
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception {
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.util.FailableSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ApplicationHelper {
|
||||
|
||||
public static String replaceFileArgument(String format, String variable, String file) {
|
||||
// Support for legacy variables that were not upper case
|
||||
variable = variable.toUpperCase(Locale.ROOT);
|
||||
format = format.replace("$" + variable.toLowerCase(Locale.ROOT), "$" + variable.toUpperCase(Locale.ROOT));
|
||||
|
||||
var fileString = file.contains(" ") ? "\"" + file + "\"" : file;
|
||||
// Check if the variable is already quoted
|
||||
return format.replace("\"$" + variable + "\"", fileString).replace("$" + variable, fileString);
|
||||
}
|
||||
|
||||
public static void executeLocalApplication(CommandBuilder b, boolean detach) throws Exception {
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var cmd = detach ? createDetachCommand(sc, b.buildString(sc)) : b.buildString(sc);
|
||||
TrackEvent.withDebug("Executing local application")
|
||||
.tag("command", cmd)
|
||||
.handle();
|
||||
try (var c = sc.command(cmd).start()) {
|
||||
c.discardOrThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String createDetachCommand(ShellControl pc, String command) {
|
||||
if (ShellDialects.isPowershell(pc)) {
|
||||
var script = ScriptHelper.createExecScript(pc, command);
|
||||
return String.format(
|
||||
"Start-Process -FilePath %s -ArgumentList \"-NoProfile\", \"-File\", %s",
|
||||
pc.getShellDialect().getExecutableName(),
|
||||
pc.getShellDialect().fileArgument(script.toString()));
|
||||
}
|
||||
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
return "start \"\" " + command;
|
||||
} else {
|
||||
return "nohup " + command + " </dev/null &>/dev/null & disown";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInPath(ShellControl processControl, String executable) throws Exception {
|
||||
return processControl.executeSimpleBooleanCommand(
|
||||
processControl.getShellDialect().getWhichCommand(executable));
|
||||
}
|
||||
|
||||
public static boolean isInPathSilent(ShellControl processControl, String executable) {
|
||||
try {
|
||||
return processControl.executeSimpleBooleanCommand(
|
||||
processControl.getShellDialect().getWhichCommand(executable));
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkIsInPath(
|
||||
ShellControl processControl, String executable, String displayName, DataStoreEntry connection)
|
||||
throws Exception {
|
||||
if (!isInPath(processControl, executable)) {
|
||||
throw ErrorEvent.expected(new IOException(displayName + " executable " + executable + " not found in PATH"
|
||||
+ (connection != null ? " on system " + connection.getName() : "")));
|
||||
}
|
||||
}
|
||||
|
||||
public static void isSupported(FailableSupplier<Boolean> supplier, String displayName, DataStoreEntry connection)
|
||||
throws Exception {
|
||||
if (!supplier.get()) {
|
||||
throw ErrorEvent.expected(new IOException(displayName + " is not supported"
|
||||
+ (connection != null ? " on system " + connection.getName() : "")));
|
||||
}
|
||||
}
|
||||
}
|
42
app/src/main/java/io/xpipe/app/util/CommandSupport.java
Normal file
42
app/src/main/java/io/xpipe/app/util/CommandSupport.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.util.FailableSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CommandSupport {
|
||||
public static boolean isInPath(ShellControl processControl, String executable) throws Exception {
|
||||
return processControl.executeSimpleBooleanCommand(
|
||||
processControl.getShellDialect().getWhichCommand(executable));
|
||||
}
|
||||
|
||||
public static boolean isInPathSilent(ShellControl processControl, String executable) {
|
||||
try {
|
||||
return processControl.executeSimpleBooleanCommand(
|
||||
processControl.getShellDialect().getWhichCommand(executable));
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void isInPathOrThrow(
|
||||
ShellControl processControl, String executable, String displayName, DataStoreEntry connection)
|
||||
throws Exception {
|
||||
if (!isInPath(processControl, executable)) {
|
||||
throw ErrorEvent.expected(new IOException(displayName + " executable " + executable + " not found in PATH"
|
||||
+ (connection != null ? " on system " + connection.getName() : "")));
|
||||
}
|
||||
}
|
||||
|
||||
public static void isSupported(FailableSupplier<Boolean> supplier, String displayName, DataStoreEntry connection)
|
||||
throws Exception {
|
||||
if (!supplier.get()) {
|
||||
throw ErrorEvent.expected(new IOException(displayName + " is not supported"
|
||||
+ (connection != null ? " on system " + connection.getName() : "")));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ public class ShellControlCache {
|
|||
public boolean isApplicationInPath(String app) {
|
||||
if (!installedApplications.containsKey(app)) {
|
||||
try {
|
||||
var b = ApplicationHelper.isInPath(shellControl, app);
|
||||
var b = CommandSupport.isInPath(shellControl, app);
|
||||
installedApplications.put(app, b);
|
||||
} catch (Exception e) {
|
||||
installedApplications.put(app, false);
|
||||
|
|
|
@ -61,9 +61,7 @@ public class TerminalLauncher {
|
|||
latch.await();
|
||||
} catch (Exception ex) {
|
||||
throw ErrorEvent.expected(new IOException(
|
||||
"Unable to launch terminal " + type.toTranslatedString().getValue() + ": " + ex.getMessage()
|
||||
+ ".\nMaybe try to use a different terminal in the settings.",
|
||||
ex));
|
||||
"Unable to launch terminal " + type.toTranslatedString().getValue() + ": " + ex.getMessage(), ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,7 +210,11 @@ public class CommandBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public String buildCommandBase(ShellControl sc) throws Exception {
|
||||
public String buildBase(ShellControl sc) throws Exception {
|
||||
return String.join(" ", buildBaseParts(sc));
|
||||
}
|
||||
|
||||
public List<String> buildBaseParts(ShellControl sc) throws Exception {
|
||||
countDown = CountDown.of();
|
||||
uuid = UUID.randomUUID();
|
||||
|
||||
|
@ -227,11 +231,11 @@ public class CommandBuilder {
|
|||
|
||||
list.add(evaluate);
|
||||
}
|
||||
return String.join(" ", list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public String buildString(ShellControl sc) throws Exception {
|
||||
var s = buildCommandBase(sc);
|
||||
public String buildFull(ShellControl sc) throws Exception {
|
||||
var s = buildBase(sc);
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
for (var e : environmentVariables.entrySet()) {
|
||||
var v = e.getValue().evaluate(sc);
|
||||
|
@ -239,7 +243,7 @@ public class CommandBuilder {
|
|||
map.put(e.getKey(), v);
|
||||
}
|
||||
}
|
||||
return sc.getShellDialect().addInlineVariablesToCommand(map, s);
|
||||
return sc.getShellDialect().assembleCommand(s, map);
|
||||
}
|
||||
|
||||
public CommandControl build(ShellControl sc) {
|
||||
|
|
|
@ -16,6 +16,8 @@ import java.util.stream.Stream;
|
|||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
public interface ShellDialect {
|
||||
|
||||
CommandBuilder launchAsnyc(CommandBuilder cmd);
|
||||
|
||||
default String getLicenseFeatureId() {
|
||||
return null;
|
||||
}
|
||||
|
@ -80,7 +82,7 @@ public interface ShellDialect {
|
|||
|
||||
String getScriptFileEnding();
|
||||
|
||||
String addInlineVariablesToCommand(Map<String, String> variables, String command);
|
||||
String assembleCommand(String command, Map<String, String> variables);
|
||||
|
||||
Stream<FileSystem.FileEntry> listFiles(FileSystem fs, ShellControl control, String dir) throws Exception;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.browser.OpenFileSystemModel;
|
|||
import io.xpipe.app.browser.action.BrowserActionFormatter;
|
||||
import io.xpipe.app.browser.action.MultiExecuteAction;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -27,8 +28,8 @@ public class JarAction extends MultiExecuteAction implements JavaAction, FileTyp
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) {
|
||||
return "java -jar " + entry.getOptionallyQuotedFileName();
|
||||
protected CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) {
|
||||
return CommandBuilder.of().add("java", "-jar").addFile(entry.getRawFileEntry().getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.ext.base.browser;
|
|||
import io.xpipe.app.browser.BrowserEntry;
|
||||
import io.xpipe.app.browser.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.action.MultiExecuteAction;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
@ -69,8 +70,7 @@ public class RunAction extends MultiExecuteAction {
|
|||
return entries.stream().allMatch(entry -> isExecutable(entry.getRawFileEntry()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) {
|
||||
return sc.getShellDialect().runScriptCommand(sc, entry.getRawFileEntry().getPath());
|
||||
protected CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) {
|
||||
return CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, entry.getRawFileEntry().getPath()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue