Skip to content

Commit 4b4f4c5

Browse files
authored
Remove redundant IO dispatch and cleanup (#171)
* Remove redundant IO dispatch and cleanup * Add some tests for file validation * Add unmockk between the tests * Pass instances for testing * Add initial test setup for findInFiles * Extract the common part for tests * Add basic test for find in files
1 parent 7d1a9a3 commit 4b4f4c5

2 files changed

Lines changed: 216 additions & 13 deletions

File tree

src/main/kotlin/com/mituuz/fuzzier/grep/FuzzyGrep.kt

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ open class FuzzyGrep : FuzzyAction() {
9292
}
9393

9494
val resolvedBackend = backendResult.getOrNull() ?: return@launch
95-
backend = resolvedBackend
95+
updateBackend(resolvedBackend)
9696
val popupTitle = grepConfig.getPopupTitle(resolvedBackend.name)
9797

9898
yield()
@@ -132,45 +132,62 @@ open class FuzzyGrep : FuzzyAction() {
132132

133133
override fun updateListContents(project: Project, searchString: String) {
134134
if (StringUtils.isBlank(searchString)) {
135+
currentUpdateListContentJob?.cancel()
136+
currentUpdateListContentJob = null
135137
component.fileList.model = DefaultListModel()
138+
component.fileList.setPaintBusy(false)
136139
return
137140
}
138141

139142
currentUpdateListContentJob?.cancel()
140-
currentUpdateListContentJob = actionScope?.launch(Dispatchers.EDT) {
143+
val updateJob = actionScope?.launch(Dispatchers.EDT, start = CoroutineStart.LAZY) {
141144
component.fileList.setPaintBusy(true)
145+
142146
try {
143-
val results = withContext(Dispatchers.IO) {
144-
findInFiles(
145-
searchString, project
146-
)
147-
}
147+
val changelistManager = ChangeListManager.getInstance(project)
148+
val results = findInFiles(
149+
searchString,
150+
project,
151+
changelistManager,
152+
backend
153+
)
148154
coroutineContext.ensureActive()
155+
149156
component.refreshModel(results, getCellRenderer())
150157
} finally {
151-
component.fileList.setPaintBusy(false)
158+
if (currentUpdateListContentJob == coroutineContext.job) {
159+
component.fileList.setPaintBusy(false)
160+
currentUpdateListContentJob = null
161+
}
152162
}
153163
}
164+
165+
currentUpdateListContentJob = updateJob
166+
updateJob?.start()
154167
}
155168

156-
private suspend fun findInFiles(
169+
suspend fun findInFiles(
157170
searchString: String,
158171
project: Project,
172+
clm: ChangeListManager,
173+
resolvedBackend: BackendStrategy?
159174
): ListModel<FuzzyContainer> {
160175
val listModel = DefaultListModel<FuzzyContainer>()
161176
val projectBasePath = project.basePath
162177

163-
if (backend != null && projectBasePath != null) {
178+
if (resolvedBackend != null && projectBasePath != null) {
164179
val secondaryFieldText = (component as FuzzyFinderComponent).getSecondaryText()
165-
backend!!.handleSearch(
180+
resolvedBackend.handleSearch(
166181
grepConfig, searchString, secondaryFieldText, commandRunner, listModel, projectBasePath, project
167-
) { vf -> validVf(vf, secondaryFieldText, ChangeListManager.getInstance(project)) }
182+
) { vf ->
183+
validVf(vf, secondaryFieldText, clm)
184+
}
168185
}
169186

170187
return listModel
171188
}
172189

173-
private fun validVf(
190+
fun validVf(
174191
virtualFile: VirtualFile, secondaryFieldText: String? = null, clm: ChangeListManager
175192
): Boolean {
176193
if (virtualFile.isDirectory) return false
@@ -222,4 +239,12 @@ open class FuzzyGrep : FuzzyAction() {
222239
}
223240
}
224241
}
242+
243+
fun updateBackend(resolvedBackend: BackendStrategy?) {
244+
backend = resolvedBackend
245+
}
246+
247+
fun updateGrepConfig(config: GrepConfig) {
248+
grepConfig = config
249+
}
225250
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 Mitja Leino
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package com.mituuz.fuzzier.grep
26+
27+
import com.intellij.openapi.project.Project
28+
import com.intellij.openapi.vcs.changes.ChangeListManager
29+
import com.intellij.openapi.vfs.VirtualFile
30+
import com.intellij.testFramework.TestApplicationManager
31+
import com.mituuz.fuzzier.components.FuzzyFinderComponent
32+
import com.mituuz.fuzzier.entities.CaseMode
33+
import com.mituuz.fuzzier.entities.GrepConfig
34+
import com.mituuz.fuzzier.grep.backend.BackendStrategy
35+
import io.mockk.every
36+
import io.mockk.mockk
37+
import io.mockk.unmockkAll
38+
import kotlinx.coroutines.runBlocking
39+
import org.junit.jupiter.api.AfterEach
40+
import org.junit.jupiter.api.Assertions.assertEquals
41+
import org.junit.jupiter.api.Assertions.assertNotNull
42+
import org.junit.jupiter.api.BeforeEach
43+
import org.junit.jupiter.api.Test
44+
45+
class FuzzyGrepTest {
46+
private lateinit var fGrep: FuzzyGrep
47+
48+
private data class ValidVfContext(
49+
val file: VirtualFile,
50+
val clm: ChangeListManager
51+
)
52+
53+
private data class FindInFilesContext(
54+
val project: Project,
55+
val component: FuzzyFinderComponent,
56+
val backend: BackendStrategy,
57+
val clm: ChangeListManager
58+
)
59+
60+
@BeforeEach
61+
fun setUp() {
62+
TestApplicationManager.getInstance()
63+
fGrep = FuzzyGrep()
64+
}
65+
66+
@AfterEach
67+
fun tearDown() {
68+
unmockkAll()
69+
}
70+
71+
private fun createValidVfContext(
72+
isDirectory: Boolean = false,
73+
isBinary: Boolean = false,
74+
isIgnored: Boolean = false,
75+
extension: String? = null
76+
): ValidVfContext {
77+
val file = mockk<VirtualFile>()
78+
val clm = mockk<ChangeListManager>()
79+
80+
every { file.isDirectory } returns isDirectory
81+
every { file.fileType.isBinary } returns isBinary
82+
every { clm.isIgnoredFile(file) } returns isIgnored
83+
if (extension != null) {
84+
every { file.extension } returns extension
85+
}
86+
87+
return ValidVfContext(file, clm)
88+
}
89+
90+
private fun createFindInFilesContext(
91+
projectBasePath: String? = "/tmp/project",
92+
secondaryText: String = "kt"
93+
): FindInFilesContext {
94+
val project = mockk<Project>()
95+
val component = mockk<FuzzyFinderComponent>()
96+
val backend = mockk<BackendStrategy>()
97+
val clm = mockk<ChangeListManager>()
98+
99+
every { project.basePath } returns projectBasePath
100+
every { component.getSecondaryText() } returns secondaryText
101+
102+
fGrep.component = component
103+
fGrep.updateBackend(backend)
104+
fGrep.updateGrepConfig(
105+
GrepConfig(targets = null, caseMode = CaseMode.SENSITIVE, title = "Fuzzy Grep")
106+
)
107+
108+
return FindInFilesContext(project, component, backend, clm)
109+
}
110+
111+
@Test
112+
fun `Directories should not be valid`() {
113+
val (file1, clm) = createValidVfContext(isDirectory = true)
114+
115+
val res = fGrep.validVf(file1, null, clm)
116+
assert(!res)
117+
}
118+
119+
@Test
120+
fun `Binary files should not be valid`() {
121+
val (file1, clm) = createValidVfContext(isBinary = true)
122+
123+
val res = fGrep.validVf(file1, null, clm)
124+
assert(!res)
125+
}
126+
127+
@Test
128+
fun `Ignored files should not be valid`() {
129+
val (file1, clm) = createValidVfContext(isIgnored = true)
130+
131+
val res = fGrep.validVf(file1, null, clm)
132+
assert(!res)
133+
}
134+
135+
@Test
136+
fun `null secondary field should be valid`() {
137+
val (file1, clm) = createValidVfContext()
138+
139+
val res = fGrep.validVf(file1, null, clm)
140+
assert(res)
141+
}
142+
143+
@Test
144+
fun `Matching secondary field should be valid`() {
145+
val (file1, clm) = createValidVfContext(extension = "kt")
146+
147+
val res = fGrep.validVf(file1, "kt", clm)
148+
assert(res)
149+
}
150+
151+
@Test
152+
fun `Non-matching secondary field should not be valid`() {
153+
val (file1, clm) = createValidVfContext(extension = "java")
154+
155+
val res = fGrep.validVf(file1, "kt", clm)
156+
assert(!res)
157+
}
158+
159+
@Test
160+
fun `findInFiles should skip backend when backend is null`() = runBlocking {
161+
val (project, _, _, clm) = createFindInFilesContext()
162+
163+
val model = fGrep.findInFiles("needle", project, clm, null)
164+
165+
assertNotNull(model)
166+
assertEquals(0, model.size)
167+
}
168+
169+
@Test
170+
fun `findInFiles should skip backend when project base path is null`() = runBlocking {
171+
val (project, _, backend, clm) = createFindInFilesContext(projectBasePath = null)
172+
173+
val model = fGrep.findInFiles("needle", project, clm, backend)
174+
175+
assertNotNull(model)
176+
assertEquals(0, model.size)
177+
}
178+
}

0 commit comments

Comments
 (0)