From e28455984d479e155dcd5fe07e65ce72574a4adb Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 3 Apr 2024 00:49:22 -0400 Subject: [PATCH] Cleanup config component annotations, add binding bundle config component factory allowing binds to be changed in the UI --- .../config/BasicCollectionConfigValue.java | 31 +++++- .../AssemblerPipelineGeneralConfig.java | 2 +- .../config/ConfigComponentFactory.java | 4 +- .../config/ConfigComponentManager.java | 3 +- .../config/TypedConfigComponentFactory.java | 8 +- .../AndroidDecompilerComponentFactory.java | 4 +- .../factories/BooleanComponentFactory.java | 4 +- .../factories/EnumComponentFactory.java | 4 +- .../factories/IntegerComponentFactory.java | 4 +- .../JvmDecompilerComponentFactory.java | 4 +- .../factories/StringComponentFactory.java | 4 +- .../coley/recaf/ui/config/Binding.java | 22 +++- .../recaf/ui/config/KeybindingConfig.java | 100 +++++++++++++++++- .../recaf/ui/config/RecentFilesConfig.java | 2 +- .../coley/recaf/ui/pane/ConfigPane.java | 10 +- .../main/resources/translations/en_US.lang | 9 ++ 16 files changed, 187 insertions(+), 28 deletions(-) diff --git a/recaf-core/src/main/java/software/coley/recaf/config/BasicCollectionConfigValue.java b/recaf-core/src/main/java/software/coley/recaf/config/BasicCollectionConfigValue.java index e15d0856a..bc211e4fa 100644 --- a/recaf-core/src/main/java/software/coley/recaf/config/BasicCollectionConfigValue.java +++ b/recaf-core/src/main/java/software/coley/recaf/config/BasicCollectionConfigValue.java @@ -20,6 +20,7 @@ public class BasicCollectionConfigValue> implements C private final Class collectionType; private final Class itemType; private final ObservableCollection observable; + private final boolean hidden; /** * @param key @@ -29,15 +30,39 @@ public class BasicCollectionConfigValue> implements C * @param observable * Observable of value. */ + public BasicCollectionConfigValue(@Nonnull String key, + @Nonnull Class type, + @Nonnull Class itemType, + @Nonnull ObservableCollection observable) { + this(key, type, itemType, observable, false); + } + + /** + * @param key + * Value key. + * @param type + * Value type class. + * @param observable + * Observable of value. + * @param hidden + * Hidden flag. + */ @SuppressWarnings({"rawtypes", "unchecked"}) public BasicCollectionConfigValue(@Nonnull String key, - @Nonnull Class type, - @Nonnull Class itemType, - @Nonnull ObservableCollection observable) { + @Nonnull Class type, + @Nonnull Class itemType, + @Nonnull ObservableCollection observable, + boolean hidden) { this.key = key; this.collectionType = (Class) type; this.itemType = itemType; this.observable = observable; + this.hidden = hidden; + } + + @Override + public boolean isHidden() { + return hidden; } @Nonnull diff --git a/recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineGeneralConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineGeneralConfig.java index a253a26d5..a55a90a5e 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineGeneralConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineGeneralConfig.java @@ -25,7 +25,7 @@ public AssemblerPipelineGeneralConfig() { super(ConfigGroups.SERVICE_ASSEMBLER, AssemblerPipelineManager.SERVICE_ID + ConfigGroups.PACKAGE_SPLIT + "general" + CONFIG_SUFFIX); addValue(new BasicConfigValue<>("disassembly-indent", String.class, disassemblyIndent)); - addValue(new BasicConfigValue<>("disassembly-ast-parse-delay", Integer.class, disassemblyAstParseDelay)); + addValue(new BasicConfigValue<>("disassembly-ast-parse-delay", int.class, disassemblyAstParseDelay)); } /** diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentFactory.java index fff1e3cd9..864cc65ce 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config; +import jakarta.annotation.Nonnull; import javafx.scene.Node; import software.coley.recaf.config.ConfigContainer; import software.coley.recaf.config.ConfigValue; @@ -45,5 +46,6 @@ public boolean isStandAlone() { * * @return Control to represent the value. */ - public abstract Node create(ConfigContainer container, ConfigValue value); + @Nonnull + public abstract Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value); } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentManager.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentManager.java index 77c12336c..b59a5ec6a 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentManager.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentManager.java @@ -27,8 +27,9 @@ public class ConfigComponentManager implements Service { public static final String ID = "config-components"; private final ConfigComponentFactory DEFAULT_FACTORY = new ConfigComponentFactory<>(false) { + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { Label label = new Label("Unsupported: " + value.getType().getName()); label.getStyleClass().add(Styles.WARNING); return label; diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/TypedConfigComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/TypedConfigComponentFactory.java index fb1df11e4..5e4244c30 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/TypedConfigComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/TypedConfigComponentFactory.java @@ -14,13 +14,13 @@ public abstract class TypedConfigComponentFactory extends ConfigComponentFact private final Class type; /** - * @param createLabel - * See {@link #isStandAlone()}. Determines if label is automatically added. + * @param isStandAlone + * See {@link #isStandAlone()}. Creates an adjacent label for the config value name if {@code true}. * @param type * The {@link ConfigValue#getType()} to support. */ - protected TypedConfigComponentFactory(boolean createLabel, Class type) { - super(createLabel); + protected TypedConfigComponentFactory(boolean isStandAlone, Class type) { + super(isStandAlone); this.type = type; } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/AndroidDecompilerComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/AndroidDecompilerComponentFactory.java index 414241bc4..8e502b4e8 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/AndroidDecompilerComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/AndroidDecompilerComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config.factories; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javafx.scene.Node; @@ -30,8 +31,9 @@ public AndroidDecompilerComponentFactory(DecompilerManager decompilerManager) { .toList(); } + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { return new ObservableComboBox<>(value.getObservable(), decompilers); } } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/BooleanComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/BooleanComponentFactory.java index 5408e4e69..ed61ed9fe 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/BooleanComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/BooleanComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config.factories; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javafx.scene.Node; @@ -22,8 +23,9 @@ public BooleanComponentFactory() { super(true, boolean.class); } + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { Observable observable = value.getObservable(); String translationKey = container.getScopedId(value); diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/EnumComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/EnumComponentFactory.java index 9562cc0ee..1318a1282 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/EnumComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/EnumComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config.factories; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javafx.scene.Node; @@ -23,8 +24,9 @@ public EnumComponentFactory() { super(false, Enum.class); } + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { Enum[] enumConstants = value.getType().getEnumConstants(); return new ObservableComboBox<>(value.getObservable(), Arrays.asList(enumConstants)); } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/IntegerComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/IntegerComponentFactory.java index fc114d6ab..e8fef82db 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/IntegerComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/IntegerComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config.factories; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javafx.beans.value.ChangeListener; @@ -27,8 +28,9 @@ protected IntegerComponentFactory() { super(false, int.class); } + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { Observable observable = value.getObservable(); TextField textField = new TextField(); textField.setText(Integer.toString(observable.getValue())); diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/JvmDecompilerComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/JvmDecompilerComponentFactory.java index 5d0f57881..e7ad373f4 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/JvmDecompilerComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/JvmDecompilerComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config.factories; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javafx.scene.Node; @@ -30,8 +31,9 @@ public JvmDecompilerComponentFactory(DecompilerManager decompilerManager) { .toList(); } + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { return new ObservableComboBox<>(value.getObservable(), decompilers); } } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/StringComponentFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/StringComponentFactory.java index 9d5466829..4f443eeae 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/StringComponentFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/factories/StringComponentFactory.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.config.factories; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javafx.scene.Node; @@ -21,8 +22,9 @@ protected StringComponentFactory() { super(false, String.class); } + @Nonnull @Override - public Node create(ConfigContainer container, ConfigValue value) { + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { Observable observable = value.getObservable(); TextField textField = new TextField(); textField.setText(observable.getValue()); diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/config/Binding.java b/recaf-ui/src/main/java/software/coley/recaf/ui/config/Binding.java index 588144406..d0fd8efbd 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/config/Binding.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/config/Binding.java @@ -43,6 +43,7 @@ public String getId() { * * @return Binding from keys + mask. */ + @Nonnull public static Binding newBind(@Nonnull String id, @Nonnull String codesString, @Nullable KeyCode mask) { String[] codes = codesString.split("\\+"); Stream stream = Arrays.stream(codes); @@ -59,6 +60,7 @@ public static Binding newBind(@Nonnull String id, @Nonnull String codesString, @ * * @return Binding from keys of the event. */ + @Nonnull public static Binding newBind(@Nonnull String id, @Nonnull KeyEvent event) { return newBind(id, namesOf(event)); } @@ -72,12 +74,29 @@ public static Binding newBind(@Nonnull String id, @Nonnull KeyEvent event) { * * @return Binding from keys. */ + @Nonnull public static Binding newBind(@Nonnull String id, @Nonnull KeyCode... codes) { return Arrays.stream(codes).map(KeyCode::getName) .map(String::toLowerCase) .collect(Collectors.toCollection(() -> new Binding(id))); } + /** + * @param id + * Unique identifier of the binding. + * @param codes + * Series of keys for a keybind, in string representation. + * + * @return Binding from keys. + */ + @Nonnull + public static Binding from(@Nonnull String id, @Nonnull Collection codes) { + return codes.stream() + .map(String::toLowerCase) + .sorted((a, b) -> (a.length() > b.length()) ? -1 : a.compareTo(b)) + .collect(Collectors.toCollection(() -> new Binding(id))); + } + /** * @param id * Unique identifier of the binding. @@ -86,6 +105,7 @@ public static Binding newBind(@Nonnull String id, @Nonnull KeyCode... codes) { * * @return Binding from keys. */ + @Nonnull public static Binding newBind(@Nonnull String id, @Nonnull Collection codes) { return codes.stream() .map(String::toLowerCase) @@ -95,7 +115,7 @@ public static Binding newBind(@Nonnull String id, @Nonnull Collection co @Override public String toString() { - return String.join("+", this); + return String.join(" + ", this); } /** diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/config/KeybindingConfig.java b/recaf-ui/src/main/java/software/coley/recaf/ui/config/KeybindingConfig.java index 18b99c45f..f78e49d78 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/config/KeybindingConfig.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/config/KeybindingConfig.java @@ -1,21 +1,27 @@ package software.coley.recaf.ui.config; -import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import javafx.event.Event; +import javafx.scene.Node; +import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; +import javafx.scene.layout.GridPane; +import software.coley.observables.ObservableBoolean; import software.coley.observables.ObservableMap; -import software.coley.recaf.config.BasicConfigContainer; -import software.coley.recaf.config.BasicMapConfigValue; -import software.coley.recaf.config.ConfigGroups; +import software.coley.recaf.config.*; +import software.coley.recaf.services.config.ConfigComponentManager; +import software.coley.recaf.services.config.TypedConfigComponentFactory; import software.coley.recaf.services.json.GsonProvider; +import software.coley.recaf.ui.control.BoundLabel; import software.coley.recaf.ui.control.richtext.Editor; import software.coley.recaf.ui.control.richtext.search.SearchBar; import software.coley.recaf.ui.pane.editing.ClassPane; import software.coley.recaf.ui.pane.editing.FilePane; import software.coley.recaf.ui.pane.editing.jvm.JvmDecompilerPane; +import software.coley.recaf.util.Lang; import software.coley.recaf.util.PlatformType; import java.util.*; @@ -44,7 +50,7 @@ public class KeybindingConfig extends BasicConfigContainer { private final BindingBundle bundle; @Inject - public KeybindingConfig(@Nonnull GsonProvider gsonProvider) { + public KeybindingConfig(@Nonnull GsonProvider gsonProvider, @Nonnull ConfigComponentManager componentManager) { super(ConfigGroups.SERVICE_UI, ID + CONFIG_SUFFIX); // We will only be storing one 'value' so that the UI can treat it as a singular element. @@ -75,6 +81,26 @@ public KeybindingConfig(@Nonnull GsonProvider gsonProvider) { return new BindingBundle(bindings); }); + + // Register custom config component display for the binding bundle. + componentManager.register(BindingBundle.class, new TypedConfigComponentFactory<>(true, BindingBundle.class) { + @Nonnull + @Override + public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue value) { + GridPane grid = new GridPane(); + grid.setVgap(10); + grid.setHgap(10); + + // Tree-map should show the items in grouped order by key. + new TreeMap<>(bundle).forEach((key, bind) -> { + BoundLabel label = new BoundLabel(Lang.getBinding("bind." + key)); + BindingInputField inputField = new BindingInputField(bundle, key, bind); + grid.addRow(grid.getRowCount(), label, inputField); + }); + + return grid; + } + }); } /** @@ -147,8 +173,72 @@ private static Binding createBindForPlatform(String id, KeyCode... codes) { * Binding bundle containing all keys. */ public static class BindingBundle extends ObservableMap> { + private final ObservableBoolean isEditing = new ObservableBoolean(false); + public BindingBundle(@Nonnull List binds) { super(binds.stream().collect(Collectors.toMap(Binding::getId, Function.identity())), HashMap::new); } + + /** + * Marks the bundle as being live-edited or not. Global key-binds should not be handled when editing. + * + * @param editing + * {@code true} to indicate the bundle is being edited by the user. + * {@code false} to indicate the user is not editing. + */ + public void setIsEditing(boolean editing) { + isEditing.setValue(editing); + } + } + + /** + * Text field that updates a {@link Binding}. + */ + private static class BindingInputField extends TextField { + private Binding lastState; + + private BindingInputField(@Nonnull BindingBundle bundle, @Nonnull String id, @Nonnull Binding bind) { + getStyleClass().add("key-field"); + setText(bind.toString()); + setPromptText(Lang.get("bind.inputprompt.initial")); + + // Show prompt when focused, otherwise show current target binding text. + focusedProperty().addListener((v, old, focus) -> setText(focus ? Lang.get("bind.inputprompt.finish") : bind.toString())); + setFocusTraversable(false); + + // Track binding state while typing (will remember last pressed key combo, before pressing enter) + setOnKeyPressed(e -> { + e.consume(); + + // Skip if no-name support for the character + if (e.getCode().getName().equalsIgnoreCase("undefined")) + return; + + // Set target to last key-press combo + if (e.getCode() == ENTER) { + bundle.setIsEditing(false); + + bind.clear(); + bind.addAll(lastState); + lastState = null; + + // Drop focus so the listener will update the display text. + getParent().requestFocus(); + return; + } else if (e.getCode() == ESCAPE) { + // Drop focus so the listener will update the display text. + lastState = null; + getParent().requestFocus(); + return; + } + + // Update key-press combo. + lastState = newBind(id, e); + bundle.setIsEditing(true); + }); + + // Disable text updating + setOnKeyTyped(Event::consume); + } } } diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/config/RecentFilesConfig.java b/recaf-ui/src/main/java/software/coley/recaf/ui/config/RecentFilesConfig.java index 872d3aff9..f6e82b8eb 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/config/RecentFilesConfig.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/config/RecentFilesConfig.java @@ -47,7 +47,7 @@ public RecentFilesConfig() { super(ConfigGroups.SERVICE_IO, ID + CONFIG_SUFFIX); // Add values addValue(new BasicConfigValue<>("max-recent-workspaces", int.class, maxRecentWorkspaces)); - addValue(new BasicCollectionConfigValue<>("recent-workspaces", List.class, WorkspaceModel.class, recentWorkspaces)); + addValue(new BasicCollectionConfigValue<>("recent-workspaces", List.class, WorkspaceModel.class, recentWorkspaces, true)); addValue(new BasicConfigValue<>("last-workspace-open-path", String.class, lastWorkspaceOpenDirectory)); addValue(new BasicConfigValue<>("last-workspace-export-path", String.class, lastWorkspaceExportDirectory)); addValue(new BasicConfigValue<>("last-class-export-path", String.class, lastClassExportDirectory)); diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java index 52a3488c2..4db8afccf 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java @@ -43,7 +43,7 @@ */ @Dependent public class ConfigPane extends SplitPane implements ManagedConfigListener { - private final Map idToPage = new TreeMap<>(); + private final Map idToPage = new TreeMap<>(); private final Map> idToTree = new TreeMap<>(); private final TreeItem root = new TreeItem<>("root"); private final TreeView tree = new TreeView<>(); @@ -96,7 +96,7 @@ protected void updateItem(String item, boolean empty) { }); tree.getSelectionModel().selectedItemProperty().addListener((ob, old, cur) -> { if (cur != null) { - ConfigPage page = idToPage.get(cur.getValue()); + ContainerPane page = idToPage.get(cur.getValue()); if (page != null) content.setContent(page); else content.setContent(new MissingPage(cur.getValue())); } @@ -122,7 +122,7 @@ public void onRegister(@Nonnull ConfigContainer container) { item.getChildren().add(treeItem); // Register page. - idToPage.put(pageKey, new ConfigPage(container)); + idToPage.put(pageKey, new ContainerPane(container)); idToTree.put(pageKey, treeItem); } } @@ -203,9 +203,9 @@ private TreeItem getItem(@Nonnull ConfigContainer container, boolean cre /** * Page for a single {@link ConfigContainer}. */ - private class ConfigPage extends GridPane { + private class ContainerPane extends GridPane { @SuppressWarnings({"rawtypes", "unchecked"}) - private ConfigPage(@Nonnull ConfigContainer container) { + private ContainerPane(@Nonnull ConfigContainer container) { // Plugin configs are given special treatment. // They are not expected to install additional translations, so their ID's will be used as literal names. boolean isThirdPartyConfig = ConfigGroups.EXTERNAL.equals(container.getGroup()); diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index c315a23f1..db94db6e4 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -119,6 +119,15 @@ menu.plugin.enabled=Enabled menu.plugin.uninstall=Uninstall menu.plugin.uninstall.warning=Are you sure you want to delete this plugin? +##### Keybinds +bind.inputprompt.initial= +bind.inputprompt.finish= +bind.editor.find=Find +bind.editor.rename=Rename +bind.editor.replace=Replace +bind.editor.save=Save +bind.quicknav=Quick nav + ##### Dialog texts dialog.cancel=Cancel dialog.close=Close