Skip to content

Commit

Permalink
Multithreading support | Part 1: Fuzzier searches (#80)
Browse files Browse the repository at this point in the history
* Fix unused exception naming

* Use multithreading for Fuzzier(VCS) searches

* Add change notes

* Add explicit instructions on how to get the most out of the plugin
  • Loading branch information
MituuZ authored Sep 8, 2024
1 parent 7b0e0a9 commit d47bfc2
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 14 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ intellijPlatform {
changeNotes = """
<h2>Version $currentVersion</h2>
- Improve task cancelling to avoid InterruptedException and reduce cancelling delay<br>
- Run searches with concurrency<br><br>
To have the most fluid experience it is recommended to NOT use the styled filename type and use a lower
debounce period (10 feels pretty good for me).
""".trimIndent()

ideaVersion {
Expand Down
41 changes: 34 additions & 7 deletions src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,14 @@ import com.intellij.openapi.wm.WindowManager
import com.mituuz.fuzzier.components.FuzzyFinderComponent
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.NONE
import com.mituuz.fuzzier.util.FuzzierUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.apache.commons.lang3.StringUtils
import java.awt.event.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Future
import javax.swing.*
import kotlin.coroutines.cancellation.CancellationException
Expand Down Expand Up @@ -184,9 +190,9 @@ open class Fuzzier : FuzzyAction() {
component.fileList.setSelectedValue(listModel[0], true)
}
}
} catch (e: InterruptedException) {
} catch (_: InterruptedException) {
return@executeOnPooledThread
} catch (e: CancellationException) {
} catch (_: CancellationException) {
return@executeOnPooledThread
}
}
Expand Down Expand Up @@ -223,16 +229,37 @@ open class Fuzzier : FuzzyAction() {

private fun processProject(project: Project, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {
val contentIterator = stringEvaluator.getContentIterator(project.name, searchString, listModel, task)
ProjectFileIndex.getInstance(project).iterateContent(contentIterator)
val filesToIterate = ConcurrentHashMap.newKeySet<FuzzierUtil.IterationFile>()
FuzzierUtil.fileIndexToIterationFile(filesToIterate, ProjectFileIndex.getInstance(project), project.name, task)
processFiles(filesToIterate, stringEvaluator, listModel, searchString, task)
}

private fun processModules(moduleManager: ModuleManager, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {
val filesToIterate = ConcurrentHashMap.newKeySet<FuzzierUtil.IterationFile>()
for (module in moduleManager.modules) {
val moduleFileIndex = module.rootManager.fileIndex
val contentIterator = stringEvaluator.getContentIterator(module.name, searchString, listModel, task)
moduleFileIndex.iterateContent(contentIterator)
FuzzierUtil.fileIndexToIterationFile(filesToIterate, module.rootManager.fileIndex, module.name, task)
}
processFiles(filesToIterate, stringEvaluator, listModel, searchString, task)
}

/**
* Processes a set of IterationFiles concurrently
*/
private fun processFiles(
filesToIterate: ConcurrentHashMap.KeySetView<FuzzierUtil.IterationFile, Boolean>,
stringEvaluator: StringEvaluator, listModel: DefaultListModel<FuzzyMatchContainer>,
searchString: String, task: Future<*>?
) {
runBlocking {
withContext(Dispatchers.IO) {
filesToIterate.forEach { iterationFile ->
if (task?.isCancelled == true) return@forEach
launch {
stringEvaluator.evaluateFile(iterationFile, listModel, searchString)
}
}
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ import com.intellij.psi.PsiManager
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil
import com.mituuz.fuzzier.components.SimpleFinderComponent
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.util.FuzzierUtil
import org.apache.commons.lang3.StringUtils
import java.awt.event.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Future
import javax.swing.*
import kotlin.coroutines.cancellation.CancellationException
Expand Down Expand Up @@ -224,9 +226,9 @@ class FuzzyMover : FuzzyAction() {
component.fileList.setSelectedValue(listModel[0], true)
}
}
} catch (e: InterruptedException) {
} catch (_: InterruptedException) {
return@executeOnPooledThread
} catch (e: CancellationException) {
} catch (_: CancellationException) {
return@executeOnPooledThread
}
}
Expand Down
29 changes: 26 additions & 3 deletions src/main/kotlin/com/mituuz/fuzzier/StringEvaluator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vfs.VirtualFile
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.ScoreCalculator
import com.mituuz.fuzzier.util.FuzzierUtil
import java.util.concurrent.Future
import javax.swing.DefaultListModel

Expand Down Expand Up @@ -58,7 +59,7 @@ class StringEvaluator(
return@ContentIterator true
}
if (filePath.isNotBlank()) {
val fuzzyMatchContainer = createFuzzyContainer(filePath, moduleName)
val fuzzyMatchContainer = createFuzzyContainer(filePath, moduleName, scoreCalculator)
if (fuzzyMatchContainer != null) {
listModel.addElement(fuzzyMatchContainer)
}
Expand All @@ -82,7 +83,7 @@ class StringEvaluator(
return@ContentIterator true
}
if (filePath.isNotBlank()) {
val fuzzyMatchContainer = createFuzzyContainer(filePath, moduleName)
val fuzzyMatchContainer = createFuzzyContainer(filePath, moduleName, scoreCalculator)
if (fuzzyMatchContainer != null) {
listModel.addElement(fuzzyMatchContainer)
}
Expand All @@ -91,6 +92,27 @@ class StringEvaluator(
true
}
}

fun evaluateFile(iterationFile: FuzzierUtil.IterationFile, listModel: DefaultListModel<FuzzyMatchContainer>,
searchString: String) {
val scoreCalculator = ScoreCalculator(searchString)
val file = iterationFile.file
val moduleName = iterationFile.module
if (!file.isDirectory) {
val moduleBasePath = modules[moduleName] ?: return

val filePath = file.path.removePrefix(moduleBasePath)
if (isExcluded(file, filePath)) {
return
}
if (filePath.isNotBlank()) {
val fuzzyMatchContainer = createFuzzyContainer(filePath, moduleName, scoreCalculator)
if (fuzzyMatchContainer != null) {
listModel.addElement(fuzzyMatchContainer)
}
}
}
}

private fun getDirPath(virtualFile: VirtualFile, basePath: String, module: String): String {
var res = virtualFile.path.removePrefix(basePath)
Expand Down Expand Up @@ -133,7 +155,8 @@ class StringEvaluator(
* @param filePath to evaluate
* @return null if no match can be found
*/
private fun createFuzzyContainer(filePath: String, module: String): FuzzyMatchContainer? {
private fun createFuzzyContainer(filePath: String, module: String,
scoreCalculator: ScoreCalculator): FuzzyMatchContainer? {
val filename = filePath.substring(filePath.lastIndexOf("/") + 1)
return when (val score = scoreCalculator.calculateScore(filePath)) {
null -> {
Expand Down
25 changes: 23 additions & 2 deletions src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,34 @@ import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.settings.FuzzierSettingsService
import java.util.*
import javax.swing.DefaultListModel
import kotlin.collections.ArrayList
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.FileIndex
import com.intellij.openapi.vfs.VirtualFile
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Future

class FuzzierUtil {
private var settingsState = service<FuzzierSettingsService>().state
private var listLimit: Int = settingsState.fileListLimit
private var prioritizeShorterDirPaths = settingsState.prioritizeShorterDirPaths

data class IterationFile(val file: VirtualFile, val module: String)

companion object {
fun fileIndexToIterationFile(iterationFiles: ConcurrentHashMap.KeySetView<IterationFile, Boolean>,
fileIndex: FileIndex, moduleName: String, task: Future<*>?,
isDir: Boolean = false) {
fileIndex.iterateContent { file ->
if (task?.isCancelled == true) {
return@iterateContent false
}
if (file.isDirectory == isDir) {
iterationFiles.add(IterationFile(file, moduleName))
}
true
}
}
}

/**
* Process all the elements in the listModel with a priority queue to limit the size
Expand All @@ -58,7 +79,7 @@ class FuzzierUtil {

var minimumScore: Int? = null
listModel.elements().toList().forEach {
if (minimumScore == null || it.getScore() > minimumScore!!) {
if (minimumScore == null || it.getScore() > minimumScore) {
priorityQueue.add(it)
if (priorityQueue.size > listLimit) {
priorityQueue.remove()
Expand Down

0 comments on commit d47bfc2

Please sign in to comment.