Merge branch better-commands

This commit is contained in:
crschnick 2024-04-01 07:25:06 +00:00
parent 10ef67a6ec
commit d3c173dbcd
18 changed files with 185 additions and 237 deletions

View file

@ -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;
}

View file

@ -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";
}
});
}
}

View file

@ -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) {

View file

@ -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

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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 {

View file

@ -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() : "")));
}
}
}

View 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() : "")));
}
}
}

View file

@ -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);

View file

@ -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));
}
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -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()));
}
}