diff --git a/src/main/java/core/AppSettings.java b/src/main/java/core/AppSettings.java new file mode 100644 index 0000000..0266d6f --- /dev/null +++ b/src/main/java/core/AppSettings.java @@ -0,0 +1,42 @@ +package core; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.Service; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import org.jetbrains.annotations.NotNull; + +// For the production config navigate to the configuration directory: +// https://www.jetbrains.com/help/idea/directories-used-by-the-ide-to-store-settings-caches-plugins-and-logs.html#config-directory +// and then to ./options/togglerPluginSettings.xml. + +// The development config used by `runIde` task is stored in the current working directory: +// ./build/idea-sandbox/config/options/togglerPluginSettings.xml +@Service +@State( + name = "TogglerSettingsState", + storages = {@Storage("togglerPluginSettings.xml")} +) +public final class AppSettings implements PersistentStateComponent { + AppSettings() { + settingsState = new SettingsState(); + } + + private SettingsState settingsState; + + public static AppSettings getInstance() { + return ApplicationManager.getApplication().getService(AppSettings.class); + } + + @Override + @NotNull + public SettingsState getState() { + return settingsState; + } + + @Override + public void loadState(@NotNull SettingsState config) { + settingsState = config; + } +} diff --git a/src/main/java/core/AppSettingsComponent.java b/src/main/java/core/AppSettingsComponent.java index afcf491..ea1b684 100644 --- a/src/main/java/core/AppSettingsComponent.java +++ b/src/main/java/core/AppSettingsComponent.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; import utils.ConfirmResetDialogWrapper; import utils.FileHandler; -import utils.JsonParser; +import utils.ConfigParser; import javax.swing.*; import java.awt.*; @@ -74,20 +74,21 @@ public void promptResetButtonAction() { } public void executeResetButtonAction() { - AppSettingsState appSettingsState = AppSettingsState.getInstance(); - appSettingsState.resetSettingsToDefault(); - setJsonText(JsonParser.toJson(appSettingsState.toggles)); - setPartialMatchingCheckboxStatus(appSettingsState.isPartialMatchingIsEnabled()); + SettingsState settingsState = AppSettings.getInstance().getState(); + settingsState.resetSettingsToDefault(); + setJsonText(settingsState.toggles.toString()); + setPartialMatchingCheckboxStatus(settingsState.isPartialMatchingEnabled()); } private void importTogglesFromJsonFile() { - AppSettingsState appSettingsState = AppSettingsState.getInstance(); + SettingsState settingsState = AppSettings.getInstance().getState(); // Load and parse the contents of the JSON file and set the toggles to // the loaded toggles. try { - appSettingsState.toggles = JsonParser.parseJsonToToggles(FileHandler.loadContentFromFileToString()); - } catch (JsonParser.TogglesFormatException e) { + String fileContent = FileHandler.loadContentFromFileToString(); + settingsState.toggles.overwriteToggles(fileContent); + } catch (ConfigParser.TogglesFormatException e) { setStatusErrorMessage(e.getMessage()); return; } catch (FileHandler.FileSelectionCancelledException e) { @@ -100,14 +101,15 @@ private void importTogglesFromJsonFile() { // Reset the settings menu JsonText textarea to the toggles that have // been loaded. - setJsonText(JsonParser.toJson(appSettingsState.toggles)); + setJsonText(settingsState.toggles.toString()); setStatusMessage("Importing toggles from file was successful."); } private void exportTogglesToJsonFile() { + SettingsState settingsState = AppSettings.getInstance().getState(); // Save the toggles to a file in JSON format. try { - FileHandler.saveTextToDisk(JsonParser.toJson(AppSettingsState.getInstance().toggles)); + FileHandler.saveTextToDisk(settingsState.toggles.toString()); } catch (FileHandler.FileSelectionCancelledException e) { setStatusErrorMessage("No file was saved, exporting toggles failed."); return; diff --git a/src/main/java/core/AppSettingsConfigurable.java b/src/main/java/core/AppSettingsConfigurable.java index e83e0b4..b88f055 100644 --- a/src/main/java/core/AppSettingsConfigurable.java +++ b/src/main/java/core/AppSettingsConfigurable.java @@ -3,10 +3,9 @@ import com.intellij.openapi.options.Configurable; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; -import utils.JsonParser; +import utils.ConfigParser; import javax.swing.*; -import java.util.List; // Docs: // When the settings are changed to something else than the defaultToggles, then @@ -37,31 +36,29 @@ public JComponent createComponent() { @Override public boolean isModified() { - AppSettingsState settings = AppSettingsState.getInstance(); - boolean modified = !mySettingsComponent.getJsonText().equals(JsonParser.toJson(settings.toggles)); - modified |= mySettingsComponent.getPartialMatchingCheckboxStatus() != settings.isPartialMatchingIsEnabled(); + SettingsState settings = AppSettings.getInstance().getState(); + boolean modified = !mySettingsComponent.getJsonText().equals(settings.toggles.toString()); + modified |= mySettingsComponent.getPartialMatchingCheckboxStatus() != settings.isPartialMatchingEnabled(); return modified; } @Override public void apply() { - AppSettingsState settings = AppSettingsState.getInstance(); + SettingsState settings = AppSettings.getInstance().getState(); try { /* Set whether the partial matching functionality is enabled. */ settings.setPartialMatchingIsEnabled(mySettingsComponent.getPartialMatchingCheckboxStatus()); - List> currentSettingsFromMenu = JsonParser.parseJsonToToggles( - mySettingsComponent.getJsonText()); - settings.toggles = currentSettingsFromMenu; + settings.toggles.overwriteToggles(mySettingsComponent.getJsonText()); /* Set the JsonTextarea in the settings menu to the toggles saved to * the plugin. The side effect is that eventual errors entered by * the user that aren't included by the JsonParser are removed from * the textarea input as the input is forcefully reset. */ - mySettingsComponent.setJsonText(JsonParser.toJson(currentSettingsFromMenu)); + mySettingsComponent.setJsonText(settings.toggles.toString()); mySettingsComponent.setStatusMessage("Saving was successful."); - } catch (JsonParser.TogglesFormatException e) { + } catch (ConfigParser.TogglesFormatException e) { mySettingsComponent.setStatusErrorMessage(e.getMessage()); } } @@ -70,9 +67,9 @@ public void apply() { * loaded. */ @Override public void reset() { - AppSettingsState settings = AppSettingsState.getInstance(); - mySettingsComponent.setJsonText(JsonParser.toJson(settings.toggles)); - mySettingsComponent.setPartialMatchingCheckboxStatus(settings.isPartialMatchingIsEnabled()); + SettingsState settings = AppSettings.getInstance().getState(); + mySettingsComponent.setJsonText(settings.toggles.toString()); + mySettingsComponent.setPartialMatchingCheckboxStatus(settings.isPartialMatchingEnabled()); mySettingsComponent.setStatusMessage("Loaded previous settings."); } diff --git a/src/main/java/core/AppSettingsState.java b/src/main/java/core/AppSettingsState.java deleted file mode 100644 index c21df80..0000000 --- a/src/main/java/core/AppSettingsState.java +++ /dev/null @@ -1,63 +0,0 @@ -package core; - -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.util.xmlb.XmlSerializerUtil; -import com.intellij.util.xmlb.annotations.OptionTag; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import utils.JsonParser; -import utils.NotificationHandler; - -import java.util.List; - -@State( - name = "TogglerSettingsState", - storages = {@Storage("togglerPluginSettings.xml")} -) -public class AppSettingsState implements PersistentStateComponent { - private static final boolean DEFAULT_PARTIAL_MATCHING_STATUS = true; - - AppSettingsState() { resetSettingsToDefault(); } - - @OptionTag(converter = TogglerStructureConverter.class) - public List> toggles; - private boolean partialMatchingIsEnabled; - - public void resetSettingsToDefault() { - try { - toggles = JsonParser.parseJsonToToggles(Constants.DEFAULT_TOGGLES); - partialMatchingIsEnabled = DEFAULT_PARTIAL_MATCHING_STATUS; - } catch (JsonParser.TogglesFormatException e) { - NotificationHandler.notify("The defaultToggles provided by the creator of the " + - "plugin don't conform to the JSON format.", - NotificationType.ERROR); - } - } - - public static AppSettingsState getInstance() { - return ApplicationManager.getApplication().getService(AppSettingsState.class); - } - - @Nullable - @Override - public AppSettingsState getState() { - return this; - } - - @Override - public void loadState(@NotNull AppSettingsState state) { - XmlSerializerUtil.copyBean(state, this); - } - - public boolean isPartialMatchingIsEnabled() { - return partialMatchingIsEnabled; - } - - public void setPartialMatchingIsEnabled(boolean partialMatchingIsEnabled) { - this.partialMatchingIsEnabled = partialMatchingIsEnabled; - } -} diff --git a/src/main/java/core/SettingsState.java b/src/main/java/core/SettingsState.java new file mode 100644 index 0000000..7e19186 --- /dev/null +++ b/src/main/java/core/SettingsState.java @@ -0,0 +1,30 @@ +package core; + +import com.intellij.util.xmlb.annotations.OptionTag; + +public class SettingsState { + private static final boolean DEFAULT_PARTIAL_MATCHING_STATUS = true; + + @OptionTag(converter = TogglerStructureConverter.class) + public TogglesConfig toggles; + @OptionTag + private boolean partialMatchingIsEnabled; + + public SettingsState() { + toggles = new TogglesConfig(); + this.resetSettingsToDefault(); + } + + public void resetSettingsToDefault() { + toggles.resetTogglesToDefault(); + partialMatchingIsEnabled = DEFAULT_PARTIAL_MATCHING_STATUS; + } + + public boolean isPartialMatchingEnabled() { + return partialMatchingIsEnabled; + } + + public void setPartialMatchingIsEnabled(boolean partialMatchingIsEnabled) { + this.partialMatchingIsEnabled = partialMatchingIsEnabled; + } +} diff --git a/src/main/java/core/ToggleAction.java b/src/main/java/core/ToggleAction.java index eae8d9e..8a05422 100644 --- a/src/main/java/core/ToggleAction.java +++ b/src/main/java/core/ToggleAction.java @@ -17,14 +17,14 @@ public class ToggleAction extends AnAction { private Editor editor; - private Project project; private Document document; + private SettingsState settingsState; // Default is to toggle to the next word/symbol in the toggle sequence. private boolean toggleForward = true; private boolean partialMatchingIsEnabled = true; - private String regexPatternOfToggles; + private Pattern regexPatternOfToggles; public ToggleAction() { } @@ -53,8 +53,8 @@ public void update(@NotNull final AnActionEvent e) { @Override public void actionPerformed(@NotNull AnActionEvent e) { this.editor = e.getData(CommonDataKeys.EDITOR); - this.project = e.getData(CommonDataKeys.PROJECT); - if (this.editor == null || this.project == null) { + Project project = e.getData(CommonDataKeys.PROJECT); + if (this.editor == null || project == null) { NotificationHandler.notify("Toggle aborted. Internal error: editor and/or project is null. " + "Please open an issue: https://github.com/Noorts/Toggler/issues.", NotificationType.ERROR, this.editor); @@ -63,9 +63,9 @@ public void actionPerformed(@NotNull AnActionEvent e) { this.document = this.editor.getDocument(); final CaretModel caretModel = this.editor.getCaretModel(); - AppSettingsState appSettingsState = AppSettingsState.getInstance(); - this.regexPatternOfToggles = createRegexPatternOfToggles(appSettingsState.toggles); - this.partialMatchingIsEnabled = appSettingsState.isPartialMatchingIsEnabled(); + this.settingsState = AppSettings.getInstance().getState(); + this.regexPatternOfToggles = this.settingsState.toggles.getRegexPatternOfToggles(); + this.partialMatchingIsEnabled = this.settingsState.isPartialMatchingEnabled(); /* Bandage (temporary fix) that might help remove the "ghost" caret that appears on load of the IDE. */ @@ -147,7 +147,7 @@ private void performToggleOnSingleCaret(Caret caret) { // If a match was found then toggle it, else display a notification. if (!positionOfMatch.isEmpty()) { String match = selectedToggleFromCaret.substring(positionOfMatch.get(0), positionOfMatch.get(1)); - String replacementToggle = findReplacementWord(match, this.toggleForward); + String replacementToggle = settingsState.toggles.findReplacementWord(match, this.toggleForward); /* The replacementToggle should never be null in this case, because * if no match was found then the positionOfMatch would be null. @@ -185,73 +185,6 @@ private void performToggleOnSingleCaret(Caret caret) { } } - /** - * Find the next or previous word/symbol for the provided word/symbol in the - * toggles. The provided word/symbol is searched for in the toggles - * configured in the plugin settings and the next or previous one in the - * sequence is returned. Whether the next or previous toggle in the sequence - * is returned depends on the toggleForward parameter. - * - * @param word The word/symbol to be replaced. - * @param toggleForward Determines whether the next or previous toggle in the sequence is returned. - * @return The next/previous word/symbol in the sequence that the provided - * word/symbol is part of. Null is returned if the provided word couldn't be - * found in the config. - */ - private String findReplacementWord(String word, boolean toggleForward) { - AppSettingsState appSettingsState = AppSettingsState.getInstance(); - List> toggleWordsStructure = appSettingsState.toggles; - - String wordInLowerCase = word.toLowerCase(); - - /* O(n) search for the word/symbol to replace. */ - for (int i = 0; i < toggleWordsStructure.size(); i++) { - for (int j = 0; j < toggleWordsStructure.get(i).size(); j++) { - if (toggleWordsStructure.get(i).get(j).toLowerCase().equals(wordInLowerCase)) { - /* The next word/symbol in the sequence is retrieved. The - modulo is used to wrap around if the end of the sequence - is reached. */ - int sequenceSize = toggleWordsStructure.get(i).size(); - return toggleWordsStructure.get(i).get( - toggleForward - ? (j + 1) % sequenceSize // Next - : (j - 1 + sequenceSize) % sequenceSize // Previous - ); - } - } - } - - /* The word/symbol could not be found. */ - return null; - } - - /** - * Takes the provided toggles and creates a regex pattern out of it that - * matches any of the toggles. - *

- * The individual toggles have been escaped by wrapping them in \\Q and \\E. - * This allows characters such as * that would normally be recognised as - * regex operators to be included in the toggles. - *

- * The following is an example of the output of the method: - * "(\\Qremove\\E|\\Qadd\\E)" - * - * @param toggleWordsStructure The data structure that holds the toggles. - * @return The regex pattern packaged inside a String. - */ - public String createRegexPatternOfToggles(List> toggleWordsStructure) { - List names = toggleWordsStructure.stream().flatMap(Collection::stream) - .sorted(Comparator.comparingInt(String::length).reversed()).toList(); - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("(\\Q").append(names.get(0)).append("\\E"); - for (int i = 1; i < names.size(); i++) { - stringBuilder.append("|\\Q").append(names.get(i)).append("\\E"); - } - stringBuilder.append(")"); - return stringBuilder.toString(); - } - /** * Provided an input, search for toggles inside that input and if found and * the caret touches it, return the match's position using its beginning and @@ -270,8 +203,8 @@ public String createRegexPatternOfToggles(List> toggleWordsStructur * @return A pair of integers that indicate the beginning and end of the * match relative to the input string. */ - public List getPositionOfToggleMatch(String regexPatternOfToggles, String input, boolean allowPartialMatch, int caretPosition) { - Matcher matcher = Pattern.compile(regexPatternOfToggles, Pattern.CASE_INSENSITIVE).matcher(input); + public List getPositionOfToggleMatch(Pattern regexPatternOfToggles, String input, boolean allowPartialMatch, int caretPosition) { + Matcher matcher = regexPatternOfToggles.matcher(input); // Sort the matches by string length, so that longer matches get // priority over smaller ones. diff --git a/src/main/java/core/TogglerStructureConverter.java b/src/main/java/core/TogglerStructureConverter.java index 41f874e..783309f 100644 --- a/src/main/java/core/TogglerStructureConverter.java +++ b/src/main/java/core/TogglerStructureConverter.java @@ -2,31 +2,28 @@ import com.intellij.notification.NotificationType; import com.intellij.util.xmlb.Converter; -import java.util.Collections; import org.jetbrains.annotations.NotNull; -import utils.JsonParser; +import utils.ConfigParser; import utils.NotificationHandler; -import java.util.List; - /** * A converter used by the AppSettingsState to write and load the state of the * toggles to and from the internal settings file of this plugin managed by the * IDE. */ -class TogglerStructureConverter extends Converter>> { - public List> fromString(@NotNull String value) { +class TogglerStructureConverter extends Converter { + public TogglesConfig fromString(@NotNull String togglesString) { try { - return JsonParser.parseJsonToToggles(value); - } catch (JsonParser.TogglesFormatException e) { + return new TogglesConfig(togglesString); + } catch (ConfigParser.TogglesFormatException e) { NotificationHandler.notify("The toggles couldn't be parsed from the " + - "plugin setting storage successfully.", - NotificationType.ERROR); - return Collections.emptyList(); + "plugin settings storage successfully.", + NotificationType.ERROR); + return null; } } - public String toString(@NotNull List> value) { - return JsonParser.toJson(value); + public String toString(@NotNull TogglesConfig togglesConfig) { + return togglesConfig.toString(); } } diff --git a/src/main/java/core/TogglesConfig.java b/src/main/java/core/TogglesConfig.java new file mode 100644 index 0000000..ee92816 --- /dev/null +++ b/src/main/java/core/TogglesConfig.java @@ -0,0 +1,122 @@ +package core; + +import com.intellij.notification.NotificationType; +import org.jetbrains.annotations.NotNull; +import utils.ConfigParser; +import utils.NotificationHandler; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class TogglesConfig { + private List> toggles; + + private Pattern regexPatternOfTogglesCache = null; + + /** + * Instantiated with default toggles. + */ + public TogglesConfig() { + resetTogglesToDefault(); + } + + /** + * Parses and loads provided toggles. + */ + public TogglesConfig(@NotNull String togglesString) throws ConfigParser.TogglesFormatException { + this.overwriteToggles(togglesString); + } + + @Override + public String toString() { + return ConfigParser.toJson(this.toggles); + } + + public void overwriteToggles(@NotNull String togglesString) throws ConfigParser.TogglesFormatException { + this.toggles = ConfigParser.parseJsonToToggles(togglesString); + invalidateCache(); + } + + public void resetTogglesToDefault() { + try { + toggles = ConfigParser.parseJsonToToggles(Constants.DEFAULT_TOGGLES); + } catch (ConfigParser.TogglesFormatException e) { + NotificationHandler.notify("The defaultToggles provided by the creator of the " + + "plugin don't conform to the JSON format.", + NotificationType.ERROR); + } + } + + private void invalidateCache() { + this.regexPatternOfTogglesCache = null; + } + + /** + * Find the next or previous word/symbol for the provided word/symbol in the + * toggles. The provided word/symbol is searched for in the toggles + * configured in the plugin settings and the next or previous one in the + * sequence is returned. Whether the next or previous toggle in the sequence + * is returned depends on the toggleForward parameter. + * + * @param word The word/symbol to be replaced. + * @param toggleForward Determines whether the next or previous toggle in the sequence is returned. + * @return The next/previous word/symbol in the sequence that the provided + * word/symbol is part of. Null is returned if the provided word couldn't be + * found in the config. + */ + public String findReplacementWord(String word, boolean toggleForward) { + String wordInLowerCase = word.toLowerCase(); + + /* O(n) search for the word/symbol to replace. */ + for (int i = 0; i < toggles.size(); i++) { + for (int j = 0; j < toggles.get(i).size(); j++) { + if (toggles.get(i).get(j).toLowerCase().equals(wordInLowerCase)) { + /* The next word/symbol in the sequence is retrieved. The + modulo is used to wrap around if the end of the sequence + is reached. */ + int sequenceSize = toggles.get(i).size(); + return toggles.get(i).get( + toggleForward + ? (j + 1) % sequenceSize // Next + : (j - 1 + sequenceSize) % sequenceSize // Previous + ); + } + } + } + + /* The word/symbol could not be found. */ + return null; + } + + /** + * Returns a regex pattern that matches any of the configured toggles. + * The regex is case-insensitive. + *

+ * The individual toggles have been escaped by wrapping them in \\Q and \\E. + * This allows characters such as * to be included in the toggles. + * These would normally be recognised as regex operators. + *

+ * An example of what the pattern might represent: + * "(\\Qremove\\E|\\Qadd\\E)" + */ + public Pattern getRegexPatternOfToggles() { + if (regexPatternOfTogglesCache == null) { + this.regexPatternOfTogglesCache = buildRegexPatternOfToggles(); + } + return regexPatternOfTogglesCache; + } + + private Pattern buildRegexPatternOfToggles() { + String regexStringOfToggles = this.toggles.stream() + .flatMap(Collection::stream) + // sort to prioritize large matches over smaller matches. + .sorted(Comparator.comparingInt(String::length).reversed()) + .map(s -> "\\Q" + s + "\\E") + .collect(Collectors.joining("|", "(", ")")); + + return Pattern.compile(regexStringOfToggles, Pattern.CASE_INSENSITIVE); + } +} diff --git a/src/main/java/utils/JsonParser.java b/src/main/java/utils/ConfigParser.java similarity index 96% rename from src/main/java/utils/JsonParser.java rename to src/main/java/utils/ConfigParser.java index 590227c..8d92133 100644 --- a/src/main/java/utils/JsonParser.java +++ b/src/main/java/utils/ConfigParser.java @@ -8,8 +8,8 @@ * A utility written to handle parsing and composing between a String and the * plugin's toggles data structure. */ -public class JsonParser { - private JsonParser() { throw new IllegalStateException("Utility class"); } +public class ConfigParser { + private ConfigParser() { throw new IllegalStateException("Utility class"); } /** * Check the provided text for errors that indicate that the text doesn't @@ -139,8 +139,8 @@ private static void checkIfWordContainsABoundaryCharacter(String word) throws To /** * A custom exception thrown when toggles parsed from a String are * malformed. - * @see JsonParser#checkJsonForErrors - * @see JsonParser#parseJsonToToggles + * @see ConfigParser#checkJsonForErrors + * @see ConfigParser#parseJsonToToggles */ public static class TogglesFormatException extends Exception { public TogglesFormatException(String errorMessage) { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 698f133..e9d91fe 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -6,7 +6,7 @@ com.intellij.modules.platform - + diff --git a/src/test/java/core/ToggleActionIntegrationTest.java b/src/test/java/core/ToggleActionIntegrationTest.java index 8d971f2..787ed83 100644 --- a/src/test/java/core/ToggleActionIntegrationTest.java +++ b/src/test/java/core/ToggleActionIntegrationTest.java @@ -100,4 +100,10 @@ public void testToggleActionTransfersCase() { myFixture.performEditorAction(TOGGLE_ACTION); // |Get myFixture.checkResult("Get"); } + + public void testToggleActionSupportsEscapedRegexCharactersInToggles() { + myFixture.configureByText(TEST_FILE_NAME, "*="); // |*= + myFixture.performEditorAction(TOGGLE_ACTION); // |/= + myFixture.checkResult("/="); + } } diff --git a/src/test/java/core/ToggleActionTest.java b/src/test/java/core/ToggleActionTest.java index 2a81eaa..f06e4b1 100644 --- a/src/test/java/core/ToggleActionTest.java +++ b/src/test/java/core/ToggleActionTest.java @@ -1,39 +1,37 @@ package core; import org.junit.Test; +import utils.ConfigParser; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.*; public class ToggleActionTest { @Test - public void regexPatternIsCorrectlyCreatedFromTheTogglePairs() { + public void regexPatternIsCorrectlyCreatedFromTheTogglePairs() throws ConfigParser.TogglesFormatException { // Arrange - List smallTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List longTogglePair = new ArrayList<>(Arrays.asList("addClass", "removeClass")); - List> toggleActionStructure = new ArrayList<>(Arrays.asList(smallTogglePair, longTogglePair)); + String toggles = "[[\"add\", \"remove\"], [\"addClass\", \"removeClass\"]]"; // Act - ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); // Assert String correctRegexPattern = "(\\QremoveClass\\E|\\QaddClass\\E|\\Qremove\\E|\\Qadd\\E)"; - assertEquals(correctRegexPattern, regexPattern, "The regex pattern that was created is incorrect."); + assertEquals(correctRegexPattern, regexPattern.pattern(), "The regex pattern that was created is incorrect."); } @Test - public void fullMatchIsFoundCorrectlyWithPartialMatchingEnabled() { + public void fullMatchIsFoundCorrectlyWithPartialMatchingEnabled() throws ConfigParser.TogglesFormatException { // Arrange - List smallTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List longTogglePair = new ArrayList<>(Arrays.asList("addClass", "removeClass")); - List> toggleActionStructure = new ArrayList<>(Arrays.asList(smallTogglePair, longTogglePair)); + String toggles = "[[\"add\", \"remove\"], [\"addClass\", \"removeClass\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "addClass"; @@ -48,12 +46,12 @@ public void fullMatchIsFoundCorrectlyWithPartialMatchingEnabled() { } @Test - public void partialMatchIsFoundCorrectlyWithPartialMatchingEnabled() { + public void partialMatchIsFoundCorrectlyWithPartialMatchingEnabled() throws ConfigParser.TogglesFormatException { // Arrange - List smallTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List> toggleActionStructure = new ArrayList<>(Collections.singletonList(smallTogglePair)); + String toggles = "[[\"add\", \"remove\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "addClass"; @@ -68,13 +66,12 @@ public void partialMatchIsFoundCorrectlyWithPartialMatchingEnabled() { } @Test - public void fullMatchIsFoundCorrectlyWithPartialMatchingDisabled() { + public void fullMatchIsFoundCorrectlyWithPartialMatchingDisabled() throws ConfigParser.TogglesFormatException { // Arrange - List smallTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List longTogglePair = new ArrayList<>(Arrays.asList("addClass", "removeClass")); - List> toggleActionStructure = new ArrayList<>(Arrays.asList(smallTogglePair, longTogglePair)); + String toggles = "[[\"add\", \"remove\"], [\"addClass\", \"removeClass\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "addClass"; @@ -89,12 +86,12 @@ public void fullMatchIsFoundCorrectlyWithPartialMatchingDisabled() { } @Test - public void partialMatchIsNotFoundCorrectlyWithPartialMatchingDisabled() { + public void partialMatchIsNotFoundCorrectlyWithPartialMatchingDisabled() throws ConfigParser.TogglesFormatException { // Arrange - List smallTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List> toggleActionStructure = new ArrayList<>(Collections.singletonList(smallTogglePair)); + String toggles = "[[\"add\", \"remove\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "addClass"; @@ -108,13 +105,12 @@ public void partialMatchIsNotFoundCorrectlyWithPartialMatchingDisabled() { } @Test - public void partialMatchUnderCaretIsFoundCorrectly() { + public void partialMatchUnderCaretIsFoundCorrectly() throws ConfigParser.TogglesFormatException { // Arrange - List firstTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List secondTogglePair = new ArrayList<>(Arrays.asList("class", "interface")); - List> toggleActionStructure = new ArrayList<>(Arrays.asList(firstTogglePair, secondTogglePair)); + String toggles = "[[\"add\", \"remove\"], [\"class\", \"interface\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "addClass"; @@ -136,12 +132,12 @@ public void partialMatchUnderCaretIsFoundCorrectly() { } @Test - public void partialMatchIsNotFoundCorrectlyWhenCaretPositionIsOutsideOfIt() { + public void partialMatchIsNotFoundCorrectlyWhenCaretPositionIsOutsideOfIt() throws ConfigParser.TogglesFormatException { // Arrange - List firstTogglePair = new ArrayList<>(Arrays.asList("add", "remove")); - List> toggleActionStructure = new ArrayList<>(Collections.singletonList(firstTogglePair)); + String toggles = "[[\"add\", \"remove\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "addClass"; @@ -161,13 +157,12 @@ public void partialMatchIsNotFoundCorrectlyWhenCaretPositionIsOutsideOfIt() { } @Test - public void ifThereArePartialMatchesOnEitherSideOfTheCaretThenTheOneOnTheRightIsCorrectlyReturned() { + public void ifThereArePartialMatchesOnEitherSideOfTheCaretThenTheOneOnTheRightIsCorrectlyReturned() throws ConfigParser.TogglesFormatException { // Arrange - List smallTogglePair = new ArrayList<>(Arrays.asList("lov", "add")); - List longTogglePair = new ArrayList<>(Arrays.asList("ely", "remove")); - List> toggleActionStructure = new ArrayList<>(Arrays.asList(smallTogglePair, longTogglePair)); + String toggles = "[[\"lov\", \"add\"], [\"ely\", \"remove\"]]"; ToggleAction newToggleAction = new ToggleAction(); - String regexPattern = newToggleAction.createRegexPatternOfToggles(toggleActionStructure); + TogglesConfig togglesConfig = new TogglesConfig(toggles); + Pattern regexPattern = togglesConfig.getRegexPatternOfToggles(); String input = "Lovely"; @@ -182,4 +177,25 @@ public void ifThereArePartialMatchesOnEitherSideOfTheCaretThenTheOneOnTheRightIs "on the left instead of the one on the right."); } + // Fix the current format used to store the toggles persistently. This is here to catch breaking changes. + @Test + public void togglesConfigurationPersistentStorageFormatHasNotDeviatedFromOriginal() throws ConfigParser.TogglesFormatException { + // Arrange + String toggles = "[[\"add\", \"remove\"], [\"addClass\", \"removeClass\"]]"; + TogglesConfig togglesConfig = new TogglesConfig(toggles); + TogglerStructureConverter converter = new TogglerStructureConverter(); + + // Act + String persistentStoredToggles = converter.toString(togglesConfig); + + // Assert + String expectedTogglesFormat = """ + [ + \t["add", "remove"], + \t["addClass", "removeClass"] + ]"""; + assertEquals(expectedTogglesFormat, persistentStoredToggles, + "The toggles config format deviates from the expected formatting."); + } + }