From b7d14c266c9bfa02cdbaa5cc60c82f85cc0a9765 Mon Sep 17 00:00:00 2001 From: MituuZ <55958056+MituuZ@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:32:55 +0300 Subject: [PATCH] Match highlighting (#66) * Initial working version * Use the actual length * Get color from global scheme * Use JB yellow * Make the test dynamic * Some cleanup * Sort the indexes before applying the highlight * Add license * Line up * Rename hex function * Use indexes instead of a for loop * Just handle each letter one by one * Add settings to control highlight (only for styled) and fix tests * Use lazy loading to compute the start tag string * Make colorAsHex private * Create a config class to store the style tags instead of a lazy var * Remove extra print * Add change notes and increment version * Ensure that highlight option is properly set at start --- build.gradle.kts | 6 +- changelog.md | 4 + .../kotlin/com/mituuz/fuzzier/FuzzyAction.kt | 6 +- .../components/FuzzierSettingsComponent.kt | 15 +++- .../fuzzier/entities/FuzzyMatchContainer.kt | 34 +++++++- .../fuzzier/entities/ScoreCalculator.kt | 1 + .../fuzzier/settings/FuzzierConfiguration.kt | 46 +++++++++++ .../settings/FuzzierSettingsConfigurable.kt | 12 ++- .../settings/FuzzierSettingsService.kt | 1 + src/main/resources/META-INF/plugin.xml | 7 +- .../com/mituuz/fuzzier/FuzzyActionTest.kt | 17 +++- .../entities/FuzzyMatchContainerTest.kt | 78 +++++++++++++++++++ .../FuzzierSettingsConfigurableTest.kt | 8 +- 13 files changed, 217 insertions(+), 18 deletions(-) create mode 100644 src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierConfiguration.kt create mode 100644 src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c2e0a6c6..aa7fea05 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,12 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("org.jetbrains.kotlin.jvm") version "2.0.0" id("org.jetbrains.intellij") version "1.17.4" } group = "com.mituuz" -version = "0.24.0" +version = "0.25.0" repositories { mavenCentral() @@ -30,7 +32,7 @@ intellij { tasks { withType { - kotlinOptions.jvmTarget = "17" + compilerOptions.jvmTarget.set(JvmTarget.JVM_17) } patchPluginXml { diff --git a/changelog.md b/changelog.md index c81e4932..bc28b130 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,8 @@ # Changelog +## Version 0.25.0 +- Clear up settings grouping +- Add option to highlight filename matches in the file list + ## Version 0.24.0 - Add an option to change the font size for the preview window - Some dependency updates diff --git a/src/main/kotlin/com/mituuz/fuzzier/FuzzyAction.kt b/src/main/kotlin/com/mituuz/fuzzier/FuzzyAction.kt index 6c7f6753..c40247db 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/FuzzyAction.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/FuzzyAction.kt @@ -183,7 +183,7 @@ abstract class FuzzyAction : AnAction() { } else { fuzzierSettingsService.state.filenameType } - renderer.text = container.toString(filenameType) + renderer.text = container.toString(filenameType, fuzzierSettingsService.state.highlightFilename) fuzzierSettingsService.state.fileListSpacing.let { renderer.border = BorderFactory.createEmptyBorder(it, 0, it, 0) } @@ -199,4 +199,8 @@ abstract class FuzzyAction : AnAction() { fun setFiletype(filenameType: FilenameType) { fuzzierSettingsService.state.filenameType = filenameType } + + fun setHighlight(highlight: Boolean) { + fuzzierSettingsService.state.highlightFilename = highlight + } } \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierSettingsComponent.kt b/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierSettingsComponent.kt index 4f3a44b7..8138fe71 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierSettingsComponent.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/components/FuzzierSettingsComponent.kt @@ -100,6 +100,14 @@ class FuzzierSettingsComponent { """.trimIndent(), false) + val highlightFilename = SettingsComponent(JBCheckBox(), "Highlight filename in file list*", + """ + Toggles highlighting of the filename on the file list. +
+ Only works with styled file list, which supports html styling. + """.trimIndent(), + false) + val fileListLimit = SettingsComponent(JBIntSpinner(50, 1, 5000), "File list limit", """ Controls how many files are shown and listed on the popup. @@ -194,11 +202,12 @@ class FuzzierSettingsComponent { .addComponent(recentFileModeSelector) .addComponent(prioritizeShortDirs) .addComponent(debounceTimerValue) - .addComponent(filenameTypeSelector) .addComponent(fileListLimit) .addSeparator() .addComponent(JBLabel("Popup styling")) + .addComponent(filenameTypeSelector) + .addComponent(highlightFilename) .addComponent(fileListFontSize) .addComponent(previewFontSize) .addComponent(fileListSpacing) @@ -256,6 +265,10 @@ class FuzzierSettingsComponent { for (filenameType in FilenameType.entries) { filenameTypeSelector.getFilenameTypeComboBox().addItem(filenameType) } + filenameTypeSelector.getFilenameTypeComboBox().addItemListener { + highlightFilename.getCheckBox().isEnabled = it.item == FilenameType.FILENAME_WITH_PATH_STYLED + } + highlightFilename.getCheckBox().isEnabled = filenameTypeSelector.getFilenameTypeComboBox().item == FilenameType.FILENAME_WITH_PATH_STYLED startTestBench.getButton().addActionListener { startTestBench.getButton().isEnabled = false diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt index 35f4a426..e52f6c7e 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt @@ -24,10 +24,13 @@ SOFTWARE. package com.mituuz.fuzzier.entities import com.intellij.openapi.components.service +import com.mituuz.fuzzier.settings.FuzzierConfiguration.END_STYLE_TAG +import com.mituuz.fuzzier.settings.FuzzierConfiguration.startStyleTag import com.mituuz.fuzzier.settings.FuzzierSettingsService class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filename: String, private var module: String = "") { private var initialPath: String? = null + companion object { fun createOrderedContainer(order: Int, filePath: String, initialPath:String, filename: String): FuzzyMatchContainer { val fuzzyScore = FuzzyScore() @@ -38,17 +41,39 @@ class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filen } } - fun toString(filenameType: FilenameType): String { + fun toString(filenameType: FilenameType, highlight: Boolean): String { return when (filenameType) { FilenameType.FILENAME_ONLY -> filename FilenameType.FILE_PATH_ONLY -> filePath FilenameType.FILENAME_WITH_PATH -> "$filename ($filePath)" - FilenameType.FILENAME_WITH_PATH_STYLED -> getFilenameWithPathStyled() + FilenameType.FILENAME_WITH_PATH_STYLED -> getFilenameWithPathStyled(highlight) + } + } + + private fun getStyledFilename(highlight: Boolean): String { + if (highlight) { + return highlight(filename) + } + return filename + } + + fun highlight(source: String): String { + val stringBuilder: StringBuilder = StringBuilder(source) + var offset = 0 + val hlIndexes = score.highlightCharacters.sorted() + for (i in hlIndexes) { + if (i < source.length) { + stringBuilder.insert(i + offset, startStyleTag) + offset += startStyleTag.length + stringBuilder.insert(i + offset + 1, END_STYLE_TAG) + offset += END_STYLE_TAG.length + } } + return stringBuilder.toString() } - private fun getFilenameWithPathStyled(): String { - return "$filename ($filePath)" + private fun getFilenameWithPathStyled(highlight: Boolean): String { + return "${getStyledFilename(highlight)} ($filePath)" } fun getFileUri(): String { @@ -85,6 +110,7 @@ class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filen var multiMatchScore = 0 var partialPathScore = 0 var filenameScore = 0 + val highlightCharacters: MutableSet = HashSet() fun getTotalScore(): Int { return streakScore + multiMatchScore + partialPathScore + filenameScore diff --git a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt index 9d9dd715..18049376 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt @@ -164,6 +164,7 @@ class ScoreCalculator(searchString: String) { private fun processFilenameChar(searchStringPartChar: Char) { val filePathPartChar = currentFilePath[filePathIndex] if (searchStringPartChar == filePathPartChar) { + fuzzyScore.highlightCharacters.add(filePathIndex - filenameIndex) searchStringIndex++ currentFilenameStreak++ if (currentFilenameStreak > longestFilenameStreak) { diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierConfiguration.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierConfiguration.kt new file mode 100644 index 00000000..ded7ab79 --- /dev/null +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierConfiguration.kt @@ -0,0 +1,46 @@ +/* +MIT License + +Copyright (c) 2024 Mitja Leino + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.mituuz.fuzzier.settings + +import com.intellij.ui.JBColor +import java.awt.Color + +object FuzzierConfiguration { + var startStyleTag = createStartStyleTag() + const val END_STYLE_TAG: String = "" + + private fun createStartStyleTag(): String { + val color = JBColor.YELLOW + return "" + } + + @Suppress("unused") // TODO: Update the base color for highlights + private fun updateStartStyleTag(colorAsHex: String) { + this.startStyleTag = "" + } + + private fun colorAsHex(color: Color): String { + return String.format("#%02x%02x%02x", color.red, color.green, color.blue) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurable.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurable.kt index 8f806ae2..eab5fac7 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurable.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurable.kt @@ -46,8 +46,10 @@ class FuzzierSettingsConfigurable : Configurable { component.recentFileModeSelector.getRecentFilesTypeComboBox().selectedIndex = state.recentFilesMode.ordinal component.prioritizeShortDirs.getCheckBox().isSelected = state.prioritizeShorterDirPaths component.debounceTimerValue.getIntSpinner().value = state.debouncePeriod - component.filenameTypeSelector.getFilenameTypeComboBox().selectedIndex = state.filenameType.ordinal component.fileListLimit.getIntSpinner().value = state.fileListLimit + + component.filenameTypeSelector.getFilenameTypeComboBox().selectedIndex = state.filenameType.ordinal + component.highlightFilename.getCheckBox().isSelected = state.highlightFilename component.fileListFontSize.getIntSpinner().value = state.fileListFontSize component.previewFontSize.getIntSpinner().value = state.previewFontSize component.fileListSpacing.getIntSpinner().value = state.fileListSpacing @@ -73,8 +75,10 @@ class FuzzierSettingsConfigurable : Configurable { || state.recentFilesMode != component.recentFileModeSelector.getRecentFilesTypeComboBox().selectedItem || state.prioritizeShorterDirPaths != component.prioritizeShortDirs.getCheckBox().isSelected || state.debouncePeriod != component.debounceTimerValue.getIntSpinner().value - || state.filenameType != component.filenameTypeSelector.getFilenameTypeComboBox().selectedItem || state.fileListLimit != component.fileListLimit.getIntSpinner().value + + || state.filenameType != component.filenameTypeSelector.getFilenameTypeComboBox().selectedItem + || state.highlightFilename != component.highlightFilename.getCheckBox().isSelected || state.fileListFontSize != component.fileListFontSize.getIntSpinner().value || state.previewFontSize != component.previewFontSize.getIntSpinner().value || state.fileListSpacing != component.fileListSpacing.getIntSpinner().value @@ -97,8 +101,10 @@ class FuzzierSettingsConfigurable : Configurable { state.recentFilesMode = RecentFilesMode.entries.toTypedArray()[component.recentFileModeSelector.getRecentFilesTypeComboBox().selectedIndex] state.prioritizeShorterDirPaths = component.prioritizeShortDirs.getCheckBox().isSelected state.debouncePeriod = component.debounceTimerValue.getIntSpinner().value as Int - state.filenameType = FilenameType.entries.toTypedArray()[component.filenameTypeSelector.getFilenameTypeComboBox().selectedIndex] state.fileListLimit = component.fileListLimit.getIntSpinner().value as Int + + state.filenameType = FilenameType.entries.toTypedArray()[component.filenameTypeSelector.getFilenameTypeComboBox().selectedIndex] + state.highlightFilename = component.highlightFilename.getCheckBox().isSelected state.fileListFontSize = component.fileListFontSize.getIntSpinner().value as Int state.previewFontSize = component.previewFontSize.getIntSpinner().value as Int state.fileListSpacing = component.fileListSpacing.getIntSpinner().value as Int diff --git a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt index aa2522f7..66a3264c 100644 --- a/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt +++ b/src/main/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsService.kt @@ -48,6 +48,7 @@ class FuzzierSettingsService : PersistentStateComponent Version 0.24.0 - - Add an option to change the font size for the preview window
- - Some dependency updates
- - Separate settings sections
+

Version 0.25.0

+ - Clear up settings grouping
+ - Add option to highlight filename matches in the file list
]]>
diff --git a/src/test/kotlin/com/mituuz/fuzzier/FuzzyActionTest.kt b/src/test/kotlin/com/mituuz/fuzzier/FuzzyActionTest.kt index 19037631..db3292ca 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/FuzzyActionTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/FuzzyActionTest.kt @@ -110,9 +110,24 @@ class FuzzyActionTest { } @Test - fun `Check renderer with styled path`() { + fun `Check renderer with styled path, no highlight`() { val action = getAction() action.setFiletype(FILENAME_WITH_PATH_STYLED) + action.setHighlight(false) + action.component = SimpleFinderComponent() + val renderer = action.getCellRenderer() + val container = FuzzyMatchContainer(FuzzyScore(), "/src/asd", "asd") + val dummyList = JList() + val component = renderer.getListCellRendererComponent(dummyList, container, 0, false, false) as JLabel + assertNotNull(component) + assertEquals("asd (/src/asd)", component.text) + } + + @Test + fun `Check renderer with styled path, with highlight but no values`() { + val action = getAction() + action.setFiletype(FILENAME_WITH_PATH_STYLED) + action.setHighlight(true) action.component = SimpleFinderComponent() val renderer = action.getCellRenderer() val container = FuzzyMatchContainer(FuzzyScore(), "/src/asd", "asd") diff --git a/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt new file mode 100644 index 00000000..efb3ef18 --- /dev/null +++ b/src/test/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainerTest.kt @@ -0,0 +1,78 @@ +/* +MIT License + +Copyright (c) 2024 Mitja Leino + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.mituuz.fuzzier.entities + +import com.intellij.testFramework.TestApplicationManager +import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FuzzyScore +import com.mituuz.fuzzier.settings.FuzzierConfiguration.END_STYLE_TAG +import com.mituuz.fuzzier.settings.FuzzierConfiguration.startStyleTag +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class FuzzyMatchContainerTest { + @Suppress("unused") + private val testManager = TestApplicationManager.getInstance() + + @Test + fun `Test highlight indexing simple case`() { + val score = FuzzyScore() + score.highlightCharacters.add(0) + score.highlightCharacters.add(4) + val container = FuzzyMatchContainer(score, "", "Hello") + val res = container.highlight(container.filename) + assertEquals("${startStyleTag}H${END_STYLE_TAG}ell${startStyleTag}o$END_STYLE_TAG", res) + } + + @Test + fun `Test highlight indexing complex case`() { + val score = FuzzyScore() + score.highlightCharacters.add(0) // f + score.highlightCharacters.add(1) // u + score.highlightCharacters.add(2) // z + score.highlightCharacters.add(3) // z + score.highlightCharacters.add(15) // i + score.highlightCharacters.add(17) // e + score.highlightCharacters.add(18) // r + + val container = FuzzyMatchContainer(score, "", "FuzzyMatchContainerTest.kt") + val res = container.highlight(container.filename) + val sb = StringBuilder() + + sb.append(startStyleTag, "F", END_STYLE_TAG) + sb.append(startStyleTag, "u", END_STYLE_TAG) + sb.append(startStyleTag, "z", END_STYLE_TAG) + sb.append(startStyleTag, "z", END_STYLE_TAG) + sb.append("yMatchConta") + sb.append(startStyleTag, "i", END_STYLE_TAG) + sb.append("n") + sb.append(startStyleTag, "e", END_STYLE_TAG) + sb.append(startStyleTag, "r", END_STYLE_TAG) + sb.append("Test.kt") + var i = 0 + while (i < res.length) { + assertEquals(sb.toString()[i], res[i]) + i++ + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt b/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt index 6569b35c..4b45f3da 100644 --- a/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt +++ b/src/test/kotlin/com/mituuz/fuzzier/settings/FuzzierSettingsConfigurableTest.kt @@ -43,8 +43,10 @@ class FuzzierSettingsConfigurableTest { state.recentFilesMode = NONE state.prioritizeShorterDirPaths = false state.debouncePeriod = 140 - state.filenameType = FILENAME_WITH_PATH_STYLED state.fileListLimit = 200 + + state.filenameType = FILENAME_WITH_PATH_STYLED + state.highlightFilename = false state.fileListFontSize = 15 state.previewFontSize = 0 state.fileListSpacing = 2 @@ -67,8 +69,10 @@ class FuzzierSettingsConfigurableTest { state.newTab = true state.recentFilesMode = RECENT_PROJECT_FILES state.debouncePeriod = 140 - state.filenameType = FILENAME_WITH_PATH_STYLED state.fileListLimit = 200 + + state.filenameType = FILENAME_WITH_PATH_STYLED + state.highlightFilename = false state.fileListFontSize = 15 state.previewFontSize = 0 state.fileListSpacing = 2