mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-15 16:59:00 +12:00
Rework default external application detection
This commit is contained in:
parent
234a4ef6b1
commit
e7bcb835a2
11 changed files with 306 additions and 154 deletions
|
@ -115,9 +115,9 @@ Furthermore, you also need the platform specific toolchains to be installed:
|
||||||
|
|
||||||
You can use the gradle wrapper to build and run the project:
|
You can use the gradle wrapper to build and run the project:
|
||||||
- `gradlew app:run` will run the desktop application. You can set various useful properties in `app/build.gradle`
|
- `gradlew app:run` will run the desktop application. You can set various useful properties in `app/build.gradle`
|
||||||
- `gradlew builtCli` will create a native image for the CLI application
|
- `gradlew buildCli` will create a native image for the CLI application
|
||||||
- `gradlew dist` will create a distributable production version in `dist/build/dist/base`.
|
- `gradlew dist` will create a distributable production version in `dist/build/dist/base`.
|
||||||
To include this CLI executable in this build, make sure to run `gradlew builtCli` first
|
To include this CLI executable in this build, make sure to run `gradlew buildCli` first
|
||||||
- You can also run the CLI application in development mode with something like `gradlew :cli:clean :cli:run --args="daemon start"`.
|
- You can also run the CLI application in development mode with something like `gradlew :cli:clean :cli:run --args="daemon start"`.
|
||||||
Note here that you should always clean the CLI project first, as the native image plugin is a little buggy in that regard.
|
Note here that you should always clean the CLI project first, as the native image plugin is a little buggy in that regard.
|
||||||
- `gradlew <project>:test` will run the tests of the specified project.
|
- `gradlew <project>:test` will run the tests of the specified project.
|
||||||
|
|
|
@ -181,7 +181,7 @@ public class EditorState {
|
||||||
|
|
||||||
public void openInEditor(String file) {
|
public void openInEditor(String file) {
|
||||||
var editor = AppPrefs.get().externalEditor().getValue();
|
var editor = AppPrefs.get().externalEditor().getValue();
|
||||||
if (editor == null || !editor.isSupported()) {
|
if (editor == null || !editor.isSelectable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,9 +61,9 @@ public class AppPrefs {
|
||||||
private final ObjectProperty<SupportedLocale> languageInternal =
|
private final ObjectProperty<SupportedLocale> languageInternal =
|
||||||
typed(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), SupportedLocale.class);
|
typed(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), SupportedLocale.class);
|
||||||
public final Property<SupportedLocale> language = new SimpleObjectProperty<>(SupportedLocale.ENGLISH);
|
public final Property<SupportedLocale> language = new SimpleObjectProperty<>(SupportedLocale.ENGLISH);
|
||||||
private final SingleSelectionField<SupportedLocale> languageControl =
|
private final SingleSelectionField<SupportedLocale> languageControl = Field.ofSingleSelectionType(
|
||||||
Field.ofSingleSelectionType(languageList, languageInternal).render(() -> new TranslatableComboBoxControl<>());
|
languageList, languageInternal)
|
||||||
|
.render(() -> new TranslatableComboBoxControl<>());
|
||||||
|
|
||||||
private final ObjectProperty<AppStyle.Theme> themeInternal =
|
private final ObjectProperty<AppStyle.Theme> themeInternal =
|
||||||
typed(new SimpleObjectProperty<>(AppStyle.Theme.LIGHT), AppStyle.Theme.class);
|
typed(new SimpleObjectProperty<>(AppStyle.Theme.LIGHT), AppStyle.Theme.class);
|
||||||
|
@ -85,32 +85,36 @@ public class AppPrefs {
|
||||||
|
|
||||||
private final ObjectProperty<CloseBehaviour> closeBehaviour =
|
private final ObjectProperty<CloseBehaviour> closeBehaviour =
|
||||||
typed(new SimpleObjectProperty<>(CloseBehaviour.QUIT), CloseBehaviour.class);
|
typed(new SimpleObjectProperty<>(CloseBehaviour.QUIT), CloseBehaviour.class);
|
||||||
private final SingleSelectionField<CloseBehaviour> closeBehaviourControl =
|
private final SingleSelectionField<CloseBehaviour> closeBehaviourControl = Field.ofSingleSelectionType(
|
||||||
Field.ofSingleSelectionType(closeBehaviourList, closeBehaviour).render(() -> new TranslatableComboBoxControl<>());
|
closeBehaviourList, closeBehaviour)
|
||||||
private final ObjectProperty<ExternalEditorType> externalEditor =
|
.render(() -> new TranslatableComboBoxControl<>());
|
||||||
typed(new SimpleObjectProperty<>(ExternalEditorType.getDefault()), ExternalEditorType.class);
|
|
||||||
private final SingleSelectionField<ExternalEditorType> externalEditorControl =
|
|
||||||
Field.ofSingleSelectionType(externalEditorList, externalEditor).render(() -> new TranslatableComboBoxControl<>());
|
|
||||||
|
|
||||||
// External editor
|
// External editor
|
||||||
// ===============
|
// ===============
|
||||||
private final StringProperty customEditorCommand = typed(new SimpleStringProperty(""), String.class);
|
|
||||||
|
final ObjectProperty<ExternalEditorType> externalEditor =
|
||||||
|
typed(new SimpleObjectProperty<>(), ExternalEditorType.class);
|
||||||
|
private final SingleSelectionField<ExternalEditorType> externalEditorControl = Field.ofSingleSelectionType(
|
||||||
|
externalEditorList, externalEditor)
|
||||||
|
.render(() -> new TranslatableComboBoxControl<>());
|
||||||
|
|
||||||
|
final StringProperty customEditorCommand = typed(new SimpleStringProperty(""), String.class);
|
||||||
private final StringField customEditorCommandControl = editable(
|
private final StringField customEditorCommandControl = editable(
|
||||||
StringField.ofStringType(customEditorCommand).render(() -> new SimpleTextControl()),
|
StringField.ofStringType(customEditorCommand).render(() -> new SimpleTextControl()),
|
||||||
externalEditor.isEqualTo(ExternalEditorType.CUSTOM));
|
externalEditor.isEqualTo(ExternalEditorType.CUSTOM));
|
||||||
private final IntegerProperty editorReloadTimeout = typed(new SimpleIntegerProperty(1000), Integer.class);
|
private final IntegerProperty editorReloadTimeout = typed(new SimpleIntegerProperty(1000), Integer.class);
|
||||||
private final ObjectProperty<ExternalStartupBehaviour> externalStartupBehaviour = typed(
|
private final ObjectProperty<ExternalStartupBehaviour> externalStartupBehaviour = typed(
|
||||||
new SimpleObjectProperty<>(
|
new SimpleObjectProperty<>(
|
||||||
ExternalStartupBehaviour.TRAY.isSupported()
|
ExternalStartupBehaviour.TRAY.isSelectable()
|
||||||
? ExternalStartupBehaviour.TRAY
|
? ExternalStartupBehaviour.TRAY
|
||||||
: ExternalStartupBehaviour.BACKGROUND),
|
: ExternalStartupBehaviour.BACKGROUND),
|
||||||
ExternalStartupBehaviour.class);
|
ExternalStartupBehaviour.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final SingleSelectionField<ExternalStartupBehaviour> externalStartupBehaviourControl =
|
private final SingleSelectionField<ExternalStartupBehaviour> externalStartupBehaviourControl =
|
||||||
Field.ofSingleSelectionType(externalStartupBehaviourList, externalStartupBehaviour)
|
Field.ofSingleSelectionType(externalStartupBehaviourList, externalStartupBehaviour)
|
||||||
.render(() -> new TranslatableComboBoxControl<>());
|
.render(() -> new TranslatableComboBoxControl<>());
|
||||||
|
// Automatically update
|
||||||
|
// ====================
|
||||||
private final BooleanProperty automaticallyUpdate =
|
private final BooleanProperty automaticallyUpdate =
|
||||||
typed(new SimpleBooleanProperty(AppDistributionType.get().supportsUpdate()), Boolean.class);
|
typed(new SimpleBooleanProperty(AppDistributionType.get().supportsUpdate()), Boolean.class);
|
||||||
private final BooleanField automaticallyUpdateField = BooleanField.ofBooleanType(automaticallyUpdate)
|
private final BooleanField automaticallyUpdateField = BooleanField.ofBooleanType(automaticallyUpdate)
|
||||||
|
@ -135,8 +139,8 @@ public class AppPrefs {
|
||||||
private final ObjectProperty<String> internalLogLevel =
|
private final ObjectProperty<String> internalLogLevel =
|
||||||
typed(new SimpleObjectProperty<>(DEFAULT_LOG_LEVEL), String.class);
|
typed(new SimpleObjectProperty<>(DEFAULT_LOG_LEVEL), String.class);
|
||||||
|
|
||||||
// Automatically update
|
// Log level
|
||||||
// ====================
|
// =========
|
||||||
private final ObjectProperty<String> effectiveLogLevel = LOG_LEVEL_FIXED
|
private final ObjectProperty<String> effectiveLogLevel = LOG_LEVEL_FIXED
|
||||||
? new SimpleObjectProperty<>(System.getProperty(LOG_LEVEL_PROP).toLowerCase())
|
? new SimpleObjectProperty<>(System.getProperty(LOG_LEVEL_PROP).toLowerCase())
|
||||||
: internalLogLevel;
|
: internalLogLevel;
|
||||||
|
@ -144,6 +148,8 @@ public class AppPrefs {
|
||||||
logLevelList, effectiveLogLevel)
|
logLevelList, effectiveLogLevel)
|
||||||
.editable(!LOG_LEVEL_FIXED)
|
.editable(!LOG_LEVEL_FIXED)
|
||||||
.render(() -> new SimpleComboBoxControl<>());
|
.render(() -> new SimpleComboBoxControl<>());
|
||||||
|
// Developer mode
|
||||||
|
// ==============
|
||||||
private final BooleanProperty internalDeveloperMode = typed(new SimpleBooleanProperty(false), Boolean.class);
|
private final BooleanProperty internalDeveloperMode = typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||||
private final BooleanProperty effectiveDeveloperMode = System.getProperty(DEVELOPER_MODE_PROP) != null
|
private final BooleanProperty effectiveDeveloperMode = System.getProperty(DEVELOPER_MODE_PROP) != null
|
||||||
? new SimpleBooleanProperty(Boolean.parseBoolean(System.getProperty(DEVELOPER_MODE_PROP)))
|
? new SimpleBooleanProperty(Boolean.parseBoolean(System.getProperty(DEVELOPER_MODE_PROP)))
|
||||||
|
@ -176,6 +182,70 @@ public class AppPrefs {
|
||||||
developerDisableConnectorInstallationVersionCheck)
|
developerDisableConnectorInstallationVersionCheck)
|
||||||
.render(() -> new ToggleControl());
|
.render(() -> new ToggleControl());
|
||||||
|
|
||||||
|
public ReadOnlyProperty<CloseBehaviour> closeBehaviour() {
|
||||||
|
return closeBehaviour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyProperty<ExternalEditorType> externalEditor() {
|
||||||
|
return externalEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<String> customEditorCommand() {
|
||||||
|
return customEditorCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ReadOnlyIntegerProperty editorReloadTimeout() {
|
||||||
|
return editorReloadTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyProperty<ExternalStartupBehaviour> externalStartupBehaviour() {
|
||||||
|
return externalStartupBehaviour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty automaticallyUpdate() {
|
||||||
|
return automaticallyUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty updateToPrereleases() {
|
||||||
|
return updateToPrereleases;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty confirmDeletions() {
|
||||||
|
return confirmDeletions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Path> storageDirectory() {
|
||||||
|
return effectiveStorageDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyProperty<String> logLevel() {
|
||||||
|
return effectiveLogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableValue<Boolean> developerMode() {
|
||||||
|
return effectiveDeveloperMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty developerDisableUpdateVersionCheck() {
|
||||||
|
return developerDisableUpdateVersionCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty developerDisableGuiRestrictions() {
|
||||||
|
return developerDisableGuiRestrictions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty developerDisableConnectorInstallationVersionCheck() {
|
||||||
|
return developerDisableConnectorInstallationVersionCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty developerShowHiddenProviders() {
|
||||||
|
return developerShowHiddenProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty developerShowHiddenEntries() {
|
||||||
|
return developerShowHiddenEntries;
|
||||||
|
}
|
||||||
|
|
||||||
private AppPreferencesFx preferencesFx;
|
private AppPreferencesFx preferencesFx;
|
||||||
private boolean controlsSetup;
|
private boolean controlsSetup;
|
||||||
|
|
||||||
|
@ -194,6 +264,8 @@ public class AppPrefs {
|
||||||
public static void init() {
|
public static void init() {
|
||||||
INSTANCE = new AppPrefs();
|
INSTANCE = new AppPrefs();
|
||||||
INSTANCE.preferencesFx.loadSettings();
|
INSTANCE.preferencesFx.loadSettings();
|
||||||
|
INSTANCE.initValues();
|
||||||
|
PrefsProvider.getAll().forEach(prov -> prov.init());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppPrefs get() {
|
public static AppPrefs get() {
|
||||||
|
@ -246,8 +318,11 @@ public class AppPrefs {
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log level
|
public void initValues() {
|
||||||
// =========
|
if (externalEditor.get() == null) {
|
||||||
|
ExternalEditorType.detectDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
preferencesFx.saveSettings();
|
preferencesFx.saveSettings();
|
||||||
|
@ -257,76 +332,6 @@ public class AppPrefs {
|
||||||
preferencesFx.discardChanges();
|
preferencesFx.discardChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyProperty<CloseBehaviour> closeBehaviour() {
|
|
||||||
return closeBehaviour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyProperty<ExternalEditorType> externalEditor() {
|
|
||||||
return externalEditor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableValue<String> customEditorCommand() {
|
|
||||||
return customEditorCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final ReadOnlyIntegerProperty editorReloadTimeout() {
|
|
||||||
return editorReloadTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyProperty<ExternalStartupBehaviour> externalStartupBehaviour() {
|
|
||||||
return externalStartupBehaviour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty automaticallyUpdate() {
|
|
||||||
return automaticallyUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Developer mode
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty updateToPrereleases() {
|
|
||||||
return updateToPrereleases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty confirmDeletions() {
|
|
||||||
return confirmDeletions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableValue<Path> storageDirectory() {
|
|
||||||
return effectiveStorageDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Developer options
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
public ReadOnlyProperty<String> logLevel() {
|
|
||||||
return effectiveLogLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableValue<Boolean> developerMode() {
|
|
||||||
return effectiveDeveloperMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty developerDisableUpdateVersionCheck() {
|
|
||||||
return developerDisableUpdateVersionCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty developerDisableGuiRestrictions() {
|
|
||||||
return developerDisableGuiRestrictions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty developerDisableConnectorInstallationVersionCheck() {
|
|
||||||
return developerDisableConnectorInstallationVersionCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty developerShowHiddenProviders() {
|
|
||||||
return developerShowHiddenProviders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty developerShowHiddenEntries() {
|
|
||||||
return developerShowHiddenEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> getSettingType(String breadcrumb) {
|
public Class<?> getSettingType(String breadcrumb) {
|
||||||
var found = classMap.get(getSetting(breadcrumb).valueProperty());
|
var found = classMap.get(getSetting(breadcrumb).valueProperty());
|
||||||
if (found == null) {
|
if (found == null) {
|
||||||
|
@ -391,15 +396,15 @@ public class AppPrefs {
|
||||||
Setting.of("useSystemFont", useSystemFontInternal),
|
Setting.of("useSystemFont", useSystemFontInternal),
|
||||||
Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax),
|
Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax),
|
||||||
Setting.of("fontSize", fontSizeInternal, fontSizeMin, fontSizeMax)),
|
Setting.of("fontSize", fontSizeInternal, fontSizeMin, fontSizeMax)),
|
||||||
Group.of(
|
Group.of("windowOptions", Setting.of("saveWindowLocation", saveWindowLocationInternal))),
|
||||||
"windowOptions",
|
|
||||||
Setting.of("saveWindowLocation", saveWindowLocationInternal))),
|
|
||||||
Category.of(
|
Category.of(
|
||||||
"integrations",
|
"integrations",
|
||||||
Group.of(
|
Group.of(
|
||||||
"editor",
|
"editor",
|
||||||
Setting.of("defaultProgram", externalEditorControl, externalEditor),
|
Setting.of("defaultProgram", externalEditorControl, externalEditor),
|
||||||
Setting.of("customEditorCommand", customEditorCommandControl, customEditorCommand).applyVisibility( VisibilityProperty.of(externalEditor.isEqualTo(ExternalEditorType.CUSTOM))),
|
Setting.of("customEditorCommand", customEditorCommandControl, customEditorCommand)
|
||||||
|
.applyVisibility(VisibilityProperty.of(
|
||||||
|
externalEditor.isEqualTo(ExternalEditorType.CUSTOM))),
|
||||||
Setting.of(
|
Setting.of(
|
||||||
"editorReloadTimeout",
|
"editorReloadTimeout",
|
||||||
editorReloadTimeout,
|
editorReloadTimeout,
|
||||||
|
|
|
@ -32,7 +32,7 @@ public enum CloseBehaviour implements PrefsChoiceValue {
|
||||||
this.exit = exit;
|
this.exit = exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSupported() {
|
public boolean isSelectable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.prefs;
|
||||||
|
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.process.ShellTypes;
|
import io.xpipe.core.process.ShellTypes;
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
import io.xpipe.extension.prefs.PrefsChoiceValue;
|
import io.xpipe.extension.prefs.PrefsChoiceValue;
|
||||||
import io.xpipe.extension.util.ApplicationHelper;
|
import io.xpipe.extension.util.ApplicationHelper;
|
||||||
import io.xpipe.extension.util.WindowsRegistry;
|
import io.xpipe.extension.util.WindowsRegistry;
|
||||||
|
@ -11,7 +10,9 @@ import lombok.Getter;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -20,13 +21,24 @@ import java.util.Optional;
|
||||||
public abstract class ExternalEditorType implements PrefsChoiceValue {
|
public abstract class ExternalEditorType implements PrefsChoiceValue {
|
||||||
|
|
||||||
public static final ExternalEditorType NOTEPAD = new WindowsFullPathType("app.notepad") {
|
public static final ExternalEditorType NOTEPAD = new WindowsFullPathType("app.notepad") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<Path> determinePath() {
|
protected Optional<Path> determinePath() {
|
||||||
return Optional.of(Path.of(System.getenv("SystemRoot") + "\\System32\\notepad.exe"));
|
return Optional.of(Path.of(System.getenv("SystemRoot") + "\\System32\\notepad.exe"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public static final ExternalEditorType NOTEPADPLUSPLUS_WINDOWS = new WindowsFullPathType("app.notepad++Windows") {
|
|
||||||
|
public static final ExternalEditorType VSCODE = new WindowsFullPathType("app.vscode") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<Path> determinePath() {
|
||||||
|
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||||
|
.resolve("Programs")
|
||||||
|
.resolve("Microsoft VS Code")
|
||||||
|
.resolve("bin")
|
||||||
|
.resolve("code.cmd"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final ExternalEditorType NOTEPADPLUSPLUS_WINDOWS = new WindowsFullPathType("app.notepad++") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<Path> determinePath() {
|
protected Optional<Path> determinePath() {
|
||||||
|
@ -40,66 +52,80 @@ public abstract class ExternalEditorType implements PrefsChoiceValue {
|
||||||
return launcherDir.map(Path::of);
|
return launcherDir.map(Path::of);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public static final ExternalEditorType NOTEPADPLUSPLUS_LINUX =
|
|
||||||
new LinuxPathType("app.notepad++Linux", "notepad++") {};
|
|
||||||
|
|
||||||
public static final ExternalEditorType KATE = new LinuxPathType("app.kate", "kate") {};
|
public static final PathType NOTEPADPLUSPLUS_LINUX = new PathType("app.notepad++", "notepad++");
|
||||||
|
|
||||||
|
public static final PathType VSCODE_LINUX = new PathType("app.vscode", "code");
|
||||||
|
|
||||||
|
public static final PathType KATE = new PathType("app.kate", "kate");
|
||||||
|
|
||||||
|
public static final PathType GEDIT = new PathType("app.gedit", "gedit");
|
||||||
|
|
||||||
|
public static final PathType LEAFPAD = new PathType("app.leafpad", "leafpad");
|
||||||
|
|
||||||
|
public static final PathType MOUSEPAD = new PathType("app.mousepad", "mousepad");
|
||||||
|
|
||||||
|
public static final PathType PLUMA = new PathType("app.pluma", "pluma");
|
||||||
|
|
||||||
|
public static final ExternalEditorType TEXT_EDIT = new MacOsFullPathType("app.textEdit") {
|
||||||
|
@Override
|
||||||
|
protected Path determinePath() {
|
||||||
|
return Path.of("/Applications/TextEdit.app");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final ExternalEditorType NOTEPADPP_MACOS = new MacOsFullPathType("app.notepad++") {
|
||||||
|
@Override
|
||||||
|
protected Path determinePath() {
|
||||||
|
return Path.of("/Applications/TextEdit.app");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final ExternalEditorType SUBLIME_MACOS = new MacOsFullPathType("app.sublime") {
|
||||||
|
@Override
|
||||||
|
protected Path determinePath() {
|
||||||
|
return Path.of("/Applications/Sublime.app");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final ExternalEditorType VSCODE_MACOS = new MacOsFullPathType("app.vscode") {
|
||||||
|
@Override
|
||||||
|
protected Path determinePath() {
|
||||||
|
return Path.of("/Applications/VSCode.app");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static final ExternalEditorType CUSTOM = new ExternalEditorType("app.custom") {
|
public static final ExternalEditorType CUSTOM = new ExternalEditorType("app.custom") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(Path file) throws IOException {
|
public void launch(Path file) throws Exception {
|
||||||
var fileName = SystemUtils.IS_OS_WINDOWS ? " \"" + file + "\"" : file;
|
var customCommand = AppPrefs.get().customEditorCommand().getValue();
|
||||||
var cmd = AppPrefs.get().customEditorCommand().getValue();
|
if (customCommand == null || customCommand.trim().isEmpty()) {
|
||||||
var fullCmd = cmd + " " + fileName;
|
return;
|
||||||
Runtime.getRuntime()
|
}
|
||||||
.exec(ShellTypes.getPlatformDefault()
|
|
||||||
.executeCommandListWithShell(fullCmd)
|
var format = customCommand.contains("$file") ? customCommand : customCommand + " $file";
|
||||||
.toArray(String[]::new));
|
var fileString = file.toString().contains(" ") ? "\"" + file + "\"" : file.toString();
|
||||||
|
ApplicationHelper.executeLocalApplication(format.replace("$file",fileString));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSelectable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final ExternalEditorType TEXT_EDIT = new ExternalEditorType("app.textEdit") {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void launch(Path file) throws Exception {
|
|
||||||
var fullCmd = "/Applications/TextEdit.app/Contents/MacOS/TextEdit \"" + file.toString() + "\"";
|
|
||||||
ShellStore.withLocal(pc -> {
|
|
||||||
pc.executeSimpleCommand(fullCmd);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return OsType.getLocal().equals(OsType.MAC);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public static final List<ExternalEditorType> ALL =
|
|
||||||
List.of(NOTEPAD, NOTEPADPLUSPLUS_WINDOWS, NOTEPADPLUSPLUS_LINUX, KATE, TEXT_EDIT, CUSTOM);
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
public static ExternalEditorType getDefault() {
|
|
||||||
if (OsType.getLocal().equals(OsType.MAC)) {
|
|
||||||
return TEXT_EDIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return OsType.getLocal().equals(OsType.WINDOWS) ? NOTEPAD : KATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void launch(Path file) throws Exception;
|
public abstract void launch(Path file) throws Exception;
|
||||||
|
|
||||||
public abstract boolean isSupported();
|
public abstract boolean isSelectable();
|
||||||
|
|
||||||
public abstract static class LinuxPathType extends ExternalEditorType {
|
public static class PathType extends ExternalEditorType {
|
||||||
|
|
||||||
private final String command;
|
private final String command;
|
||||||
|
|
||||||
public LinuxPathType(String id, String command) {
|
public PathType(String id, String command) {
|
||||||
super(id);
|
super(id);
|
||||||
this.command = command;
|
this.command = command;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +137,7 @@ public abstract class ExternalEditorType implements PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSelectable() {
|
||||||
return OsType.getLocal().equals(OsType.LINUX);
|
return OsType.getLocal().equals(OsType.LINUX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,17 +157,103 @@ public abstract class ExternalEditorType implements PrefsChoiceValue {
|
||||||
throw new IOException("Unable to find installation of " + getId());
|
throw new IOException("Unable to find installation of " + getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationHelper.executeLocalApplication(getCommand(path.get(), file));
|
ApplicationHelper.executeLocalApplication(List.of(path.get().toString(), file.toString()));
|
||||||
}
|
|
||||||
|
|
||||||
protected String getCommand(Path p, Path file) {
|
|
||||||
var cmd = "\"" + p + "\"";
|
|
||||||
return "start \"\" " + cmd + " \"" + file + "\"";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSelectable() {
|
||||||
return OsType.getLocal().equals(OsType.WINDOWS);
|
return OsType.getLocal().equals(OsType.WINDOWS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
var path = determinePath();
|
||||||
|
return path.isPresent() && Files.exists(path.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class MacOsFullPathType extends ExternalEditorType {
|
||||||
|
|
||||||
|
public MacOsFullPathType(String id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Path determinePath();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(Path file) throws Exception {
|
||||||
|
var path = determinePath();
|
||||||
|
ApplicationHelper.executeLocalApplication(List.of("open", path.toString(), file.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSelectable() {
|
||||||
|
return OsType.getLocal().equals(OsType.MAC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
var path = determinePath();
|
||||||
|
return Files.exists(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final List<ExternalEditorType> WINDOWS_EDITORS = List.of(VSCODE, NOTEPADPLUSPLUS_WINDOWS, NOTEPAD);
|
||||||
|
public static final List<PathType> LINUX_EDITORS =
|
||||||
|
List.of(VSCODE_LINUX, NOTEPADPLUSPLUS_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD);
|
||||||
|
public static final List<ExternalEditorType> MACOS_EDITORS =
|
||||||
|
List.of(VSCODE_MACOS, SUBLIME_MACOS, NOTEPADPP_MACOS, TEXT_EDIT);
|
||||||
|
|
||||||
|
public static final List<ExternalEditorType> ALL = new ArrayList<>();
|
||||||
|
static {
|
||||||
|
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
ALL.addAll(WINDOWS_EDITORS);
|
||||||
|
}
|
||||||
|
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
|
ALL.addAll(LINUX_EDITORS);
|
||||||
|
}
|
||||||
|
if (OsType.getLocal().equals(OsType.MAC)) {
|
||||||
|
ALL.addAll(MACOS_EDITORS);
|
||||||
|
}
|
||||||
|
ALL.add(CUSTOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void detectDefault() {
|
||||||
|
var typeProperty = AppPrefs.get().externalEditor;
|
||||||
|
var customProperty = AppPrefs.get().customEditorCommand;
|
||||||
|
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
typeProperty.set(WINDOWS_EDITORS.stream()
|
||||||
|
.filter(externalEditorType -> externalEditorType.isAvailable())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
|
var env = System.getenv("VISUAL");
|
||||||
|
if (env != null) {
|
||||||
|
var found = LINUX_EDITORS.stream()
|
||||||
|
.filter(externalEditorType -> externalEditorType.command.equalsIgnoreCase(env))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (found == null) {
|
||||||
|
typeProperty.set(CUSTOM);
|
||||||
|
customProperty.set(env);
|
||||||
|
} else {
|
||||||
|
typeProperty.set(found);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typeProperty.set(LINUX_EDITORS.stream()
|
||||||
|
.filter(externalEditorType -> externalEditorType.isAvailable())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OsType.getLocal().equals(OsType.MAC)) {
|
||||||
|
typeProperty.set(MACOS_EDITORS.stream()
|
||||||
|
.filter(externalEditorType -> externalEditorType.isAvailable())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ public enum ExternalStartupBehaviour implements PrefsChoiceValue {
|
||||||
private final String id;
|
private final String id;
|
||||||
private final OperationMode mode;
|
private final OperationMode mode;
|
||||||
|
|
||||||
public boolean isSupported() {
|
public boolean isSelectable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,8 @@ public interface ShellType {
|
||||||
|
|
||||||
List<String> executeCommandListWithShell(String cmd);
|
List<String> executeCommandListWithShell(String cmd);
|
||||||
|
|
||||||
|
List<String> executeCommandListWithShell(List<String> cmd);
|
||||||
|
|
||||||
List<String> getMkdirsCommand(String dirs);
|
List<String> getMkdirsCommand(String dirs);
|
||||||
|
|
||||||
String getFileReadCommand(String file);
|
String getFileReadCommand(String file);
|
||||||
|
|
|
@ -141,6 +141,14 @@ public class ShellTypes {
|
||||||
return List.of("cmd", "/C", cmd.replaceAll("[\\^]", "^$0"));
|
return List.of("cmd", "/C", cmd.replaceAll("[\\^]", "^$0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> executeCommandListWithShell(List<String> cmd) {
|
||||||
|
var list = new ArrayList<String>();
|
||||||
|
list.addAll(List.of("cmd", "/C"));
|
||||||
|
list.addAll(cmd.stream().map(s -> s.replaceAll(" ", "^ ")).toList());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getMkdirsCommand(String dirs) {
|
public List<String> getMkdirsCommand(String dirs) {
|
||||||
return List.of("(", "if", "not", "exist", dirs, "mkdir", dirs, ")");
|
return List.of("(", "if", "not", "exist", dirs, "mkdir", dirs, ")");
|
||||||
|
@ -230,6 +238,11 @@ public class ShellTypes {
|
||||||
@Value
|
@Value
|
||||||
public static class PowerShell implements ShellType {
|
public static class PowerShell implements ShellType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> executeCommandListWithShell(List<String> cmd) {
|
||||||
|
return List.of("powershell", "-Command", flatten(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableHistory(ShellProcessControl pc) throws Exception {
|
public void disableHistory(ShellProcessControl pc) throws Exception {
|
||||||
pc.executeLine("Set-PSReadLineOption -HistorySaveStyle SaveNothing");
|
pc.executeLine("Set-PSReadLineOption -HistorySaveStyle SaveNothing");
|
||||||
|
@ -416,6 +429,11 @@ public class ShellTypes {
|
||||||
|
|
||||||
public abstract static class PosixBase implements ShellType {
|
public abstract static class PosixBase implements ShellType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> executeCommandListWithShell(List<String> cmd) {
|
||||||
|
return List.of(getExecutable(), "-c", flatten(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getInitFileOpenCommand(String file) {
|
public String getInitFileOpenCommand(String file) {
|
||||||
return getName() + " --rcfile \"" + file + "\"";
|
return getName() + " --rcfile \"" + file + "\"";
|
||||||
|
|
|
@ -39,11 +39,15 @@ public interface PrefsChoiceValue extends Translatable {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return all.stream().filter(t -> ((PrefsChoiceValue) t).isSupported()).toList();
|
return all.stream().filter(t -> ((PrefsChoiceValue) t).isSelectable()).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean isSupported() {
|
default boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isSelectable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,4 +34,6 @@ public abstract class PrefsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void addPrefs(PrefsHandler handler);
|
public abstract void addPrefs(PrefsHandler handler);
|
||||||
|
|
||||||
|
public abstract void init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,27 @@ package io.xpipe.extension.util;
|
||||||
|
|
||||||
import io.xpipe.core.process.ShellProcessControl;
|
import io.xpipe.core.process.ShellProcessControl;
|
||||||
import io.xpipe.core.process.ShellTypes;
|
import io.xpipe.core.process.ShellTypes;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.util.List;
|
||||||
|
|
||||||
public class ApplicationHelper {
|
public class ApplicationHelper {
|
||||||
|
|
||||||
public static void executeLocalApplication(String s) throws Exception {
|
public static void executeLocalApplication(String s) throws Exception {
|
||||||
var args = ShellTypes.getPlatformDefault().executeCommandListWithShell(s);
|
var args = ShellTypes.getPlatformDefault().executeCommandListWithShell(s);
|
||||||
var p = new ProcessBuilder(args).redirectOutput(ProcessBuilder.Redirect.DISCARD).start();
|
TrackEvent.withDebug("proc", "Executing local application").elements(args).handle();
|
||||||
var error = new String(p.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
|
try (var c = ShellStore.local().create().command(s).start()) {
|
||||||
if (p.waitFor() != 0) {
|
c.discardOrThrow();
|
||||||
throw new IOException(error);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void executeLocalApplication(List<String> s) throws Exception {
|
||||||
|
var args = ShellTypes.getPlatformDefault().executeCommandListWithShell(s);
|
||||||
|
TrackEvent.withDebug("proc", "Executing local application").elements(args).handle();
|
||||||
|
try (var c = ShellStore.local().create().command(s).start()) {
|
||||||
|
c.discardOrThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue