From 812a97d365c355efb66d67f427f32e8864bd0dca Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 6 Nov 2023 19:21:57 +0000 Subject: [PATCH] Improve split panes --- .../io/xpipe/app/browser/BrowserComp.java | 123 ++++++++---------- .../app/comp/base/SideSplitPaneComp.java | 81 ++++++++++++ .../comp/store/StoreEntryListStatusComp.java | 3 + .../xpipe/app/comp/store/StoreLayoutComp.java | 20 ++- .../io/xpipe/app/core/AppLayoutModel.java | 25 +++- .../java/io/xpipe/app/core/AppMainWindow.java | 4 +- .../io/xpipe/app/core/mode/PlatformMode.java | 1 + .../main/java/io/xpipe/app/fxcomps/Comp.java | 9 ++ .../java/io/xpipe/app/prefs/AppPrefs.java | 9 +- .../resources/style/side-split-pane-comp.css | 10 ++ .../io/xpipe/app/resources/style/style.css | 4 +- gradle/gradle_scripts/extension_test.gradle | 4 +- gradle/gradle_scripts/java.gradle | 2 +- 13 files changed, 202 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java create mode 100644 app/src/main/resources/io/xpipe/app/resources/style/side-split-pane-comp.css diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java index a9ad1b1ea..552f026b2 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java @@ -7,10 +7,13 @@ import io.xpipe.app.browser.icon.DirectoryType; import io.xpipe.app.browser.icon.FileIconManager; import io.xpipe.app.browser.icon.FileType; import io.xpipe.app.comp.base.MultiContentComp; +import io.xpipe.app.comp.base.SideSplitPaneComp; +import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.FancyTooltipAugment; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; +import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.SimpleChangeListener; @@ -26,12 +29,12 @@ import javafx.collections.ListChangeListener; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; -import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.input.DragEvent; import javafx.scene.layout.*; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -55,42 +58,35 @@ public class BrowserComp extends SimpleComp { FileIconManager.loadIfNecessary(); }); - var bookmarksList = new BrowserBookmarkList(model).createRegion(); - VBox.setVgrow(bookmarksList, Priority.ALWAYS); - var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()) - .hide(PlatformThread.sync(Bindings.createBooleanBinding( - () -> { - if (model.getOpenFileSystems().size() == 0) { - return true; - } + var bookmarksList = new BrowserBookmarkList(model).vgrow(); + var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide( + PlatformThread.sync(Bindings.createBooleanBinding(() -> { + if (model.getOpenFileSystems().size() == 0) { + return true; + } - if (model.getMode().isChooser()) { - return true; - } + if (model.getMode().isChooser()) { + return true; + } - // Also show on local - if (model.getSelected().getValue() != null) { - // return model.getSelected().getValue().isLocal(); - } + // Also show on local + if (model.getSelected().getValue() != null) { + // return model.getSelected().getValue().isLocal(); + } - return false; - }, - model.getOpenFileSystems(), - model.getSelected()))) - .createRegion(); - localDownloadStage.setPrefHeight(200); - localDownloadStage.setMaxHeight(200); - var vertical = new VBox(bookmarksList, localDownloadStage); - vertical.setFillWidth(true); + return false; + }, model.getOpenFileSystems(), model.getSelected()))); + localDownloadStage.prefHeight(200); + localDownloadStage.maxHeight(200); + var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage)); - var splitPane = new SplitPane(vertical, createTabs()); - splitPane - .widthProperty() - .addListener( - // set sidebar width in pixels depending on split pane width - (obs, old, val) -> splitPane.setDividerPosition(0, 360 / splitPane.getWidth())); - - var r = addBottomBar(splitPane); + var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth( + AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange( + AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> { + struc.getLeft().setMinWidth(200); + struc.getLeft().setMaxWidth(500); + }); + var r = addBottomBar(splitPane.createRegion()); r.getStyleClass().add("browser"); // AppFont.small(r); return r; @@ -108,16 +104,12 @@ public class BrowserComp extends SimpleComp { selected.setSpacing(10); model.getSelection().addListener((ListChangeListener) c -> { PlatformThread.runLaterIfNeeded(() -> { - selected.getChildren() - .setAll(c.getList().stream() - .map(s -> { - var field = - new TextField(s.getRawFileEntry().getPath()); - field.setEditable(false); - field.setPrefWidth(400); - return field; - }) - .toList()); + selected.getChildren().setAll(c.getList().stream().map(s -> { + var field = new TextField(s.getRawFileEntry().getPath()); + field.setEditable(false); + field.setPrefWidth(500); + return field; + }).toList()); }); }); var spacer = new Spacer(Orientation.HORIZONTAL); @@ -135,15 +127,14 @@ public class BrowserComp extends SimpleComp { return layout; } - private Node createTabs() { - var multi = new MultiContentComp(Map., ObservableValue>of( - Comp.of(() -> createTabPane()), + private Comp createTabs() { + var multi = new MultiContentComp(Map., ObservableValue>of(Comp.of(() -> createTabPane()), BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())), new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)), Bindings.createBooleanBinding(() -> { return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser(); }, model.getOpenFileSystems()))); - return multi.createRegion(); + return multi; } private TabPane createTabPane() { @@ -163,8 +154,7 @@ public class BrowserComp extends SimpleComp { map.put(v, t); tabs.getTabs().add(t); }); - tabs.getSelectionModel() - .select(model.getOpenFileSystems().indexOf(model.getSelected().getValue())); + tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue())); // Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually! var modifying = new SimpleBooleanProperty(); @@ -180,9 +170,9 @@ public class BrowserComp extends SimpleComp { return; } - var source = map.entrySet().stream() - .filter(openFileSystemModelTabEntry -> - openFileSystemModelTabEntry.getValue().equals(newValue)) + var source = map.entrySet() + .stream() + .filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue)) .findAny() .map(Map.Entry::getKey) .orElse(null); @@ -197,9 +187,9 @@ public class BrowserComp extends SimpleComp { return; } - var toSelect = map.entrySet().stream() - .filter(openFileSystemModelTabEntry -> - openFileSystemModelTabEntry.getKey().equals(newValue)) + var toSelect = map.entrySet() + .stream() + .filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue)) .findAny() .map(Map.Entry::getValue) .orElse(null); @@ -238,9 +228,9 @@ public class BrowserComp extends SimpleComp { tabs.getTabs().addListener((ListChangeListener) c -> { while (c.next()) { for (var r : c.getRemoved()) { - var source = map.entrySet().stream() - .filter(openFileSystemModelTabEntry -> - openFileSystemModelTabEntry.getValue().equals(r)) + var source = map.entrySet() + .stream() + .filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r)) .findAny() .orElse(null); @@ -263,19 +253,14 @@ public class BrowserComp extends SimpleComp { ring.setMinSize(16, 16); ring.setPrefSize(16, 16); ring.setMaxSize(16, 16); - ring.progressProperty() - .bind(Bindings.createDoubleBinding( - () -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy()))); + ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy()))); var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore()); var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion(); - tab.graphicProperty() - .bind(Bindings.createObjectBinding( - () -> { - return model.getBusy().get() ? ring : logo; - }, - PlatformThread.sync(model.getBusy()))); + tab.graphicProperty().bind(Bindings.createObjectBinding(() -> { + return model.getBusy().get() ? ring : logo; + }, PlatformThread.sync(model.getBusy()))); tab.setText(model.getName()); tab.setContent(new OpenFileSystemComp(model).createSimple()); @@ -301,9 +286,7 @@ public class BrowserComp extends SimpleComp { c.getStyleClass().add(color.getId()); } new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c); - c.addEventHandler( - DragEvent.DRAG_ENTERED, - mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab))); + c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab))); }); } }); diff --git a/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java b/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java new file mode 100644 index 000000000..5bfb9400c --- /dev/null +++ b/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java @@ -0,0 +1,81 @@ +package io.xpipe.app.comp.base; + +import io.xpipe.app.fxcomps.Comp; +import io.xpipe.app.fxcomps.CompStructure; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.Region; +import lombok.Value; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public class SideSplitPaneComp extends Comp { + + private final Comp left; + private final Comp center; + private Double initialWidth; + private Consumer onDividerChange; + public SideSplitPaneComp(Comp left, Comp center) { + this.left = left; + this.center = center; + } + + @Override + public Structure createBase() { + var c = center.createRegion(); + var sidebar = left.createRegion(); + if (initialWidth != null) { + sidebar.setPrefWidth(initialWidth); + } + var r = new SplitPane(sidebar, c); + + AtomicBoolean setInitial = new AtomicBoolean(false); + r.widthProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.doubleValue() <= 0) { + return; + } + + if (!setInitial.get() && initialWidth != null) { + r.getDividers().get(0).setPosition(initialWidth / newValue.doubleValue()); + setInitial.set(true); + } + }); + + SplitPane.setResizableWithParent(sidebar, false); + r.getDividers().get(0).positionProperty().addListener((observable, oldValue, newValue) -> { + if (r.getWidth() <= 0) { + return; + } + + if (onDividerChange != null) { + onDividerChange.accept(newValue.doubleValue() * r.getWidth()); + } + }); + r.getStyleClass().add("side-split-pane-comp"); + return new Structure(sidebar, c, r, r.getDividers().get(0)); + } + + public SideSplitPaneComp withInitialWidth(double val) { + this.initialWidth = val; + return this; + } + + public SideSplitPaneComp withOnDividerChange(Consumer onDividerChange) { + this.onDividerChange = onDividerChange; + return this; + } + + @Value + public static class Structure implements CompStructure { + + Region left; + Region center; + SplitPane pane; + SplitPane.Divider divider; + + @Override + public SplitPane get() { + return pane; + } + } +} diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java index 1b3d4b754..d55b40aa0 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java @@ -17,6 +17,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.layout.*; +import javafx.scene.text.TextAlignment; import org.kordamp.ikonli.javafx.FontIcon; public class StoreEntryListStatusComp extends SimpleComp { @@ -74,6 +75,8 @@ public class StoreEntryListStatusComp extends SimpleComp { private Region createButtons() { var menu = new MenuButton(AppI18n.get("addConnections"), new FontIcon("mdi2p-plus-thick")); + menu.setAlignment(Pos.CENTER); + menu.setTextAlignment(TextAlignment.CENTER); AppFont.medium(menu); GrowAugment.create(true, false).augment(menu); StoreCreationMenu.addButtons(menu); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java index b14cef275..8cb694ced 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java @@ -1,12 +1,12 @@ package io.xpipe.app.comp.store; +import io.xpipe.app.comp.base.SideSplitPaneComp; import io.xpipe.app.core.AppActionLinkDetector; +import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.augment.GrowAugment; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.Region; public class StoreLayoutComp extends SimpleComp { @@ -19,14 +19,12 @@ public class StoreLayoutComp extends SimpleComp { @Override protected Region createSimple() { - var listComp = new StoreEntryListComp().apply(GrowAugment.create(false, true)); - var r = new BorderPane(); - - var listR = listComp.createRegion(); - var groupHeader = new StoreSidebarComp().createRegion(); - r.setLeft(groupHeader); - r.setCenter(listR); - r.getStyleClass().add("layout"); - return r; + var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth( + AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> { + AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble); + }).createStructure(); + struc.getLeft().setMaxWidth(400); + struc.get().getStyleClass().add("store-layout"); + return struc.get(); } } diff --git a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java index 2b69c9af1..97969656c 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java +++ b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java @@ -10,7 +10,10 @@ import io.xpipe.app.util.LicenseProvider; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import lombok.Builder; +import lombok.Data; import lombok.Getter; +import lombok.extern.jackson.Jacksonized; import java.util.ArrayList; import java.util.List; @@ -18,6 +21,15 @@ import java.util.List; @Getter public class AppLayoutModel { + @Data + @Builder + @Jacksonized + public static class SavedState { + + double sidebarWidth; + double browserConnectionsWidth; + } + private static AppLayoutModel INSTANCE; public static AppLayoutModel get() { @@ -25,13 +37,22 @@ public class AppLayoutModel { } public static void init() { - INSTANCE = new AppLayoutModel(); + var state = AppCache.get("layoutState", SavedState.class, () -> new SavedState(250, 300)); + INSTANCE = new AppLayoutModel(state); } + public static void reset() { + AppCache.update("layoutState", INSTANCE.savedState); + INSTANCE = null; + } + + @Getter + private final SavedState savedState; private final List entries; private final Property selected; - public AppLayoutModel() { + public AppLayoutModel(SavedState savedState) { + this.savedState = savedState; this.entries = createEntryList(); this.selected = new SimpleObjectProperty<>(entries.get(1)); } diff --git a/app/src/main/java/io/xpipe/app/core/AppMainWindow.java b/app/src/main/java/io/xpipe/app/core/AppMainWindow.java index 26562960c..bf7fc0331 100644 --- a/app/src/main/java/io/xpipe/app/core/AppMainWindow.java +++ b/app/src/main/java/io/xpipe/app/core/AppMainWindow.java @@ -182,7 +182,7 @@ public class AppMainWindow { } private void saveState() { - if (!AppPrefs.get().saveWindowLocation.get()) { + if (!AppPrefs.get().saveWindowLocation().get()) { return; } @@ -201,7 +201,7 @@ public class AppMainWindow { } private WindowState loadState() { - if (!AppPrefs.get().saveWindowLocation.get()) { + if (!AppPrefs.get().saveWindowLocation().get()) { return null; } diff --git a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java index 16f8dd5ea..9ad2fba57 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java @@ -62,6 +62,7 @@ public abstract class PlatformMode extends OperationMode { TrackEvent.info("mode", "Shutting down platform components"); onSwitchFrom(); StoreViewState.reset(); + AppLayoutModel.reset(); PlatformState.teardown(); TrackEvent.info("mode", "Platform shutdown finished"); BACKGROUND.finalTeardown(); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java b/app/src/main/java/io/xpipe/app/fxcomps/Comp.java index 4f6908691..e716e6530 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/Comp.java @@ -83,6 +83,15 @@ public abstract class Comp> { return apply(struc -> struc.get().setPrefHeight(height)); } + + public Comp maxWidth(int width) { + return apply(struc -> struc.get().setMaxWidth(width)); + } + + public Comp maxHeight(int height) { + return apply(struc -> struc.get().setMaxHeight(height)); + } + public Comp hgrow() { return apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)); } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 2fbb5a21a..db1b89c2a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -112,8 +112,7 @@ public class AppPrefs { return connectionTimeOut; } - private final BooleanProperty saveWindowLocationInternal = typed(new SimpleBooleanProperty(false), Boolean.class); - public final ReadOnlyBooleanProperty saveWindowLocation = saveWindowLocationInternal; + private final BooleanProperty saveWindowLocation = typed(new SimpleBooleanProperty(true), Boolean.class); // External terminal // ================= @@ -336,6 +335,10 @@ public class AppPrefs { return windowOpacity; } + public ObservableBooleanValue saveWindowLocation() { + return saveWindowLocation; + } + public ObservableBooleanValue developerDisableUpdateVersionCheck() { return developerDisableUpdateVersionCheckEffective; } @@ -591,7 +594,7 @@ public class AppPrefs { Setting.of("useSystemFont", BooleanField.ofBooleanType(useSystemFontInternal).render(() -> new CustomToggleControl()), useSystemFontInternal), Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax), Setting.of("language", languageControl, languageInternal)), - Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocationInternal).render(() -> new CustomToggleControl()), saveWindowLocationInternal))), + Group.of("windowOptions", Setting.of("saveWindowLocation", BooleanField.ofBooleanType(saveWindowLocation).render(() -> new CustomToggleControl()), saveWindowLocation))), Category.of( "connections", Group.of( diff --git a/app/src/main/resources/io/xpipe/app/resources/style/side-split-pane-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/side-split-pane-comp.css new file mode 100644 index 000000000..6d39fc259 --- /dev/null +++ b/app/src/main/resources/io/xpipe/app/resources/style/side-split-pane-comp.css @@ -0,0 +1,10 @@ +.side-split-pane-comp.split-pane-divider { + -fx-padding: 1; +} + +.side-split-pane-comp .split-pane-divider .horizontal-grabber { + -fx-padding: 0; + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-shape: " "; +} \ No newline at end of file diff --git a/app/src/main/resources/io/xpipe/app/resources/style/style.css b/app/src/main/resources/io/xpipe/app/resources/style/style.css index 4ed1be1cf..c6592c0e8 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/style.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/style.css @@ -13,8 +13,8 @@ -fx-padding: 1.2em; } -.layout { - +.store-layout .split-pane-divider { + -fx-background-color: transparent; } .root:dark.background { diff --git a/gradle/gradle_scripts/extension_test.gradle b/gradle/gradle_scripts/extension_test.gradle index cc7abbecb..a69e936c1 100644 --- a/gradle/gradle_scripts/extension_test.gradle +++ b/gradle/gradle_scripts/extension_test.gradle @@ -18,7 +18,7 @@ def daemonCommand = attachDebugger ? ':app:runAttachedDebugger' : ':app:run' test { workingDir = rootDir - jvmArgs += ["--enable-preview", "-Xmx2g"] + jvmArgs += ["-Xmx2g"] // Daemon properties systemProperty "io.xpipe.beacon.daemonArgs", @@ -51,7 +51,7 @@ task productionTest(type: Test) { useJUnitPlatform() workingDir = rootDir - jvmArgs += ["--enable-preview", "-Xmx2g"] + jvmArgs += ["-Xmx2g"] // Daemon properties systemProperty "io.xpipe.beacon.daemonArgs", diff --git a/gradle/gradle_scripts/java.gradle b/gradle/gradle_scripts/java.gradle index 910ecb25a..ee14af1d1 100644 --- a/gradle/gradle_scripts/java.gradle +++ b/gradle/gradle_scripts/java.gradle @@ -4,7 +4,7 @@ tasks.withType(JavaCompile).configureEach { modularity.inferModulePath = true options.encoding = 'UTF-8' options.compilerArgs << "-Xlint:unchecked" - options.compilerArgs << "--enable-preview" + // options.compilerArgs << "--enable-preview" // These settings are explicitly specified as they can cause problems with annotation processors options.compilerArgs << "-implicit:none"