Skip to content

Commit

Permalink
Recently searched files (#90)
Browse files Browse the repository at this point in the history
* Implement basic list handling for recently searched files

* Working implementation, but list resets after restart

* Format file

* Implement a converter to persist recently searched files accross ide restarts

* Add javadoc

* Move string evaluator to another package

* Separate initial views to a util class

* Fix class name

* Create initial view handler test

* Add test for recent project files

* Add tests for recently searched files

* Add new settings to non-modified test

* Add separate checks for each user configurable setting

* Reverse recently searched files before listing them

* Add tests for adding files to recently used file list

* Move recent file additions to initial view handler

* Use repeat instead

* Add missing licenses

* Use index accessors

* Increment version and add change notes
  • Loading branch information
MituuZ authored Nov 23, 2024
1 parent 22ee920 commit 5dacdb2
Show file tree
Hide file tree
Showing 15 changed files with 622 additions and 87 deletions.
14 changes: 6 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

// Use same version and group for the jar and the plugin
val currentVersion = "1.2.0"
val currentVersion = "1.3.0"
val myGroup = "com.mituuz"
version = currentVersion
group = myGroup
Expand All @@ -33,10 +33,10 @@ dependencies {
testFramework(TestFrameworkType.Platform)
}

testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.3")
testImplementation("org.mockito:mockito-core:5.14.2")

testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.3")

// Required to fix issue where JUnit5 Test Framework refers to JUnit4
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
Expand All @@ -63,10 +63,8 @@ intellijPlatform {

changeNotes = """
<h2>Version $currentVersion</h2>
- Make popup dimensions persistent across projects<br>
&emsp;- Popup dimensions are saved per screen bounds (location and size)<br>
- Improve popup location consistency (fixes right screen, left half issue)<br>
- Update kotlin-jvm and intellij-platform plugins to 2.1.0
- Add option to list recently searched files on popup open<br>
- Update some dependencies
""".trimIndent()

ideaVersion {
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
## Version 1.3.0
- Add option to list recently searched files on popup open
- Update some dependencies

## Version 1.2.0
- Make popup dimensions persistent across projects
- Improve popup location consistency (fixes right screen, left half issue)
Expand Down
69 changes: 39 additions & 30 deletions src/main/kotlin/com/mituuz/fuzzier/Fuzzier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.wm.WindowManager
import com.mituuz.fuzzier.components.FuzzyFinderComponent
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.StringEvaluator
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.NONE
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENTLY_SEARCHED_FILES
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENT_PROJECT_FILES
import com.mituuz.fuzzier.util.FuzzierUtil
import com.mituuz.fuzzier.util.FuzzierUtil.Companion.createDimensionKey
import com.mituuz.fuzzier.util.InitialViewHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
Expand All @@ -66,6 +70,7 @@ open class Fuzzier : FuzzyAction() {
private var defaultDoc: Document? = null
open var title: String = "Fuzzy Search"
private val fuzzyDimensionKey: String = "FuzzySearchPopup"

// Used by FuzzierVCS to check if files are tracked by the VCS
protected var changeListManager: ChangeListManager? = null

Expand Down Expand Up @@ -133,24 +138,19 @@ open class Fuzzier : FuzzyAction() {
*/
private fun createInitialView(project: Project) {
ApplicationManager.getApplication().executeOnPooledThread {
val editorHistory = EditorHistoryManager.getInstance(project).fileList
val listModel = DefaultListModel<FuzzyMatchContainer>()
val limit = fuzzierSettingsService.state.fileListLimit

// Start from the end of editor history (most recent file)
var i = editorHistory.size - 1
while (i >= 0 && listModel.size() < limit) {
val file = editorHistory[i]
val filePathAndModule = fuzzierUtil.removeModulePath(file.path)
// Don't add files that do not have a module path in the project
if (filePathAndModule.second == "") {
i--
continue
val editorHistoryManager = EditorHistoryManager.getInstance(project)

val listModel = when (fuzzierSettingsService.state.recentFilesMode) {
RECENT_PROJECT_FILES -> InitialViewHandler.getRecentProjectFiles(
fuzzierSettingsService,
fuzzierUtil,
editorHistoryManager
)

RECENTLY_SEARCHED_FILES -> InitialViewHandler.getRecentlySearchedFiles(fuzzierSettingsService)
else -> {
DefaultListModel<FuzzyMatchContainer>()
}
val fuzzyMatchContainer =
FuzzyMatchContainer.createOrderedContainer(i, filePathAndModule.first, filePathAndModule.second, file.name)
listModel.addElement(fuzzyMatchContainer)
i--
}

ApplicationManager.getApplication().invokeLater {
Expand Down Expand Up @@ -205,7 +205,7 @@ open class Fuzzier : FuzzyAction() {
}
}
}

private fun handleEmptySearchString(project: Project) {
if (fuzzierSettingsService.state.recentFilesMode != NONE) {
createInitialView(project)
Expand All @@ -216,34 +216,40 @@ open class Fuzzier : FuzzyAction() {
}
}
}

private fun getStringEvaluator(): StringEvaluator {
return StringEvaluator(
fuzzierSettingsService.state.exclusionSet,
fuzzierSettingsService.state.modules,
changeListManager
)
}

private fun process(project: Project, stringEvaluator: StringEvaluator, searchString: String,
listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {

private fun process(
project: Project, stringEvaluator: StringEvaluator, searchString: String,
listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?
) {
val moduleManager = ModuleManager.getInstance(project)
if (fuzzierSettingsService.state.isProject) {
processProject(project, stringEvaluator, searchString, listModel, task)
} else {
processModules(moduleManager, stringEvaluator, searchString, listModel, task)
}
}

private fun processProject(project: Project, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?) {

private fun processProject(
project: Project, stringEvaluator: StringEvaluator,
searchString: String, listModel: DefaultListModel<FuzzyMatchContainer>, task: Future<*>?
) {
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<*>?) {
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) {
FuzzierUtil.fileIndexToIterationFile(filesToIterate, module.rootManager.fileIndex, module.name, task)
Expand Down Expand Up @@ -271,7 +277,7 @@ open class Fuzzier : FuzzyAction() {
}
}

private fun openFile(project: Project, virtualFile: VirtualFile) {
private fun openFile(project: Project, fuzzyMatchContainer: FuzzyMatchContainer?, virtualFile: VirtualFile) {
val fileEditorManager = FileEditorManager.getInstance(project)
val currentEditor = fileEditorManager.selectedTextEditor
val previousFile = currentEditor?.virtualFile
Expand All @@ -288,6 +294,9 @@ open class Fuzzier : FuzzyAction() {
}
}
}
if (fuzzyMatchContainer != null) {
InitialViewHandler.addFileToRecentlySearchedFiles(fuzzyMatchContainer, fuzzierSettingsService)
}
popup?.cancel()
}

Expand Down Expand Up @@ -324,7 +333,7 @@ open class Fuzzier : FuzzyAction() {
VirtualFileManager.getInstance().findFileByUrl("file://${selectedValue?.getFileUri()}")
// Open the file in the editor
virtualFile?.let {
openFile(project, it)
openFile(project, selectedValue, it)
}
}
}
Expand All @@ -341,7 +350,7 @@ open class Fuzzier : FuzzyAction() {
val virtualFile =
VirtualFileManager.getInstance().findFileByUrl("file://${selectedValue?.getFileUri()}")
virtualFile?.let {
openFile(project, it)
openFile(project, selectedValue, it)
}
}
})
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/mituuz/fuzzier/FuzzyMover.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ 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.entities.StringEvaluator
import com.mituuz.fuzzier.util.FuzzierUtil.Companion.createDimensionKey
import org.apache.commons.lang3.StringUtils
import java.awt.Point
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import com.intellij.ui.components.JBTextArea
import com.intellij.ui.table.JBTable
import com.intellij.uiDesigner.core.GridConstraints
import com.intellij.uiDesigner.core.GridLayoutManager
import com.mituuz.fuzzier.StringEvaluator
import com.mituuz.fuzzier.entities.StringEvaluator
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.settings.FuzzierSettingsService
import com.mituuz.fuzzier.util.FuzzierUtil
Expand Down
61 changes: 57 additions & 4 deletions src/main/kotlin/com/mituuz/fuzzier/entities/FuzzyMatchContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,35 @@ SOFTWARE.
package com.mituuz.fuzzier.entities

import com.intellij.openapi.components.service
import com.intellij.util.xmlb.Converter
import com.intellij.util.xmlb.XmlSerializationException
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 = "") {
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.Base64
import javax.swing.DefaultListModel

class FuzzyMatchContainer(
val score: FuzzyScore,
var filePath: String,
var filename: String,
private var module: String = ""
) : Serializable {
@Transient
private var initialPath: String? = null

companion object {
fun createOrderedContainer(order: Int, filePath: String, initialPath:String, filename: String): FuzzyMatchContainer {
fun createOrderedContainer(
order: Int,
filePath: String,
initialPath: String,
filename: String
): FuzzyMatchContainer {
val fuzzyScore = FuzzyScore()
fuzzyScore.filenameScore = order
val fuzzyMatchContainer = FuzzyMatchContainer(fuzzyScore, filePath, filename)
Expand Down Expand Up @@ -105,7 +125,7 @@ class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filen
FILENAME_WITH_PATH_STYLED("Filename with (path) styled")
}

class FuzzyScore {
class FuzzyScore : Serializable {
var streakScore = 0
var multiMatchScore = 0
var partialPathScore = 0
Expand All @@ -120,4 +140,37 @@ class FuzzyMatchContainer(val score: FuzzyScore, var filePath: String, var filen
override fun toString(): String {
return "FuzzyMatchContainer: $filename, score: ${getScore()}, dir score: ${getScoreWithDirLength()}"
}

/**
* This is necessary to persists recently used files between IDE restarts
*
* Uses a base 64 encoded string
*
* ```
* @OptionTag(converter = FuzzyMatchContainer.FuzzyMatchContainerConverter::class)
* var recentlySearchedFiles: DefaultListModel<FuzzyMatchContainer>? = DefaultListModel()
* ```
*
* @see FuzzierSettingsService
*/
class FuzzyMatchContainerConverter : Converter<DefaultListModel<FuzzyMatchContainer>>() {
override fun fromString(value: String) : DefaultListModel<FuzzyMatchContainer> {
// Fallback to an empty list if deserialization fails
try {
val data = Base64.getDecoder().decode(value)
val byteArrayInputStream = ByteArrayInputStream(data)
return ObjectInputStream(byteArrayInputStream).use { it.readObject() as DefaultListModel<FuzzyMatchContainer> }
} catch (_: XmlSerializationException) {
return DefaultListModel<FuzzyMatchContainer>();
} catch (_: IllegalArgumentException) {
return DefaultListModel<FuzzyMatchContainer>();
}
}

override fun toString(value: DefaultListModel<FuzzyMatchContainer>) : String {
val byteArrayOutputStream = ByteArrayOutputStream()
ObjectOutputStream(byteArrayOutputStream).use { it.writeObject(value) }
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ 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
package com.mituuz.fuzzier.entities

import com.intellij.openapi.roots.ContentIterator
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 @@ -92,7 +90,7 @@ class StringEvaluator(
true
}
}

fun evaluateFile(iterationFile: FuzzierUtil.IterationFile, listModel: DefaultListModel<FuzzyMatchContainer>,
searchString: String) {
val scoreCalculator = ScoreCalculator(searchString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ package com.mituuz.fuzzier.settings
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.util.xmlb.annotations.OptionTag
import com.mituuz.fuzzier.entities.FuzzyMatchContainer
import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FilenameType
import com.mituuz.fuzzier.entities.FuzzyMatchContainer.FilenameType.FILE_PATH_ONLY
import com.mituuz.fuzzier.settings.FuzzierSettingsService.RecentFilesMode.RECENT_PROJECT_FILES
import javax.swing.DefaultListModel

@State(
name = "com.mituuz.fuzzier.FuzzierSettings",
Expand All @@ -39,6 +42,8 @@ class FuzzierSettingsService : PersistentStateComponent<FuzzierSettingsService.S
var modules: Map<String, String> = HashMap()
var isProject = false
var recentFilesMode: RecentFilesMode = RECENT_PROJECT_FILES
@OptionTag(converter = FuzzyMatchContainer.FuzzyMatchContainerConverter::class)
var recentlySearchedFiles: DefaultListModel<FuzzyMatchContainer>? = DefaultListModel()

var splitPosition: Int = 300
var exclusionSet: Set<String> = setOf("/.idea/*", "/.git/*", "/target/*", "/build/*", "/.gradle/*", "/.run/*")
Expand Down Expand Up @@ -74,6 +79,7 @@ class FuzzierSettingsService : PersistentStateComponent<FuzzierSettingsService.S

enum class RecentFilesMode(val text: String) {
NONE("None"),
RECENT_PROJECT_FILES("Recent project files")
RECENT_PROJECT_FILES("Recent project files"),
RECENTLY_SEARCHED_FILES("Recently searched files")
}
}
6 changes: 5 additions & 1 deletion src/main/kotlin/com/mituuz/fuzzier/util/FuzzierUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ class FuzzierUtil {
this.prioritizeShorterDirPaths = prioritizeShortedFilePaths;
}

fun removeModulePath(filePath: String): Pair<String, String> {
/**
* For each module in the project, check if the file path contains the module path.
* @return a pair of the file path (with the module path removed) and the module path
*/
fun extractModulePath(filePath: String): Pair<String, String> {
val modules = settingsState.modules
for (modulePath in modules.values) {
if (filePath.contains(modulePath)) {
Expand Down
Loading

0 comments on commit 5dacdb2

Please sign in to comment.