Skip to content

Commit 9498142

Browse files
authored
Merge branch 'feature/q-lsp-chat' into manodnyb/fixGenerateUnitTests
2 parents 47bcef1 + 810c815 commit 9498142

File tree

3 files changed

+87
-96
lines changed

3 files changed

+87
-96
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Fix UI freezes that may occur when interacting with large files in the editor"
4+
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,18 @@ class TextDocumentServiceHandler(
7777
}
7878
AmazonQLspService.executeIfRunning(project) { languageServer ->
7979
toUriString(file)?.let { uri ->
80-
languageServer.textDocumentService.didOpen(
81-
DidOpenTextDocumentParams().apply {
82-
textDocument = TextDocumentItem().apply {
83-
this.uri = uri
84-
text = file.inputStream.readAllBytes().decodeToString()
85-
languageId = file.fileType.name.lowercase()
86-
version = file.modificationStamp.toInt()
80+
pluginAwareExecuteOnPooledThread {
81+
languageServer.textDocumentService.didOpen(
82+
DidOpenTextDocumentParams().apply {
83+
textDocument = TextDocumentItem().apply {
84+
this.uri = uri
85+
text = file.inputStream.readAllBytes().decodeToString()
86+
languageId = file.fileType.name.lowercase()
87+
version = file.modificationStamp.toInt()
88+
}
8789
}
88-
}
89-
)
90+
)
91+
}
9092
}
9193
}
9294
}
@@ -95,14 +97,16 @@ class TextDocumentServiceHandler(
9597
AmazonQLspService.executeIfRunning(project) { languageServer ->
9698
val file = FileDocumentManager.getInstance().getFile(document) ?: return@executeIfRunning
9799
toUriString(file)?.let { uri ->
98-
languageServer.textDocumentService.didSave(
99-
DidSaveTextDocumentParams().apply {
100-
textDocument = TextDocumentIdentifier().apply {
101-
this.uri = uri
100+
pluginAwareExecuteOnPooledThread {
101+
languageServer.textDocumentService.didSave(
102+
DidSaveTextDocumentParams().apply {
103+
textDocument = TextDocumentIdentifier().apply {
104+
this.uri = uri
105+
}
106+
text = document.text
102107
}
103-
text = document.text
104-
}
105-
)
108+
)
109+
}
106110
}
107111
}
108112
}

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandlerTest.kt

Lines changed: 63 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,28 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument
55

6-
import com.intellij.openapi.Disposable
7-
import com.intellij.openapi.components.serviceIfCreated
6+
import com.intellij.openapi.application.writeAction
87
import com.intellij.openapi.editor.Document
98
import com.intellij.openapi.fileEditor.FileDocumentManager
10-
import com.intellij.openapi.fileEditor.FileEditorManager
119
import com.intellij.openapi.fileTypes.FileType
12-
import com.intellij.openapi.project.Project
1310
import com.intellij.openapi.vfs.VirtualFile
1411
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent
1512
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
16-
import com.intellij.testFramework.ApplicationRule
13+
import com.intellij.openapi.vfs.writeText
14+
import com.intellij.testFramework.DisposableRule
1715
import com.intellij.testFramework.LightVirtualFile
18-
import com.intellij.testFramework.runInEdtAndWait
19-
import com.intellij.util.messages.MessageBus
20-
import com.intellij.util.messages.MessageBusConnection
16+
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
17+
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
18+
import com.intellij.testFramework.replaceService
2119
import io.mockk.every
22-
import io.mockk.just
2320
import io.mockk.mockk
2421
import io.mockk.mockkObject
2522
import io.mockk.mockkStatic
26-
import io.mockk.runs
2723
import io.mockk.slot
2824
import io.mockk.spyk
2925
import io.mockk.verify
3026
import kotlinx.coroutines.test.runTest
27+
import kotlinx.coroutines.withContext
3128
import org.assertj.core.api.Assertions.assertThat
3229
import org.eclipse.lsp4j.DidChangeTextDocumentParams
3330
import org.eclipse.lsp4j.DidCloseTextDocumentParams
@@ -38,37 +35,49 @@ import org.eclipse.lsp4j.services.TextDocumentService
3835
import org.junit.Before
3936
import org.junit.Rule
4037
import org.junit.Test
38+
import software.aws.toolkits.jetbrains.core.coroutines.EDT
4139
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer
4240
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
4341
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.LspEditorUtil
42+
import software.aws.toolkits.jetbrains.utils.rules.CodeInsightTestFixtureRule
43+
import software.aws.toolkits.jetbrains.utils.satisfiesKt
4444
import java.net.URI
4545
import java.nio.file.Path
4646
import java.util.concurrent.CompletableFuture
47+
import kotlin.collections.first
4748

4849
class TextDocumentServiceHandlerTest {
49-
@Rule
50-
@JvmField
51-
val application = ApplicationRule()
52-
53-
private lateinit var project: Project
54-
private lateinit var mockFileEditorManager: FileEditorManager
5550
private lateinit var mockLanguageServer: AmazonQLanguageServer
5651
private lateinit var mockTextDocumentService: TextDocumentService
5752
private lateinit var sut: TextDocumentServiceHandler
58-
// private lateinit var mockApplication: Application
53+
54+
@get:Rule
55+
val projectRule = object : CodeInsightTestFixtureRule() {
56+
override fun createTestFixture(): CodeInsightTestFixture {
57+
val fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory()
58+
val fixtureBuilder = fixtureFactory.createLightFixtureBuilder(testDescription, testName)
59+
val newFixture = fixtureFactory
60+
.createCodeInsightFixture(fixtureBuilder.fixture, fixtureFactory.createTempDirTestFixture())
61+
newFixture.setUp()
62+
newFixture.testDataPath = testDataPath
63+
64+
return newFixture
65+
}
66+
}
67+
68+
@get:Rule
69+
val disposableRule = DisposableRule()
5970

6071
@Before
6172
fun setup() {
62-
project = mockk<Project>()
6373
mockTextDocumentService = mockk<TextDocumentService>()
6474
mockLanguageServer = mockk<AmazonQLanguageServer>()
6575

6676
// Mock the LSP service
67-
val mockLspService = mockk<AmazonQLspService>()
77+
val mockLspService = mockk<AmazonQLspService>(relaxed = true)
6878

6979
// Mock the service methods on Project
70-
every { project.getService(AmazonQLspService::class.java) } returns mockLspService
71-
every { project.serviceIfCreated<AmazonQLspService>() } returns mockLspService
80+
projectRule.project.replaceService(AmazonQLspService::class.java, mockLspService, disposableRule.disposable)
7281

7382
// Mock the LSP service's executeSync method as a suspend function
7483
every {
@@ -85,19 +94,7 @@ class TextDocumentServiceHandlerTest {
8594
every { mockTextDocumentService.didOpen(any()) } returns Unit
8695
every { mockTextDocumentService.didClose(any()) } returns Unit
8796

88-
// Mock message bus
89-
val messageBus = mockk<MessageBus>()
90-
every { project.messageBus } returns messageBus
91-
val mockConnection = mockk<MessageBusConnection>()
92-
every { messageBus.connect(any<Disposable>()) } returns mockConnection
93-
every { mockConnection.subscribe(any(), any()) } just runs
94-
95-
// Mock FileEditorManager
96-
mockFileEditorManager = mockk<FileEditorManager>()
97-
every { mockFileEditorManager.openFiles } returns emptyArray()
98-
every { project.getService(FileEditorManager::class.java) } returns mockFileEditorManager
99-
100-
sut = TextDocumentServiceHandler(project, mockk())
97+
sut = TextDocumentServiceHandler(projectRule.project, mockk())
10198
}
10299

103100
@Test
@@ -135,41 +132,39 @@ class TextDocumentServiceHandlerTest {
135132

136133
@Test
137134
fun `didOpen runs on service init`() = runTest {
138-
val uri = URI.create("file:///test/path/file.txt")
139135
val content = "test content"
140-
val file = createMockVirtualFile(uri, content)
141-
142-
every { mockFileEditorManager.openFiles } returns arrayOf(file)
136+
val file = withContext(EDT) {
137+
projectRule.fixture.createFile("name", content).also { projectRule.fixture.openFileInEditor(it) }
138+
}
143139

144-
sut = TextDocumentServiceHandler(project, mockk())
140+
sut = TextDocumentServiceHandler(projectRule.project, mockk())
145141

146-
val paramsSlot = slot<DidOpenTextDocumentParams>()
142+
val paramsSlot = mutableListOf<DidOpenTextDocumentParams>()
147143
verify { mockTextDocumentService.didOpen(capture(paramsSlot)) }
148144

149-
with(paramsSlot.captured.textDocument) {
150-
assertThat(this.uri).isEqualTo(normalizeFileUri(uri.toString()))
151-
assertThat(text).isEqualTo(content)
152-
assertThat(languageId).isEqualTo("java")
153-
assertThat(version).isEqualTo(1)
145+
assertThat(paramsSlot.first().textDocument).satisfiesKt {
146+
assertThat(it.uri).isEqualTo(file.toNioPath().toUri().toString())
147+
assertThat(it.text).isEqualTo(content)
148+
assertThat(it.languageId).isEqualTo("plain_text")
154149
}
155150
}
156151

157152
@Test
158153
fun `didOpen runs on fileOpened`() = runTest {
159-
val uri = URI.create("file:///test/path/file.txt")
160154
val content = "test content"
161-
val file = createMockVirtualFile(uri, content)
155+
val file = withContext(EDT) {
156+
projectRule.fixture.createFile("name", content).also { projectRule.fixture.openFileInEditor(it) }
157+
}
162158

163159
sut.fileOpened(mockk(), file)
164160

165-
val paramsSlot = slot<DidOpenTextDocumentParams>()
161+
val paramsSlot = mutableListOf<DidOpenTextDocumentParams>()
166162
verify { mockTextDocumentService.didOpen(capture(paramsSlot)) }
167163

168-
with(paramsSlot.captured.textDocument) {
169-
assertThat(this.uri).isEqualTo(normalizeFileUri(uri.toString()))
170-
assertThat(text).isEqualTo(content)
171-
assertThat(languageId).isEqualTo("java")
172-
assertThat(version).isEqualTo(1)
164+
assertThat(paramsSlot.first().textDocument).satisfiesKt {
165+
assertThat(it.uri).isEqualTo(file.toNioPath().toUri().toString())
166+
assertThat(it.text).isEqualTo(content)
167+
assertThat(it.languageId).isEqualTo("plain_text")
173168
}
174169
}
175170

@@ -188,40 +183,23 @@ class TextDocumentServiceHandlerTest {
188183

189184
@Test
190185
fun `didChange runs on content change events`() = runTest {
191-
val uri = URI.create("file:///test/path/file.txt")
192-
val document = mockk<Document> {
193-
every { text } returns "changed content"
194-
every { modificationStamp } returns 123L
195-
}
186+
val file = withContext(EDT) {
187+
projectRule.fixture.createFile("name", "").also {
188+
projectRule.fixture.openFileInEditor(it)
196189

197-
val file = createMockVirtualFile(uri)
198-
199-
val changeEvent = mockk<VFileContentChangeEvent> {
200-
every { this@mockk.file } returns file
201-
}
202-
203-
// Mock FileDocumentManager
204-
val fileDocumentManager = mockk<FileDocumentManager> {
205-
every { getCachedDocument(file) } returns document
206-
}
207-
208-
mockkStatic(FileDocumentManager::class) {
209-
every { FileDocumentManager.getInstance() } returns fileDocumentManager
210-
211-
// Call the handler method
212-
runInEdtAndWait {
213-
sut.after(mutableListOf(changeEvent))
190+
writeAction {
191+
it.writeText("changed content")
192+
}
214193
}
215194
}
216195

217196
// Verify the correct LSP method was called with matching parameters
218-
val paramsSlot = slot<DidChangeTextDocumentParams>()
197+
val paramsSlot = mutableListOf<DidChangeTextDocumentParams>()
219198
verify { mockTextDocumentService.didChange(capture(paramsSlot)) }
220199

221-
with(paramsSlot.captured) {
222-
assertThat(textDocument.uri).isEqualTo(normalizeFileUri(uri.toString()))
223-
assertThat(textDocument.version).isEqualTo(123)
224-
assertThat(contentChanges[0].text).isEqualTo("changed content")
200+
assertThat(paramsSlot.first()).satisfiesKt {
201+
assertThat(it.textDocument.uri).isEqualTo(file.toNioPath().toUri().toString())
202+
assertThat(it.contentChanges[0].text).isEqualTo("changed content")
225203
}
226204
}
227205

@@ -337,6 +315,11 @@ class TextDocumentServiceHandlerTest {
337315
return uri
338316
}
339317

318+
if (uri.startsWith("file://C:/")) {
319+
val path = uri.substringAfter("file://C:/")
320+
return "file:///C:/$path"
321+
}
322+
340323
val path = uri.substringAfter("file:///")
341324
return "file:///C:/$path"
342325
}

0 commit comments

Comments
 (0)