Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Settings #99

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0cc48db
refactor: extract settings state to `SettingsState` class
Noorts Feb 9, 2025
3f99f62
refactor: rename `isPartialMatchingEnabled`
Noorts Feb 9, 2025
cac2df0
fix: initialize SettingsState
Noorts Feb 9, 2025
0e5fa1c
refactor: rename `AppSettingsState` to `AppSettings`
Noorts Feb 9, 2025
b59928f
docs: document location of the persistent plugin configuration file
Noorts Feb 9, 2025
a8bf2b4
fix: re-enable persistence of `partialMatchingIsEnabled`
Noorts Feb 9, 2025
484028b
refactor: extract toggles to `TogglesConfig` class
Noorts Feb 9, 2025
fb1043d
refactor: make `Project` local variable
Noorts Feb 9, 2025
9e68c84
refactor: move `findReplacementWord` to `TogglesConfig`
Noorts Feb 9, 2025
3f825c5
refactor: move `createRegexPatternOfToggles` to `TogglesConfig`
Noorts Feb 9, 2025
6f72ff4
refactor: clean up `TogglesConfig` interface
Noorts Feb 10, 2025
b0f9ebf
refactor: rename `JsonParser` to `ConfigParser`
Noorts Feb 10, 2025
4445bf0
refactor: rename `createRegexPatternOfToggles` to `getRegexPatternOfT…
Noorts Feb 11, 2025
e2015ed
chore: clean up `AppSettingsConfigurable` import
Noorts Feb 15, 2025
e083e8d
refactor: add `regexPatternOfTogglesCache` and improve docs
Noorts Feb 15, 2025
8f8d4aa
refactor: simplify `generateRegexPatternOfToggles`
Noorts Feb 15, 2025
fe72314
test: ensure regex characters in toggles are supported
Noorts Feb 15, 2025
54521e1
fix: prioritize longer length matches
Noorts Feb 15, 2025
7951c9f
test: prevent persistent storage format regression
Noorts Feb 15, 2025
eb7df7f
refactor: make `getRegexPatternOfToggles` return `Pattern` instead of…
Noorts Feb 15, 2025
be32a2f
refactor: rename `getRegexPatternOfToggles` to `buildRegexPatternOfTo…
Noorts Feb 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/main/java/core/AppSettings.java
Original file line number Diff line number Diff line change
@@ -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<SettingsState> {
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;
}
}
22 changes: 12 additions & 10 deletions src/main/java/core/AppSettingsComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
25 changes: 11 additions & 14 deletions src/main/java/core/AppSettingsConfigurable.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<List<String>> 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());
}
}
Expand All @@ -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.");
}

Expand Down
63 changes: 0 additions & 63 deletions src/main/java/core/AppSettingsState.java

This file was deleted.

30 changes: 30 additions & 0 deletions src/main/java/core/SettingsState.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
87 changes: 10 additions & 77 deletions src/main/java/core/ToggleAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}
Expand Down Expand Up @@ -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);
Expand All @@ -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. */
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<List<String>> 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.
* <p>
* 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.
* <p>
* 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<List<String>> toggleWordsStructure) {
List<String> 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
Expand All @@ -270,8 +203,8 @@ public String createRegexPatternOfToggles(List<List<String>> toggleWordsStructur
* @return A pair of integers that indicate the beginning and end of the
* match relative to the input string.
*/
public List<Integer> getPositionOfToggleMatch(String regexPatternOfToggles, String input, boolean allowPartialMatch, int caretPosition) {
Matcher matcher = Pattern.compile(regexPatternOfToggles, Pattern.CASE_INSENSITIVE).matcher(input);
public List<Integer> 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.
Expand Down
Loading