Skip to content

Commit c9b292f

Browse files
authored
Merge branch 'main' into cwspr-inline-lang
2 parents 9b80fd5 + 90fda64 commit c9b292f

File tree

8 files changed

+111
-125
lines changed

8 files changed

+111
-125
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 error occuring when Amazon Q attempts to show UI hints on manually triggerred inline suggestion (#4929)"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Loosen inline completion support limitations for YAML/JSON"
4+
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import com.intellij.openapi.util.TextRange
1212
import com.intellij.openapi.vfs.VfsUtilCore
1313
import com.intellij.psi.PsiFile
1414
import com.intellij.ui.popup.AbstractPopup
15-
import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJson
16-
import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererYaml
1715
import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage
1816
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretContext
1917
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
2018
import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo
2119
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
20+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.AWSTemplateCaseInsensitiveKeyWordsRegex
21+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.AWSTemplateKeyWordsRegex
22+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.JsonConfigFileNamingConvention
2223
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.LEFT_CONTEXT_ON_CURRENT_LINE
2324
import java.awt.Point
2425
import java.util.Locale
@@ -106,16 +107,12 @@ object CodeWhispererEditorUtil {
106107
}
107108

108109
/**
109-
* Checks if the language is json or yaml and checks if left context contains keywords
110+
* Check if left context contains keywords or file name follow config json file naming pattern
110111
*/
111-
fun checkLeftContextKeywordsForJsonAndYaml(leftContext: String, language: String): Boolean = (
112-
(language == CodeWhispererJson.INSTANCE.languageId) ||
113-
(language == CodeWhispererYaml.INSTANCE.languageId)
114-
) &&
115-
(
116-
(!CodeWhispererConstants.AWSTemplateKeyWordsRegex.containsMatchIn(leftContext)) &&
117-
(!CodeWhispererConstants.AWSTemplateCaseInsensitiveKeyWordsRegex.containsMatchIn(leftContext.lowercase(Locale.getDefault())))
118-
)
112+
fun isSupportedJsonFormat(fileName: String, leftContext: String): Boolean =
113+
JsonConfigFileNamingConvention.contains(fileName.lowercase()) ||
114+
AWSTemplateKeyWordsRegex.containsMatchIn(leftContext) ||
115+
AWSTemplateCaseInsensitiveKeyWordsRegex.containsMatchIn(leftContext.lowercase(Locale.getDefault()))
119116

120117
/**
121118
* Checks if the [otherRange] overlaps this TextRange. Note that the comparison is `<` because the endOffset of TextRange is exclusive.

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererCon
5555
import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor
5656
import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator
5757
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager
58-
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.checkLeftContextKeywordsForJsonAndYaml
5958
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getCaretPosition
59+
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.isSupportedJsonFormat
6060
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
6161
import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled
62+
import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJson
6263
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition
6364
import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext
6465
import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo
@@ -171,7 +172,13 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
171172

172173
val language = requestContext.fileContextInfo.programmingLanguage
173174
val leftContext = requestContext.fileContextInfo.caretContext.leftFileContext
174-
if (!language.isCodeCompletionSupported() || (checkLeftContextKeywordsForJsonAndYaml(leftContext, language.languageId))) {
175+
if (!language.isCodeCompletionSupported() || (
176+
language is CodeWhispererJson && !isSupportedJsonFormat(
177+
requestContext.fileContextInfo.filename,
178+
leftContext
179+
)
180+
)
181+
) {
175182
LOG.debug { "Programming language $language is not supported by CodeWhisperer" }
176183
if (triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
177184
showCodeWhispererInfoHint(
@@ -409,10 +416,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
409416
if (requestContext.triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) {
410417
// We should only show error hint when CodeWhisperer popup is not visible,
411418
// and make it silent if CodeWhisperer popup is showing.
412-
runInEdt {
413-
if (!CodeWhispererInvocationStatus.getInstance().isPopupActive()) {
414-
showCodeWhispererErrorHint(requestContext.editor, displayMessage)
415-
}
419+
if (!CodeWhispererInvocationStatus.getInstance().isPopupActive()) {
420+
showCodeWhispererErrorHint(requestContext.editor, displayMessage)
416421
}
417422
}
418423
}
@@ -754,12 +759,16 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
754759
return true
755760
}
756761

757-
fun showCodeWhispererInfoHint(editor: Editor, message: String) {
758-
HintManager.getInstance().showInformationHint(editor, message, HintManager.UNDER)
762+
private fun showCodeWhispererInfoHint(editor: Editor, message: String) {
763+
runInEdt {
764+
HintManager.getInstance().showInformationHint(editor, message, HintManager.UNDER)
765+
}
759766
}
760767

761-
fun showCodeWhispererErrorHint(editor: Editor, message: String) {
762-
HintManager.getInstance().showErrorHint(editor, message, HintManager.UNDER)
768+
private fun showCodeWhispererErrorHint(editor: Editor, message: String) {
769+
runInEdt {
770+
HintManager.getInstance().showErrorHint(editor, message, HintManager.UNDER)
771+
}
763772
}
764773

765774
override fun dispose() {}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ object CodeWhispererConstants {
3636
val AWSTemplateKeyWordsRegex = Regex("(AWSTemplateFormatVersion|Resources|AWS::|Description)")
3737
val AWSTemplateCaseInsensitiveKeyWordsRegex = Regex("(cloudformation|cfn|template|description)")
3838

39+
val JsonConfigFileNamingConvention = setOf(
40+
"app.json",
41+
"appsettings.json",
42+
"bower.json",
43+
"composer.json",
44+
"db.json",
45+
"manifest.json",
46+
"package.json",
47+
"schema.json",
48+
"settings.json",
49+
"tsconfig.json",
50+
"vcpkg.json"
51+
)
52+
3953
// TODO: this is currently set to 2050 to account for the server side 0.5 TPS and and extra 50 ms buffer to
4054
// avoid ThrottlingException as much as possible.
4155
const val INVOCATION_INTERVAL: Long = 2050
@@ -131,9 +145,10 @@ object CodeWhispererConstants {
131145
}
132146
}
133147
}
148+
134149
object CrossFile {
135150
const val CHUNK_SIZE = 60
136-
const val NUMBER_OF_LINE_IN_CHUNK = 10
151+
const val NUMBER_OF_LINE_IN_CHUNK = 50
137152
const val NUMBER_OF_CHUNK_TO_FETCH = 3
138153
}
139154

@@ -145,7 +160,7 @@ object CodeWhispererConstants {
145160
object TryExampleFileContent {
146161

147162
private const val AUTO_TRIGGER_CONTENT_JAVA =
148-
"""import java.util.ArrayList;
163+
"""import java.util.ArrayList;
149164
import java.util.HashMap;
150165
import java.util.List;
151166
import java.util.Map;
@@ -167,7 +182,7 @@ public class Main {
167182
}"""
168183

169184
private const val MANUAL_TRIGGER_CONTENT_JAVA =
170-
"""// TODO: Press either Option + C on MacOS or Alt + C on Windows on a new line.
185+
"""// TODO: Press either Option + C on MacOS or Alt + C on Windows on a new line.
171186
172187
public class S3Uploader {
173188
@@ -178,7 +193,7 @@ public class S3Uploader {
178193
}"""
179194

180195
private const val UNIT_TEST_CONTENT_JAVA =
181-
"""// TODO: Ask Amazon Q to write unit tests.
196+
"""// TODO: Ask Amazon Q to write unit tests.
182197
183198
// Write a test case for the sum function.
184199

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import org.assertj.core.api.Assertions.assertThat
1111
import org.junit.Before
1212
import org.junit.Rule
1313
import org.junit.Test
14+
import org.junit.jupiter.params.ParameterizedTest
15+
import org.junit.jupiter.params.provider.ValueSource
1416
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.leftContext_success_Iac
1517
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName
1618
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext
17-
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.yaml_langauge
1819
import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil
1920
import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython
2021
import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule
@@ -76,12 +77,36 @@ class CodeWhispererEditorUtilTest {
7677
assertThat(caretContext.leftContextOnCurrentLine).isEqualTo(pythonTestLeftContext)
7778
}
7879

79-
/**
80-
* Test for keyword checks for json and yaml
81-
*/
8280
@Test
83-
fun `test for keyword check for json and yaml`() {
84-
val result = CodeWhispererEditorUtil.checkLeftContextKeywordsForJsonAndYaml(leftContext_success_Iac, yaml_langauge)
81+
fun `test for keyword check for json`() {
82+
val result = CodeWhispererEditorUtil.isSupportedJsonFormat("foo.json", leftContext_success_Iac)
83+
assertThat(result).isEqualTo(true)
84+
}
85+
86+
@ParameterizedTest
87+
@ValueSource(
88+
strings = [
89+
"app.json",
90+
"appsettings.json",
91+
"bower.json",
92+
"composer.json",
93+
"db.json",
94+
"manifest.json",
95+
"package.json",
96+
"schema.json",
97+
"settings.json",
98+
"tsconfig.json",
99+
"vcpkg.json"
100+
]
101+
)
102+
fun `isSupportedJsonFormat should return true by file name`(fileName: String) {
103+
val result = CodeWhispererEditorUtil.isSupportedJsonFormat(fileName, "")
104+
assertThat(result).isEqualTo(true)
105+
}
106+
107+
@Test
108+
fun `isSupportedJsonFormat should return false due to no match`() {
109+
val result = CodeWhispererEditorUtil.isSupportedJsonFormat("foo.json", "")
85110
assertThat(result).isEqualTo(false)
86111
}
87112
}

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererUtilTest.kt

Lines changed: 20 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.junit.Test
1414
import software.amazon.awssdk.regions.Region
1515
import software.amazon.awssdk.services.codewhispererruntime.model.OptOutPreference
1616
import software.amazon.awssdk.services.ssooidc.SsoOidcClient
17+
import software.aws.toolkits.core.utils.test.aStringWithLineCount
1718
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
1819
import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection
1920
import software.aws.toolkits.jetbrains.core.credentials.sono.Q_SCOPES
@@ -119,83 +120,45 @@ class CodeWhispererUtilTest {
119120

120121
@Test
121122
fun `toCodeChunk case_2`() {
122-
val psiFile = fixture.configureByText("Sample.java", codeSample33Lines)
123+
val fakeCodeWith210Lines = aStringWithLineCount(210)
124+
val psiFile = fixture.configureByText("Sample.java", fakeCodeWith210Lines)
123125

124126
val result = runBlocking {
125127
psiFile.virtualFile.toCodeChunk("fake/path")
126128
}.toList()
127129

128-
assertThat(result).hasSize(5)
130+
// 210 / 50 + 2
131+
assertThat(result).hasSize(6)
129132

130133
// 0th
131-
assertThat(result[0].content).isEqualTo(
132-
"""public int runBinarySearchRecursively(int[] sortedArray, int key, int low, int high) {
133-
| int middle = low + ((high - low) / 2);
134-
""".trimMargin()
135-
)
134+
assertThat(result[0].content).isEqualTo(aStringWithLineCount(3))
136135
assertThat(result[0].path).isEqualTo("fake/path")
137-
assertThat(result[0].nextChunk).isEqualTo(result[1].content)
136+
assertThat(result[0].nextChunk).isEqualTo(aStringWithLineCount(50, start = 0))
138137

139138
// 1st
140-
assertThat(result[1].content).isEqualTo(
141-
"""|public int runBinarySearchRecursively(int[] sortedArray, int key, int low, int high) {
142-
| int middle = low + ((high - low) / 2);
143-
|
144-
| if (high < low) {
145-
| return -1;
146-
| }
147-
|
148-
| if (key == sortedArray[middle]) {
149-
| return middle;
150-
| } else if (key < sortedArray[middle]) {
151-
""".trimMargin()
152-
)
139+
assertThat(result[1].content).isEqualTo(aStringWithLineCount(50, start = 0))
153140
assertThat(result[1].path).isEqualTo("fake/path")
154-
assertThat(result[1].nextChunk).isEqualTo(result[2].content)
141+
assertThat(result[1].nextChunk).isEqualTo(aStringWithLineCount(50, start = 50))
155142

156143
// 2nd
157-
assertThat(result[2].content).isEqualTo(
158-
"""| return runBinarySearchRecursively(sortedArray, key, low, middle - 1);
159-
| } else {
160-
| return runBinarySearchRecursively(sortedArray, key, middle + 1, high);
161-
| }
162-
|}
163-
|
164-
|public int runBinarySearchIteratively(int[] sortedArray, int key, int low, int high) {
165-
| int index = Integer.MAX_VALUE;
166-
|
167-
| while (low <= high) {
168-
""".trimMargin()
169-
)
144+
assertThat(result[2].content).isEqualTo(aStringWithLineCount(50, start = 50))
170145
assertThat(result[2].path).isEqualTo("fake/path")
171-
assertThat(result[2].nextChunk).isEqualTo(result[3].content)
146+
assertThat(result[2].nextChunk).isEqualTo(aStringWithLineCount(50, start = 100))
172147

173148
// 3rd
174-
assertThat(result[3].content).isEqualTo(
175-
"""| int mid = low + ((high - low) / 2);
176-
| if (sortedArray[mid] < key) {
177-
| low = mid + 1;
178-
| } else if (sortedArray[mid] > key) {
179-
| high = mid - 1;
180-
| } else if (sortedArray[mid] == key) {
181-
| index = mid;
182-
| break;
183-
| }
184-
| }
185-
""".trimMargin()
186-
)
149+
assertThat(result[3].content).isEqualTo(aStringWithLineCount(50, start = 100))
187150
assertThat(result[3].path).isEqualTo("fake/path")
188-
assertThat(result[3].nextChunk).isEqualTo(result[4].content)
151+
assertThat(result[3].nextChunk).isEqualTo(aStringWithLineCount(50, start = 150))
189152

190153
// 4th
191-
assertThat(result[4].content).isEqualTo(
192-
"""|
193-
| return index;
194-
|}
195-
""".trimMargin()
196-
)
154+
assertThat(result[4].content).isEqualTo(aStringWithLineCount(50, start = 150))
197155
assertThat(result[4].path).isEqualTo("fake/path")
198-
assertThat(result[4].nextChunk).isEqualTo(result[4].content)
156+
assertThat(result[4].nextChunk).isEqualTo(aStringWithLineCount(10, start = 200))
157+
158+
// 5th
159+
assertThat(result[5].content).isEqualTo(aStringWithLineCount(10, start = 200))
160+
assertThat(result[5].path).isEqualTo("fake/path")
161+
assertThat(result[5].nextChunk).isEqualTo(aStringWithLineCount(10, start = 200))
199162
}
200163

201164
@Test
@@ -230,40 +193,3 @@ class CodeWhispererUtilTest {
230193
assertThat(getTelemetryOptOutPreference()).isEqualTo(OptOutPreference.OPTOUT)
231194
}
232195
}
233-
234-
private val codeSample33Lines =
235-
"""public int runBinarySearchRecursively(int[] sortedArray, int key, int low, int high) {
236-
| int middle = low + ((high - low) / 2);
237-
|
238-
| if (high < low) {
239-
| return -1;
240-
| }
241-
|
242-
| if (key == sortedArray[middle]) {
243-
| return middle;
244-
| } else if (key < sortedArray[middle]) {
245-
| return runBinarySearchRecursively(sortedArray, key, low, middle - 1);
246-
| } else {
247-
| return runBinarySearchRecursively(sortedArray, key, middle + 1, high);
248-
| }
249-
|}
250-
|
251-
|public int runBinarySearchIteratively(int[] sortedArray, int key, int low, int high) {
252-
| int index = Integer.MAX_VALUE;
253-
|
254-
| while (low <= high) {
255-
| int mid = low + ((high - low) / 2);
256-
| if (sortedArray[mid] < key) {
257-
| low = mid + 1;
258-
| } else if (sortedArray[mid] > key) {
259-
| high = mid - 1;
260-
| } else if (sortedArray[mid] == key) {
261-
| index = mid;
262-
| break;
263-
| }
264-
| }
265-
|
266-
| return index;
267-
|}
268-
|
269-
""".trimMargin()

plugins/core/core/tst/software/aws/toolkits/core/utils/test/TestUtils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import kotlin.random.Random
1111

1212
fun aString(length: Int = Random.nextInt(5, 30)): String = UUID.randomUUID().toString().substring(length)
1313

14+
fun aStringWithLineCount(lineCount: Int, start: Int = 0): String = buildString {
15+
for (i in start until start + lineCount) {
16+
append("line$i\n")
17+
}
18+
}.trimEnd()
19+
1420
fun retryableAssert(
1521
timeout: Duration? = null,
1622
maxAttempts: Int? = null,

0 commit comments

Comments
 (0)