Skip to content

Commit

Permalink
Handle tolerance in search string (#47)
Browse files Browse the repository at this point in the history
* Add tolerance setting, handling and tests

* Add tolerance component to settings

* Fix tolerance setting entry

* Reset tolerance for each file path search

---------

Co-authored-by: Mitja Leino <[email protected]>
  • Loading branch information
MituuZ and Mitja Leino authored Apr 27, 2024
1 parent d74bfd7 commit fdddf0a
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ class FuzzierSettingsComponent {
/////////////////////////////////////////////////////////////////
// Match settings
/////////////////////////////////////////////////////////////////
val tolerance = SettingsComponent(JBIntSpinner(0, 0, 5), "Match tolerance",
"""
How many non-matching letters are allowed when calculating matches.<br><br>
e.g. korlin would still match kotlin.
""".trimIndent(),
false)

val multiMatchActive = SettingsComponent(JBCheckBox(), "Match characters multiple times",
"""
Count score for each instance of a character in the search string.<br><br>
Expand Down Expand Up @@ -149,6 +156,7 @@ class FuzzierSettingsComponent {

.addSeparator()
.addComponent(JBLabel("<html><strong>Match settings</strong></html>"))
.addComponent(tolerance)
.addComponent(multiMatchActive)
.addComponent(matchWeightSingleChar)
.addComponent(matchWeightPartialPath)
Expand Down
21 changes: 19 additions & 2 deletions src/main/kotlin/com/mituuz/fuzzier/entities/ScoreCalculator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ScoreCalculator(searchString: String) {

// Set up the settings
private val settings = service<FuzzierSettingsService>().state
private var tolerance = settings.tolerance
private var multiMatch = settings.multiMatch
private var matchWeightSingleChar = settings.matchWeightSingleChar
private var matchWeightStreakModifier = settings.matchWeightStreakModifier
Expand All @@ -29,6 +30,7 @@ class ScoreCalculator(searchString: String) {
private var currentStreak: Int = 0
private var longestFilenameStreak: Int = 0
private var currentFilenameStreak: Int = 0
private var toleranceCount: Int = 0

/**
* Returns null if no match can be found
Expand All @@ -38,9 +40,10 @@ class ScoreCalculator(searchString: String) {
filenameIndex = currentFilePath.lastIndexOf("/") + 1
longestStreak = 0
fuzzyScore = FuzzyScore()
toleranceCount = 0

// Check if the search string is longer than the file path, which results in no match
if (lowerSearchString.length > currentFilePath.length) { // TODO: + tolerance when it is implemented
if (lowerSearchString.length > (currentFilePath.length + tolerance)) {
return null
}

Expand Down Expand Up @@ -99,11 +102,21 @@ class ScoreCalculator(searchString: String) {
}

private fun processChar(searchStringPartChar: Char): Boolean {
if (filePathIndex >= currentFilePath.length) {
return false;
}
val filePathPartChar = currentFilePath[filePathIndex]
if (searchStringPartChar == filePathPartChar) {
searchStringIndex++
updateStreak(true)
} else {
if (currentStreak > 0 && toleranceCount < tolerance) {
// When hitting tolerance increment the search string and filepath, but do not add streak
searchStringIndex++
toleranceCount++
filePathIndex++
return true
}
updateStreak(false)
}
filePathIndex++
Expand Down Expand Up @@ -158,7 +171,7 @@ class ScoreCalculator(searchString: String) {
fun canSearchStringBeContained(): Boolean {
val remainingSearchStringLength = searchStringLength - searchStringIndex
val remainingFilePathLength = currentFilePath.length - filePathIndex
return remainingSearchStringLength <= remainingFilePathLength // TODO: + tolerance when it is implemented
return remainingSearchStringLength <= (remainingFilePathLength + tolerance)
}

fun setMatchWeightStreakModifier(value: Int) {
Expand All @@ -180,4 +193,8 @@ class ScoreCalculator(searchString: String) {
fun setMultiMatch(value: Boolean) {
multiMatch = value
}

fun setTolerance(value: Int) {
tolerance = value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class FuzzierSettingsConfigurable : Configurable {
fuzzierSettingsComponent.fontSize.getIntSpinner().value = fuzzierSettingsService.state.fontSize
fuzzierSettingsComponent.fileListSpacing.getIntSpinner().value = fuzzierSettingsService.state.fileListSpacing

fuzzierSettingsComponent.tolerance.getIntSpinner().value = fuzzierSettingsService.state.tolerance
fuzzierSettingsComponent.multiMatchActive.getCheckBox().isSelected = fuzzierSettingsService.state.multiMatch
fuzzierSettingsComponent.matchWeightPartialPath.getIntSpinner().value = fuzzierSettingsService.state.matchWeightPartialPath
fuzzierSettingsComponent.matchWeightSingleChar.getIntSpinner().value = fuzzierSettingsService.state.matchWeightSingleChar
Expand All @@ -48,6 +49,7 @@ class FuzzierSettingsConfigurable : Configurable {
|| fuzzierSettingsService.state.fontSize != fuzzierSettingsComponent.fontSize.getIntSpinner().value
|| fuzzierSettingsService.state.fileListSpacing != fuzzierSettingsComponent.fileListSpacing.getIntSpinner().value

|| fuzzierSettingsService.state.tolerance != fuzzierSettingsComponent.tolerance.getIntSpinner().value
|| fuzzierSettingsService.state.multiMatch != fuzzierSettingsComponent.multiMatchActive.getCheckBox().isSelected
|| fuzzierSettingsService.state.matchWeightPartialPath != fuzzierSettingsComponent.matchWeightPartialPath.getIntSpinner().value
|| fuzzierSettingsService.state.matchWeightSingleChar != fuzzierSettingsComponent.matchWeightSingleChar.getIntSpinner().value
Expand All @@ -67,7 +69,8 @@ class FuzzierSettingsConfigurable : Configurable {
fuzzierSettingsService.state.fileListLimit = fuzzierSettingsComponent.fileListLimit.getIntSpinner().value as Int
fuzzierSettingsService.state.fontSize = fuzzierSettingsComponent.fontSize.getIntSpinner().value as Int
fuzzierSettingsService.state.fileListSpacing = fuzzierSettingsComponent.fileListSpacing.getIntSpinner().value as Int


fuzzierSettingsService.state.tolerance = fuzzierSettingsComponent.tolerance.getIntSpinner().value as Int
fuzzierSettingsService.state.multiMatch = fuzzierSettingsComponent.multiMatchActive.getCheckBox().isSelected
fuzzierSettingsService.state.matchWeightPartialPath = fuzzierSettingsComponent.matchWeightPartialPath.getIntSpinner().value as Int
fuzzierSettingsService.state.matchWeightSingleChar = fuzzierSettingsComponent.matchWeightSingleChar.getIntSpinner().value as Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class FuzzierSettingsService : PersistentStateComponent<FuzzierSettingsService.S
var fontSize = 14
var fileListSpacing = 0

var tolerance = 0
var multiMatch = false
var matchWeightPartialPath = 10
var matchWeightSingleChar = 5
Expand Down
64 changes: 64 additions & 0 deletions src/test/kotlin/com/mituuz/fuzzier/entities/ScoreCalculatorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,68 @@ class ScoreCalculatorTest {
assertEquals(30, fScore.partialPathScore)
assertEquals(3, fScore.filenameScore)
}

@Test
fun `5 tolerance matching normal match`() {
val sc = ScoreCalculator("kotlin")
sc.setTolerance(5)

assertNotNull(sc.calculateScore("/Kotlin"))
}

@Test
fun `5 tolerance matching 1 letter difference`() {
val sc = ScoreCalculator("korlin")
sc.setTolerance(5)

assertNotNull(sc.calculateScore("/Kotlin"))
}

@Test
fun `1 tolerance matching 1 letter difference`() {
val sc = ScoreCalculator("korlin")
sc.setTolerance(1)

assertNotNull(sc.calculateScore("/Kotlin"))
}

@Test
fun `1 tolerance matching 2 letter difference`() {
val sc = ScoreCalculator("korlnn")
sc.setTolerance(1)

assertNull(sc.calculateScore("/Kotlin"))
}

@Test
fun `1 tolerance matching 1 letter difference with split path`() {
val sc = ScoreCalculator("korlin")
sc.setTolerance(1)

assertNotNull(sc.calculateScore("/Kot/lin"))
}

@Test
fun `1 tolerance matching 2 letter difference with split path`() {
val sc = ScoreCalculator("korlin")
sc.setTolerance(1)

assertNull(sc.calculateScore("/Kot/sin"))
}

@Test
fun `2 tolerance matching 2 letter difference with split path`() {
val sc = ScoreCalculator("korlin")
sc.setTolerance(2)

assertNotNull(sc.calculateScore("/Kot/sin"))
}

@Test
fun `Don't match longer strings even if there is tolerance left`() {
val sc = ScoreCalculator("kotlin12345")
sc.setTolerance(5)

assertNull(sc.calculateScore("/Kotlin"))
}
}

0 comments on commit fdddf0a

Please sign in to comment.