diff --git a/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java b/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java index bd13ccd64..7d1a6f62c 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java @@ -80,6 +80,15 @@ public class SideMenuBarComp extends Comp> { vbox.getChildren().add(b.createRegion()); } + { + var b = new IconButtonComp("mdi2c-comment-processing-outline", () -> Hyperlinks.open(Hyperlinks.ROADMAP)) + .apply(new FancyTooltipAugment<>("roadmap")); + b.apply(struc -> { + AppFont.setSize(struc.get(), 2); + }); + vbox.getChildren().add(b.createRegion()); + } + { var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded()) .apply(new FancyTooltipAugment<>("updateAvailableTooltip")); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java index cf203968e..ef517f03d 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java @@ -380,7 +380,9 @@ public abstract class StoreEntryComp extends SimpleComp { } var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline")); - del.disableProperty().bind(wrapper.getDeletable().not()); + del.disableProperty().bind(Bindings.createBooleanBinding(() -> { + return !wrapper.getDeletable().get() && !AppPrefs.get().developerDisableGuiRestrictions().get(); + }, wrapper.getDeletable(), AppPrefs.get().developerDisableGuiRestrictions())); del.setOnAction(event -> wrapper.delete()); contextMenu.getItems().add(del); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java index 2e4b129db..3fec04c3a 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java @@ -195,6 +195,11 @@ public class StoreEntryWrapper { } public void executeDefaultAction() throws Exception { + if (getEntry().getValidity() == DataStoreEntry.Validity.INCOMPLETE) { + editDialog(); + return; + } + var found = getDefaultActionProvider().getValue(); entry.updateLastUsed(); if (found != null) { diff --git a/app/src/main/java/io/xpipe/app/core/AppGreetings.java b/app/src/main/java/io/xpipe/app/core/AppGreetings.java index 2b7abed04..7a7d75bd4 100644 --- a/app/src/main/java/io/xpipe/app/core/AppGreetings.java +++ b/app/src/main/java/io/xpipe/app/core/AppGreetings.java @@ -6,6 +6,7 @@ import io.xpipe.app.core.mode.OperationMode; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.util.Hyperlinks; import io.xpipe.app.util.MarkdownHelper; import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; @@ -110,9 +111,7 @@ public class AppGreetings { temp, MarkdownHelper.toHtml(Files.readString(file), UnaryOperator.identity())); }); - App.getApp() - .getHostServices() - .showDocument(temp.toUri().toString()); + Hyperlinks.open(temp.toUri().toString()); } catch (IOException e) { ErrorEvent.fromThrowable(e).handle(); } diff --git a/app/src/main/java/io/xpipe/app/core/AppTheme.java b/app/src/main/java/io/xpipe/app/core/AppTheme.java index e35abad84..bcac047a6 100644 --- a/app/src/main/java/io/xpipe/app/core/AppTheme.java +++ b/app/src/main/java/io/xpipe/app/core/AppTheme.java @@ -36,6 +36,10 @@ public class AppTheme { private static final PseudoClass PERFORMANCE = PseudoClass.getPseudoClass("performance"); public static void initThemeHandlers(Window stage) { + if (AppPrefs.get() == null) { + return; + } + SimpleChangeListener.apply(AppPrefs.get().theme, t -> { Theme.ALL.forEach(theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId())); if (t == null) { @@ -73,17 +77,20 @@ public class AppTheme { t.apply(); TrackEvent.debug("Set theme " + t.getId() + " for scene"); - detector.registerListener(dark -> { - PlatformThread.runLaterIfNeeded(() -> { - if (dark && !AppPrefs.get().theme.getValue().isDark()) { - AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme()); - } + // The gnome detector sometimes runs into issues, also it's not that important + if (!OsType.getLocal().equals(OsType.LINUX)) { + detector.registerListener(dark -> { + PlatformThread.runLaterIfNeeded(() -> { + if (dark && !AppPrefs.get().theme.getValue().isDark()) { + AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme()); + } - if (!dark && AppPrefs.get().theme.getValue().isDark()) { - AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme()); - } + if (!dark && AppPrefs.get().theme.getValue().isDark()) { + AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme()); + } + }); }); - }); + } AppPrefs.get().theme.addListener((c, o, n) -> { changeTheme(n); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/Shortcuts.java b/app/src/main/java/io/xpipe/app/fxcomps/util/Shortcuts.java index f39faea87..316c4e95c 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/Shortcuts.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/Shortcuts.java @@ -24,6 +24,11 @@ public class Shortcuts { public static void addShortcut(T region, KeyCombination comb, Consumer exec) { var filter = new EventHandler() { public void handle(KeyEvent ke) { + var target = ke.getTarget(); + if (!region.isVisible() || !region.isManaged() || region.isDisabled()) { + return; + } + if (comb.match(ke)) { exec.accept(region); ke.consume(); diff --git a/app/src/main/java/io/xpipe/app/issue/GuiErrorHandlerBase.java b/app/src/main/java/io/xpipe/app/issue/GuiErrorHandlerBase.java index 64090ce8a..e618a3942 100644 --- a/app/src/main/java/io/xpipe/app/issue/GuiErrorHandlerBase.java +++ b/app/src/main/java/io/xpipe/app/issue/GuiErrorHandlerBase.java @@ -10,6 +10,10 @@ import java.util.function.Consumer; public class GuiErrorHandlerBase { protected boolean startupGui(Consumer onFail) { + if (PlatformState.getCurrent() == PlatformState.EXITED) { + return false; + } + if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) { try { CountDownLatch latch = new CountDownLatch(1); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index 1665a40ac..a178ce874 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -37,7 +37,9 @@ public abstract class DataStorage { private static DataStorage INSTANCE; protected final Path dir; + @Getter protected final List storeCategories; + @Getter protected final Set storeEntries; @Getter @@ -159,6 +161,8 @@ public abstract class DataStorage { listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd)); refreshValidities(true); } + + saveAsync(); } public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) { @@ -171,6 +175,7 @@ public abstract class DataStorage { var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new); listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd)); + saveAsync(); } public boolean refreshChildren(DataStoreEntry e) { @@ -228,6 +233,15 @@ public abstract class DataStorage { return !newChildren.isEmpty(); } + public void deleteChildren(DataStoreEntry e) { + var c = getDeepStoreChildren(e); + c.forEach(entry -> entry.finalizeEntry()); + this.storeEntries.removeAll(c); + this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new))); + refreshValidities(false); + saveAsync(); + } + public void deleteWithChildren(DataStoreEntry... entries) { var toDelete = Arrays.stream(entries) .flatMap(entry -> { @@ -244,15 +258,130 @@ public abstract class DataStorage { saveAsync(); } - public void deleteChildren(DataStoreEntry e) { - var c = getDeepStoreChildren(e); - c.forEach(entry -> entry.finalizeEntry()); - this.storeEntries.removeAll(c); - this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new))); + + public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) { + if (storeCategories.contains(cat)) { + return cat; + } + + var byId = getStoreCategoryIfPresent(cat.getUuid()).orElse(null); + if (byId != null) { + return byId; + } + + addStoreCategory(cat); + return cat; + } + + public void addStoreCategory(@NonNull DataStoreCategory cat) { + cat.setDirectory(getCategoriesDir().resolve(cat.getUuid().toString())); + this.storeCategories.add(cat); + saveAsync(); + + this.listeners.forEach(l -> l.onCategoryAdd(cat)); + } + + public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) { + if (storeEntries.contains(e)) { + return e; + } + + var byId = getStoreEntryIfPresent(e.getUuid()).orElse(null); + if (byId != null) { + return byId; + } + + var syntheticParent = getSyntheticParent(e); + if (syntheticParent.isPresent()) { + addStoreEntryIfNotPresent(syntheticParent.get()); + } + + var displayParent = syntheticParent.or(() -> getDisplayParent(e)); + if (displayParent.isPresent()) { + displayParent.get().setExpanded(true); + } + + e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); + this.storeEntries.add(e); + displayParent.ifPresent(p -> { + p.setChildrenCache(null); + }); + saveAsync(); + + this.listeners.forEach(l -> l.onStoreAdd(e)); + e.initializeEntry(); + refreshValidities(true); + return e; + } + + public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) { + for (DataStoreEntry e : es) { + if (storeEntries.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) { + return; + } + + var syntheticParent = getSyntheticParent(e); + if (syntheticParent.isPresent()) { + addStoreEntryIfNotPresent(syntheticParent.get()); + } + + var displayParent = syntheticParent.or(() -> getDisplayParent(e)); + if (displayParent.isPresent()) { + displayParent.get().setExpanded(true); + } + + e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); + this.storeEntries.add(e); + displayParent.ifPresent(p -> { + p.setChildrenCache(null); + }); + } + this.listeners.forEach(l -> l.onStoreAdd(es)); + for (DataStoreEntry e : es) { + e.initializeEntry(); + } + refreshValidities(true); + saveAsync(); + } + + public DataStoreEntry addStoreIfNotPresent(@NonNull String name, DataStore store) { + var f = getStoreEntryIfPresent(store); + if (f.isPresent()) { + return f.get(); + } + + var e = DataStoreEntry.createNew(UUID.randomUUID(), selectedCategory.getUuid(), name, store); + addStoreEntryIfNotPresent(e); + return e; + } + + public void deleteStoreEntry(@NonNull DataStoreEntry store) { + store.finalizeEntry(); + this.storeEntries.remove(store); + getDisplayParent(store).ifPresent(p -> p.setChildrenCache(null)); + this.listeners.forEach(l -> l.onStoreRemove(store)); refreshValidities(false); saveAsync(); } + public void deleteStoreCategory(@NonNull DataStoreCategory cat) { + if (cat.getUuid().equals(DEFAULT_CATEGORY_UUID) || cat.getUuid().equals(ALL_CONNECTIONS_CATEGORY_UUID)) { + return; + } + + storeEntries.forEach(entry -> { + if (entry.getCategoryUuid().equals(cat.getUuid())) { + entry.setCategoryUuid(DEFAULT_CATEGORY_UUID); + } + }); + + storeCategories.remove(cat); + saveAsync(); + this.listeners.forEach(l -> l.onCategoryRemove(cat)); + } + + // Get operations + public boolean isRootEntry(DataStoreEntry entry) { var noParent = DataStorage.get().getDisplayParent(entry).isEmpty(); var diffParentCategory = DataStorage.get() @@ -312,8 +441,9 @@ public abstract class DataStorage { public Set getDeepStoreChildren(DataStoreEntry entry) { var set = new HashSet(); - getStoreChildren(entry).forEach(entry1 -> { - set.addAll(getDeepStoreChildren(entry1)); + getStoreChildren(entry).forEach(c -> { + set.add(c); + set.addAll(getDeepStoreChildren(c)); }); return set; } @@ -435,112 +565,6 @@ public abstract class DataStorage { .findFirst(); } - public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) { - if (storeCategories.contains(cat)) { - return cat; - } - - var byId = getStoreCategoryIfPresent(cat.getUuid()).orElse(null); - if (byId != null) { - return byId; - } - - addStoreCategory(cat); - return cat; - } - - public void addStoreCategory(@NonNull DataStoreCategory cat) { - cat.setDirectory(getCategoriesDir().resolve(cat.getUuid().toString())); - this.storeCategories.add(cat); - saveAsync(); - - this.listeners.forEach(l -> l.onCategoryAdd(cat)); - } - - public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) { - if (storeEntries.contains(e)) { - return e; - } - - var byId = getStoreEntryIfPresent(e.getUuid()).orElse(null); - if (byId != null) { - return byId; - } - - var syntheticParent = getSyntheticParent(e); - if (syntheticParent.isPresent()) { - addStoreEntryIfNotPresent(syntheticParent.get()); - } - - var displayParent = syntheticParent.or(() -> getDisplayParent(e)); - if (displayParent.isPresent()) { - displayParent.get().setExpanded(true); - } - - e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); - this.storeEntries.add(e); - displayParent.ifPresent(p -> { - p.setChildrenCache(null); - }); - saveAsync(); - - this.listeners.forEach(l -> l.onStoreAdd(e)); - e.initializeEntry(); - refreshValidities(true); - return e; - } - - public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) { - var uuid = UuidHelper.generateFromObject(parent.getUuid(), name); - var found = getStoreEntryIfPresent(uuid); - if (found.isPresent()) { - return found.get(); - } - - return DataStoreEntry.createNew(uuid, parent.getCategoryUuid(), name, store); - } - - public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) { - for (DataStoreEntry e : es) { - if (storeEntries.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) { - return; - } - - var syntheticParent = getSyntheticParent(e); - if (syntheticParent.isPresent()) { - addStoreEntryIfNotPresent(syntheticParent.get()); - } - - var displayParent = syntheticParent.or(() -> getDisplayParent(e)); - if (displayParent.isPresent()) { - displayParent.get().setExpanded(true); - } - - e.setDirectory(getStoresDir().resolve(e.getUuid().toString())); - this.storeEntries.add(e); - displayParent.ifPresent(p -> { - p.setChildrenCache(null); - }); - } - this.listeners.forEach(l -> l.onStoreAdd(es)); - for (DataStoreEntry e : es) { - e.initializeEntry(); - } - refreshValidities(true); - saveAsync(); - } - - public DataStoreEntry addStoreIfNotPresent(@NonNull String name, DataStore store) { - var f = getStoreEntryIfPresent(store); - if (f.isPresent()) { - return f.get(); - } - - var e = DataStoreEntry.createNew(UUID.randomUUID(), selectedCategory.getUuid(), name, store); - addStoreEntryIfNotPresent(e); - return e; - } - public Optional getStoreDisplayName(DataStore store) { if (store == null) { return Optional.empty(); @@ -557,35 +581,20 @@ public abstract class DataStorage { return store.getProvider().browserDisplayName(store.getStore()); } - public void deleteStoreEntry(@NonNull DataStoreEntry store) { - store.finalizeEntry(); - this.storeEntries.remove(store); - getDisplayParent(store).ifPresent(p -> p.setChildrenCache(null)); - this.listeners.forEach(l -> l.onStoreRemove(store)); - refreshValidities(false); - saveAsync(); - } - - public void deleteStoreCategory(@NonNull DataStoreCategory cat) { - if (cat.getUuid().equals(DEFAULT_CATEGORY_UUID) || cat.getUuid().equals(ALL_CONNECTIONS_CATEGORY_UUID)) { - return; - } - - storeEntries.forEach(entry -> { - if (entry.getCategoryUuid().equals(cat.getUuid())) { - entry.setCategoryUuid(DEFAULT_CATEGORY_UUID); - } - }); - - storeCategories.remove(cat); - saveAsync(); - this.listeners.forEach(l -> l.onCategoryRemove(cat)); - } - public Optional getStoreEntryIfPresent(UUID id) { return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny(); } + public DataStoreEntry getOrCreateNewSyntheticEntry(DataStoreEntry parent, String name, DataStore store) { + var uuid = UuidHelper.generateFromObject(parent.getUuid(), name); + var found = getStoreEntryIfPresent(uuid); + if (found.isPresent()) { + return found.get(); + } + + return DataStoreEntry.createNew(uuid, parent.getCategoryUuid(), name, store); + } + public DataStoreEntry getStoreEntry(UUID id) { return getStoreEntryIfPresent(id).orElseThrow(); } @@ -594,11 +603,4 @@ public abstract class DataStorage { return getStoreEntryIfPresent(LOCAL_ID).orElse(null); } - public Set getStoreEntries() { - return storeEntries; - } - - public List getStoreCategories() { - return storeCategories; - } } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntryRef.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntryRef.java index f2d3db183..1c2e45a68 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntryRef.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntryRef.java @@ -10,6 +10,10 @@ public class DataStoreEntryRef { @NonNull DataStoreEntry entry; + public DataStoreEntryRef(@NonNull DataStoreEntry entry) { + this.entry = entry; + } + public void checkComplete() throws Exception { getStore().checkComplete(); } diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index 8e0ad0db9..b9efe6017 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -247,9 +247,7 @@ public class StandardStorage extends DataStorage { ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); } - { var hasFixedLocal = storeEntries.stream().anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID)); - // storeEntries.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore); if (!hasFixedLocal) { var e = DataStoreEntry.createNew( LOCAL_ID, DataStorage.DEFAULT_CATEGORY_UUID, "Local Machine", new LocalStore()); @@ -264,7 +262,6 @@ public class StandardStorage extends DataStorage { if (storeEntries.stream().noneMatch(entry -> entry.getColor() != null)) { local.setColor(DataStoreColor.BLUE); } - } // Refresh to update state storeEntries.forEach(dataStoreEntry -> dataStoreEntry.refresh()); @@ -278,6 +275,12 @@ public class StandardStorage extends DataStorage { refreshValidities(true); + // Save to apply changes + if (!hasFixedLocal) { + storeEntries.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore); + save(); + } + deleteLeftovers(); } diff --git a/app/src/main/java/io/xpipe/app/util/Hyperlinks.java b/app/src/main/java/io/xpipe/app/util/Hyperlinks.java index b6f7c6341..5855c6069 100644 --- a/app/src/main/java/io/xpipe/app/util/Hyperlinks.java +++ b/app/src/main/java/io/xpipe/app/util/Hyperlinks.java @@ -7,6 +7,7 @@ public class Hyperlinks { public static final String WEBSITE = "https://xpipe.io"; public static final String DOCUMENTATION = "https://docs.xpipe.io"; public static final String GITHUB = "https://github.com/xpipe-io/xpipe"; + public static final String ROADMAP = "https://xpipe.kampsite.co/"; public static final String PRIVACY = "https://github.com/xpipe-io/xpipe/blob/master/PRIVACY.md"; public static final String TOS = "https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/misc/tos.md"; public static final String SECURITY = "https://github.com/xpipe-io/xpipe/blob/master/SECURITY.md"; diff --git a/build.gradle b/build.gradle index b3c280cbd..248c08eed 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ project.ext { productName = isStage ? 'XPipe PTB' : 'XPipe' kebapProductName = isStage ? 'xpipe-ptb' : 'xpipe' publisher = 'XPipe UG (haftungsbeschränkt)' - shortDescription = 'The shell connection hub and remote file browser for your entire infrastructure' + shortDescription = 'Your entire server infrastructure at your fingertips' longDescription = 'XPipe is a new type of shell connection hub and remote file manager that allows you to access your entire sever infrastructure from your local machine. It works on top of your installed command-line programs that you normally use to connect and does not require any setup on your remote systems.' website = 'https://xpipe.io' sourceWebsite = 'https://github.com/xpipe-io/xpipe' diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialect.java b/core/src/main/java/io/xpipe/core/process/ShellDialect.java index c147d5677..a91cd43e7 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialect.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialect.java @@ -34,6 +34,10 @@ public interface ShellDialect { return other.equals(this); } + default ShellDialect getDumbReplacementDialect() { + return this; + } + String getCatchAllVariable(); CommandControl queryVersion(ShellControl shellControl); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java index d506da6cf..cf686e090 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java @@ -13,6 +13,7 @@ import lombok.extern.jackson.Jacksonized; import java.util.LinkedHashSet; import java.util.List; +import java.util.stream.Collectors; @SuperBuilder @Getter @@ -29,9 +30,18 @@ public class SimpleScriptStore extends ScriptStore { } private String assemble(ShellControl shellControl, ExecutionType type) { - var targetType = type == ExecutionType.TERMINAL_ONLY ? shellControl.getTargetTerminalShellDialect() : shellControl.getShellDialect(); - if ((executionType == type || executionType == ExecutionType.BOTH) && minimumDialect.isCompatibleTo(targetType)) { - var script = ScriptHelper.createExecScript(targetType, shellControl, commands); + var targetType = type == ExecutionType.TERMINAL_ONLY + ? shellControl.getTargetTerminalShellDialect() + : shellControl.getShellDialect(); + if ((executionType == type || executionType == ExecutionType.BOTH) + && minimumDialect.isCompatibleTo(targetType)) { + var shebang = commands.startsWith("#"); + // Fix new lines and shebang + var fixedCommands = commands.lines() + .skip(shebang ? 1 : 0) + .collect(Collectors.joining( + shellControl.getShellDialect().getNewLine().getNewLineString())); + var script = ScriptHelper.createExecScript(targetType, shellControl, fixedCommands); return targetType.sourceScriptCommand(shellControl, script); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java index 074b709df..8bab6629d 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java @@ -145,7 +145,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { .nonNull() .name("scriptContents") .description("scriptContentsDescription") - .longDescription("proc:environmentScript") + .longDescription("base:script") .addComp( new IntegratedTextAreaComp(commandProp, false, "commands", Bindings.createStringBinding(() -> { return dialect.getValue() != null diff --git a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/executionType_en.md b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/executionType_en.md index 789f29f99..c34e58bf5 100644 --- a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/executionType_en.md +++ b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/executionType_en.md @@ -6,6 +6,8 @@ Only afterward will a separate connection be made in the actual terminal. The file browser for example entirely uses the dumb background mode to handle its operations, so if you want your script environment to apply to the file browser session, it should run in the dumb mode. +If you want the script to be run when you open the connection in a terminal, then choose the terminal mode. + ### Blocking commands Blocking commands that require user input can freeze the shell process when XPipe starts it up internally first in the background. diff --git a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/script_en.md b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/script_en.md new file mode 100644 index 000000000..ee7969f5d --- /dev/null +++ b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/script_en.md @@ -0,0 +1,5 @@ +## Init script + +The contents of the script to run. You can choose to either edit this in place or use the external edit button in the top right corner to launch an external text editor. + +You don't have to specify a shebang line for shells that support it, one is added automatically with the appropriate shell type.