Noorts / Toggler

JetBrains IDE Plugin - Quickly toggle words and symbols with a hotkey

Home Page:https://plugins.jetbrains.com/plugin/16166-toggler

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Refactoring and Performance Improvements

Noorts opened this issue · comments

Introduction

The plugin in its current form using the default toggles is already fast enough not to cause noticable lag. There are however parts of Toggler's implementation that can be optimised. This issue has been set up to investigate potential performance improvements.

Furthermore, initially the focus was on getting the plugin working as intended. This has led to suboptimal design choices and structuring of the application. There are likely several refactoring opportunities (e.g., to avoid recreating the "RegexPatternOfToggles" everytime the toggle action is called (could instead be cached), or to use a built-in JSON parser (with list only constraints) instead of the current homebrew version).

Toggles structure

The structure used to store the toggles is a List<List<String>> (thus O(n) in terms of space complexity). Utilities have been written to parse and stringify this toggles structure into JSON. The JSON representation maps onto this structure quite well (as can be seen from the default toggles).

We should be aware that the toggles structure persists the users their personal configs. Thus changing this might be risky as users could lose their personal configs if an update changes this structure.

Current steps

The business logic found in ToggleAction.java uses the following steps to perform a toggle for each caret separately.

  1. The caret temporarily expands its selection until boundary characters are found on both sides. E.g. const s|etName = 0 (with the | indicating the caret and selection) would turn into const |setName| = 0.
  2. A regex pattern (that contains all the individual toggles) is built when the action is triggered allowing all carets to use the pattern to detect whether and how many toggle matches were found inside of the expanded caret selection. E.g. const |setName| = 0 would return one match if partial matching is enabled, namely set.
  3. The location inside of the document where this match is found is returned to be used for replacement further down the line. E.g. this location could indicate that the match is located on line 3, from character 7 until 10.
  4. The match is then used to find the next toggle. In the current implementation this means that we loop through the toggles structure until we find our match e.g.set. We then return the next word in the structure (wrapping around if the match is the last in the sublist). Finding the match its replacement using this implementation is thus an O(n) operation.
  5. An API call is made to replace the match with the replacement found.
  6. The caret selection (which was expanded in step 1) is restored to its original state.

Potential Improvements

The following is a list of potential improvements that can be made.

  • Extract the regex pattern to the configuration logic
    • Step 2. This would make sure its only set up once and not every time the toggle action is triggered. We'd have to rebuilt the pattern upon the initialisation of the plugin and upon modification of the toggles configuration through the configuration menu.
  • Replace the find next toggle O(n) logic with a hashmap implementation
    • Step 4. This would improve the time complexity of the action from O(n) to O(1). What's still left is to figure out how to store the hashmap in relation to the current toggles structure discussed above. See the following two approaches:
      • Replacing the original toggles structure might cause breaking changes, which means users might have to migrate their configs. We might be able to write some custom code for that, but this is best avoided. This approach also means that other parts of the application will have to be updated, such as the JsonParser.
      • Having the hashmap be derived from the toggles structure will mean that changing the toggles structure will have to trigger the hashmap to also be rebuilt. The hashmap in this approach would also take up additional memory.