diff --git a/.changes/next-release/bugfix-caf370b9-c943-4ece-a4c0-81bcefa7a4f5.json b/.changes/next-release/bugfix-caf370b9-c943-4ece-a4c0-81bcefa7a4f5.json new file mode 100644 index 00000000000..e09b9b9d4f2 --- /dev/null +++ b/.changes/next-release/bugfix-caf370b9-c943-4ece-a4c0-81bcefa7a4f5.json @@ -0,0 +1,4 @@ +{ + "type" : "bugfix", + "description" : "Amazon Q Feature Dev: Add error messages when the upload URL expires" +} \ No newline at end of file diff --git a/.changes/next-release/bugfix-e43f051f-5a66-4a46-ac6f-3f351f76cf6e.json b/.changes/next-release/bugfix-e43f051f-5a66-4a46-ac6f-3f351f76cf6e.json new file mode 100644 index 00000000000..b64f7db646a --- /dev/null +++ b/.changes/next-release/bugfix-e43f051f-5a66-4a46-ac6f-3f351f76cf6e.json @@ -0,0 +1,4 @@ +{ + "type" : "bugfix", + "description" : "Fix inability to sign out in reauth view in Q chat panel" +} \ No newline at end of file diff --git a/.changes/next-release/feature-fd316552-7161-43bf-aaa5-e1ac1330b939.json b/.changes/next-release/feature-fd316552-7161-43bf-aaa5-e1ac1330b939.json new file mode 100644 index 00000000000..0a086fe911b --- /dev/null +++ b/.changes/next-release/feature-fd316552-7161-43bf-aaa5-e1ac1330b939.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Loosen inline completion support limitations for YAML/JSON" +} \ No newline at end of file diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt index 7952179c9b6..6af627f0279 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QLoginWebview.kt @@ -6,6 +6,7 @@ package software.aws.toolkits.jetbrains.services.amazonq import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.application.runInEdt import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project @@ -177,13 +178,15 @@ class QWebviewBrowser(val project: Project, private val parentDisposable: Dispos ToolkitConnectionManager.getInstance(project) .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection )?.let { connection -> - SsoLogoutAction(connection).actionPerformed( - AnActionEvent.createFromDataContext( - "qBrowser", - null, - DataContext.EMPTY_CONTEXT + runInEdt { + SsoLogoutAction(connection).actionPerformed( + AnActionEvent.createFromDataContext( + "qBrowser", + null, + DataContext.EMPTY_CONTEXT + ) ) - ) + } } } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt index c23c36c5bf2..3cdbcf1c04f 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt @@ -16,6 +16,13 @@ class CodeIterationLimitError(override val message: String, override val cause: class MonthlyConversationLimitError(override val message: String, override val cause: Throwable?) : RuntimeException() +class UploadURLExpired( + override val message: String = message( + "amazonqFeatureDev.exception.upload_url_expiry" + ), + override val cause: Throwable? = null, +) : FeatureDevException(message, cause) + internal fun featureDevServiceError(message: String?): Nothing = throw FeatureDevException(message) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt index 89f90bd2265..f2c37017c45 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt @@ -36,6 +36,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevExce import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.InboundAppMessagesHandler import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ModifySourceFolderErrorReason import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadURLExpired import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileError import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.createUserFacingErrorMessage import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.denyListedErrors @@ -444,6 +445,12 @@ class FeatureDevController( messenger.sendMonthlyLimitError(tabId = tabId) messenger.sendChatInputEnabledMessage(tabId, enabled = false) } + is UploadURLExpired -> messenger.sendAnswer( + tabId = tabId, + message = err.message, + messageType = FeatureDevMessageType.Answer, + canBeVoted = true + ) is FeatureDevException -> { messenger.sendError( tabId = tabId, @@ -471,7 +478,6 @@ class FeatureDevController( ), ) } - else -> { var msg = createUserFacingErrorMessage("$FEATURE_NAME request failed: ${err.message ?: err.cause?.message}") val isDenyListedError = denyListedErrors.any { msg?.contains(it) ?: false } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt index 0d88929ee33..66177d5a21f 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt @@ -14,6 +14,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.CONTENT_SHA256 import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadURLExpired import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.clients.FeatureDevClient import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.uploadCodeError import java.io.File @@ -36,9 +37,13 @@ fun uploadArtifactToS3(url: String, fileToUpload: File, checksumSha256: String, connection.setFixedLengthStreamingMode(fileToUpload.length()) IoUtils.copy(fileToUpload.inputStream(), connection.outputStream) } - } catch (err: Exception) { + } catch (err: HttpRequests.HttpStatusException) { logger.warn(err) { "$FEATURE_NAME: Failed to upload code to S3" } - uploadCodeError() + + when (err.statusCode) { + 403 -> throw UploadURLExpired() + else -> uploadCodeError() + } } } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt index 3d921ffec1d..ccb6434fd21 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/controller/chat/messenger/ChatPromptHandler.kt @@ -58,7 +58,14 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) { .onStart { // The first thing we always send back is an AnswerStream message to indicate the beginning of a streaming answer val response = - ChatMessage(tabId = tabId, triggerId = triggerId, messageId = requestId, messageType = ChatMessageType.AnswerStream, message = "") + ChatMessage( + tabId = tabId, + triggerId = triggerId, + messageId = requestId, + messageType = ChatMessageType.AnswerStream, + message = "", + userIntent = data.userIntent, + ) telemetryHelper.setResponseStreamStartTime(tabId) emit(response) @@ -81,13 +88,20 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) { messageType = ChatMessageType.AnswerPart, message = responseText.toString(), relatedSuggestions = relatedSuggestions, + userIntent = data.userIntent, ) emit(suggestionMessage) } // Send the Answer message to indicate the end of the response stream - val response = - ChatMessage(tabId = tabId, triggerId = triggerId, messageId = requestId, messageType = ChatMessageType.Answer, followUps = followUps) + val response = ChatMessage( + tabId = tabId, + triggerId = triggerId, + messageId = requestId, + messageType = ChatMessageType.Answer, + followUps = followUps, + userIntent = data.userIntent, + ) telemetryHelper.setResponseStreamTotalTime(tabId) telemetryHelper.setResponseHasProjectContext( @@ -119,11 +133,23 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) { } } .collect { responseEvent -> - processChatEvent(tabId, triggerId, responseEvent, shouldAddIndexInProgressMessage)?.let { emit(it) } + processChatEvent( + tabId, + triggerId, + data, + responseEvent, + shouldAddIndexInProgressMessage + )?.let { emit(it) } } } - private fun processChatEvent(tabId: String, triggerId: String, event: ChatResponseEvent, shouldAddIndexInProgressMessage: Boolean): ChatMessage? { + private fun processChatEvent( + tabId: String, + triggerId: String, + data: ChatRequestData, + event: ChatResponseEvent, + shouldAddIndexInProgressMessage: Boolean, + ): ChatMessage? { requestId = event.requestId statusCode = event.statusCode @@ -190,6 +216,7 @@ class ChatPromptHandler(private val telemetryHelper: TelemetryHelper) { messageType = ChatMessageType.AnswerPart, message = message, codeReference = codeReferences, + userIntent = data.userIntent, ) } else { null diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt index 599b050d272..06193bdc4ac 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/messages/CwcMessage.kt @@ -213,6 +213,7 @@ data class ChatMessage( val followUpsHeader: String? = null, val relatedSuggestions: List? = null, val codeReference: List? = null, + val userIntent: UserIntent? = null, ) : UiMessage( tabId = tabId, type = "chatMessage", diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt index e699e97e856..a2fa515d2fe 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorUtil.kt @@ -12,13 +12,14 @@ import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.psi.PsiFile import com.intellij.ui.popup.AbstractPopup -import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJson -import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererYaml import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.AWSTemplateCaseInsensitiveKeyWordsRegex +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.AWSTemplateKeyWordsRegex +import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.JsonConfigFileNamingConvention import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.LEFT_CONTEXT_ON_CURRENT_LINE import java.awt.Point import java.util.Locale @@ -106,16 +107,12 @@ object CodeWhispererEditorUtil { } /** - * Checks if the language is json or yaml and checks if left context contains keywords + * Check if left context contains keywords or file name follow config json file naming pattern */ - fun checkLeftContextKeywordsForJsonAndYaml(leftContext: String, language: String): Boolean = ( - (language == CodeWhispererJson.INSTANCE.languageId) || - (language == CodeWhispererYaml.INSTANCE.languageId) - ) && - ( - (!CodeWhispererConstants.AWSTemplateKeyWordsRegex.containsMatchIn(leftContext)) && - (!CodeWhispererConstants.AWSTemplateCaseInsensitiveKeyWordsRegex.containsMatchIn(leftContext.lowercase(Locale.getDefault()))) - ) + fun isSupportedJsonFormat(fileName: String, leftContext: String): Boolean = + JsonConfigFileNamingConvention.contains(fileName.lowercase()) || + AWSTemplateKeyWordsRegex.containsMatchIn(leftContext) || + AWSTemplateCaseInsensitiveKeyWordsRegex.containsMatchIn(leftContext.lowercase(Locale.getDefault())) /** * Checks if the [otherRange] overlaps this TextRange. Note that the comparison is `<` because the endOffset of TextRange is exclusive. diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/CodeWhispererLanguageManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/CodeWhispererLanguageManager.kt index 1ef98cf119d..aae1ac995e6 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/CodeWhispererLanguageManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/CodeWhispererLanguageManager.kt @@ -10,24 +10,31 @@ import com.intellij.psi.PsiFile import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererC import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererCpp import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererCsharp +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererDart import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererGo import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJava import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJavaScript import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJson import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJsx import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererKotlin +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererLua import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPhp import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPlainText +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPowershell import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererR import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererRuby import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererRust import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererScala import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererShell import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererSql +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererSwift +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererSystemVerilog import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererTf import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererTsx import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererTypeScript import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererVue import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererYaml @Service @@ -73,8 +80,15 @@ class CodeWhispererLanguageManager { fileTypeName.contains("php") -> CodeWhispererPhp.INSTANCE fileTypeName.contains("sql") -> CodeWhispererSql.INSTANCE fileTypeName.contains("go") -> CodeWhispererGo.INSTANCE - fileTypeName.contains("shell") -> CodeWhispererShell.INSTANCE fileTypeName.contains("rust") -> CodeWhispererRust.INSTANCE + fileTypeName.contains("swift") -> CodeWhispererSwift.INSTANCE + fileTypeName.contains("lua") -> CodeWhispererLua.INSTANCE + fileTypeName.contains("dart") -> CodeWhispererDart.INSTANCE + fileTypeName.contains("vue") -> CodeWhispererVue.INSTANCE + fileTypeName.contains("systemverilog") -> CodeWhispererSystemVerilog.INSTANCE + fileTypeName.contains("powershell") -> CodeWhispererPowershell.INSTANCE + fileTypeName.contains("shell") -> CodeWhispererShell.INSTANCE + fileTypeName == "r" -> CodeWhispererR.INSTANCE // fileTypeName.contains("plain_text") -> CodeWhispererPlainText.INSTANCE // This needs to be removed because Hcl files are recognised as plain_text by JB else -> null } @@ -125,7 +139,14 @@ class CodeWhispererLanguageManager { listOf("go") to CodeWhispererGo.INSTANCE, listOf("php") to CodeWhispererPhp.INSTANCE, listOf("sql") to CodeWhispererSql.INSTANCE, - listOf("txt") to CodeWhispererPlainText.INSTANCE + listOf("txt") to CodeWhispererPlainText.INSTANCE, + listOf("sv", "svh", "vh") to CodeWhispererSystemVerilog.INSTANCE, + listOf("dart") to CodeWhispererDart.INSTANCE, + listOf("lua", "wlua") to CodeWhispererLua.INSTANCE, + listOf("swift") to CodeWhispererSwift.INSTANCE, + listOf("vue") to CodeWhispererVue.INSTANCE, + listOf("ps1", "psm1") to CodeWhispererPowershell.INSTANCE, + listOf("r") to CodeWhispererR.INSTANCE, ).map { val exts = it.first val lang = it.second diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererDart.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererDart.kt new file mode 100644 index 00000000000..7ba4d022cb3 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererDart.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererDart private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.Dart + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "dart" + + val INSTANCE = CodeWhispererDart() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererLua.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererLua.kt new file mode 100644 index 00000000000..5b2f39d73e7 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererLua.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererLua private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.Lua + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "lua" + + val INSTANCE = CodeWhispererLua() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPowershell.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPowershell.kt new file mode 100644 index 00000000000..e74e7153c45 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererPowershell.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererPowershell private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.Powershell + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "powershell" + + val INSTANCE = CodeWhispererPowershell() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererR.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererR.kt new file mode 100644 index 00000000000..370abe8d84a --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererR.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererR private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.R + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "r" + + val INSTANCE = CodeWhispererR() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSwift.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSwift.kt new file mode 100644 index 00000000000..36513f8908e --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSwift.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererSwift private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.Swift + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "swift" + + val INSTANCE = CodeWhispererSwift() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSystemVerilog.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSystemVerilog.kt new file mode 100644 index 00000000000..4f445ebffad --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererSystemVerilog.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererSystemVerilog private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.SystemVerilog + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "systemverilog" + + val INSTANCE = CodeWhispererSystemVerilog() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererVue.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererVue.kt new file mode 100644 index 00000000000..a2345178fe2 --- /dev/null +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/language/languages/CodeWhispererVue.kt @@ -0,0 +1,22 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.language.languages + +import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage +import software.aws.toolkits.telemetry.CodewhispererLanguage + +class CodeWhispererVue private constructor() : CodeWhispererProgrammingLanguage() { + override val languageId: String = ID + + override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.Vue + + override fun isCodeCompletionSupported(): Boolean = true + + companion object { + // TODO: confirm with service team language id + const val ID = "vue" + + val INSTANCE = CodeWhispererVue() + } +} diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt index e9d1cf26527..bdfb45cd57c 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt @@ -55,10 +55,11 @@ import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererCon import software.aws.toolkits.jetbrains.services.codewhisperer.credentials.CodeWhispererClientAdaptor import software.aws.toolkits.jetbrains.services.codewhisperer.customization.CodeWhispererModelConfigurator import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager -import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.checkLeftContextKeywordsForJsonAndYaml import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.getCaretPosition +import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil.isSupportedJsonFormat import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.isCodeWhispererEnabled +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJson import software.aws.toolkits.jetbrains.services.codewhisperer.model.CaretPosition import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext import software.aws.toolkits.jetbrains.services.codewhisperer.model.FileContextInfo @@ -171,7 +172,13 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable { val language = requestContext.fileContextInfo.programmingLanguage val leftContext = requestContext.fileContextInfo.caretContext.leftFileContext - if (!language.isCodeCompletionSupported() || (checkLeftContextKeywordsForJsonAndYaml(leftContext, language.languageId))) { + if (!language.isCodeCompletionSupported() || ( + language is CodeWhispererJson && !isSupportedJsonFormat( + requestContext.fileContextInfo.filename, + leftContext + ) + ) + ) { LOG.debug { "Programming language $language is not supported by CodeWhisperer" } if (triggerTypeInfo.triggerType == CodewhispererTriggerType.OnDemand) { showCodeWhispererInfoHint( diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt index 0b7259e3f2e..ba1a3ba6851 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererConstants.kt @@ -36,6 +36,20 @@ object CodeWhispererConstants { val AWSTemplateKeyWordsRegex = Regex("(AWSTemplateFormatVersion|Resources|AWS::|Description)") val AWSTemplateCaseInsensitiveKeyWordsRegex = Regex("(cloudformation|cfn|template|description)") + val JsonConfigFileNamingConvention = setOf( + "app.json", + "appsettings.json", + "bower.json", + "composer.json", + "db.json", + "manifest.json", + "package.json", + "schema.json", + "settings.json", + "tsconfig.json", + "vcpkg.json" + ) + // TODO: this is currently set to 2050 to account for the server side 0.5 TPS and and extra 50 ms buffer to // avoid ThrottlingException as much as possible. const val INVOCATION_INTERVAL: Long = 2050 @@ -131,6 +145,7 @@ object CodeWhispererConstants { } } } + object CrossFile { const val CHUNK_SIZE = 60 const val NUMBER_OF_LINE_IN_CHUNK = 50 @@ -145,7 +160,7 @@ object CodeWhispererConstants { object TryExampleFileContent { private const val AUTO_TRIGGER_CONTENT_JAVA = -"""import java.util.ArrayList; + """import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -167,7 +182,7 @@ public class Main { }""" private const val MANUAL_TRIGGER_CONTENT_JAVA = -"""// TODO: Press either Option + C on MacOS or Alt + C on Windows on a new line. + """// TODO: Press either Option + C on MacOS or Alt + C on Windows on a new line. public class S3Uploader { @@ -178,7 +193,7 @@ public class S3Uploader { }""" private const val UNIT_TEST_CONTENT_JAVA = -"""// TODO: Ask Amazon Q to write unit tests. + """// TODO: Ask Amazon Q to write unit tests. // Write a test case for the sum function. diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt index c2098b61329..84332655d74 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererEditorUtilTest.kt @@ -11,10 +11,11 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.leftContext_success_Iac import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonFileName import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonTestLeftContext -import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.yaml_langauge import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorUtil import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule @@ -76,12 +77,36 @@ class CodeWhispererEditorUtilTest { assertThat(caretContext.leftContextOnCurrentLine).isEqualTo(pythonTestLeftContext) } - /** - * Test for keyword checks for json and yaml - */ @Test - fun `test for keyword check for json and yaml`() { - val result = CodeWhispererEditorUtil.checkLeftContextKeywordsForJsonAndYaml(leftContext_success_Iac, yaml_langauge) + fun `test for keyword check for json`() { + val result = CodeWhispererEditorUtil.isSupportedJsonFormat("foo.json", leftContext_success_Iac) + assertThat(result).isEqualTo(true) + } + + @ParameterizedTest + @ValueSource( + strings = [ + "app.json", + "appsettings.json", + "bower.json", + "composer.json", + "db.json", + "manifest.json", + "package.json", + "schema.json", + "settings.json", + "tsconfig.json", + "vcpkg.json" + ] + ) + fun `isSupportedJsonFormat should return true by file name`(fileName: String) { + val result = CodeWhispererEditorUtil.isSupportedJsonFormat(fileName, "") + assertThat(result).isEqualTo(true) + } + + @Test + fun `isSupportedJsonFormat should return false due to no match`() { + val result = CodeWhispererEditorUtil.isSupportedJsonFormat("foo.json", "") assertThat(result).isEqualTo(false) } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt index 333d30bd0d8..f03c31e3f55 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererLanguageManagerTest.kt @@ -19,21 +19,32 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhisp import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererC import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererCpp import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererCsharp +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererDart import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererGo import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJava import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJavaScript +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJson import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererJsx import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererKotlin +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererLua import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPhp import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPlainText +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPowershell import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererPython +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererR import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererRuby import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererRust import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererScala import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererShell import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererSql +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererSwift +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererSystemVerilog +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererTf import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererTsx import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererTypeScript +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererVue +import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererYaml import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule import software.aws.toolkits.telemetry.CodewhispererLanguage import kotlin.reflect.full.createInstance @@ -70,26 +81,97 @@ class CodeWhispererLanguageManagerTest { assertThat(lang1).isSameAs(lang2) } + @Test + fun `test getProgrammingLanguage(virtualFile) by fileType`() { + testGetProgrammingLanguageUtil(fileTypeNames = listOf("java", "Java", "JAVA"), fileExtensions = listOf("")) + testGetProgrammingLanguageUtil(listOf("python", "Python"), listOf("")) + testGetProgrammingLanguageUtil(listOf("javascript", "JavaScript"), listOf("")) + testGetProgrammingLanguageUtil(listOf("jsx harmony"), listOf("")) + testGetProgrammingLanguageUtil(listOf("typescript jsx"), listOf("tsx")) + testGetProgrammingLanguageUtil(listOf("typescript", "TypeScript"), listOf("")) + testGetProgrammingLanguageUtil(listOf("c#", "C#"), listOf("")) + testGetProgrammingLanguageUtil(listOf("go", "Go"), listOf("")) + testGetProgrammingLanguageUtil(listOf("kotlin", "Kotlin"), listOf("")) + testGetProgrammingLanguageUtil(listOf("php", "Php"), listOf("")) + testGetProgrammingLanguageUtil(listOf("ruby", "Ruby"), listOf("")) + testGetProgrammingLanguageUtil(listOf("scala", "Scala"), listOf("")) + testGetProgrammingLanguageUtil(listOf("sql", "Sql"), listOf("")) +// testGetProgrammingLanguageUtil(listOf("c++"), listOf("")) +// testGetProgrammingLanguageUtil(listOf("c"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Shell"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Rust"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Dart"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Lua"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Powershell"), listOf("")) + testGetProgrammingLanguageUtil(listOf("R"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Swift"), listOf("")) + testGetProgrammingLanguageUtil(listOf("SystemVerilog"), listOf("")) + testGetProgrammingLanguageUtil(listOf("Vue"), listOf("")) + } + + @Test + fun `test getProgrammingLanguage(virtualFile) by fileExtensions`() { + testGetProgrammingLanguageUtil(fileTypeNames = listOf("foo"), fileExtensions = listOf("java")) + testGetProgrammingLanguageUtil(listOf("bar"), listOf("py")) + testGetProgrammingLanguageUtil(listOf("baz"), listOf("js")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("jsx")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("tsx")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("ts")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("cs")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("go")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("kt")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("php")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("rb")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("scala")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("sql")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("txt")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("cpp", "c++", "cc")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("c", "h")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("sh")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("rs")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("dart")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("lua", "wlua")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("ps1", "psm1")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("r")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("swift")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("sv", "svh", "vh")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("vue")) + } + + @Test + fun `test getProgrammingLanguage(virtualFile) will fallback to unknown if either byFileType or byFileExtension works`() { + testGetProgrammingLanguageUtil(listOf("foo"), listOf("foo")) + testGetProgrammingLanguageUtil(listOf("foo"), listOf("")) + testGetProgrammingLanguageUtil(listOf(""), listOf("foo")) + } + @Test fun `test getProgrammingLanguage(virtualFile)`() { - testGetProgrammingLanguageUtil(listOf("java", "Java", "JAVA"), listOf("java"), CodeWhispererJava::class.java) - testGetProgrammingLanguageUtil(listOf("python", "Python"), listOf("py"), CodeWhispererPython::class.java) - testGetProgrammingLanguageUtil(listOf("javascript", "JavaScript"), listOf("js"), CodeWhispererJavaScript::class.java) - testGetProgrammingLanguageUtil(listOf("jsx harmony"), listOf("jsx"), CodeWhispererJsx::class.java) - testGetProgrammingLanguageUtil(listOf("typescript jsx"), listOf("tsx"), CodeWhispererTsx::class.java) - testGetProgrammingLanguageUtil(listOf("typescript", "TypeScript"), listOf("ts"), CodeWhispererTypeScript::class.java) - testGetProgrammingLanguageUtil(listOf("c#", "C#"), listOf("cs"), CodeWhispererCsharp::class.java) - testGetProgrammingLanguageUtil(listOf("go", "Go"), listOf("go"), CodeWhispererGo::class.java) - testGetProgrammingLanguageUtil(listOf("kotlin", "Kotlin"), listOf("kt"), CodeWhispererKotlin::class.java) - testGetProgrammingLanguageUtil(listOf("php", "Php"), listOf("php"), CodeWhispererPhp::class.java) - testGetProgrammingLanguageUtil(listOf("ruby", "Ruby"), listOf("rb"), CodeWhispererRuby::class.java) - testGetProgrammingLanguageUtil(listOf("scala", "Scala"), listOf("scala"), CodeWhispererScala::class.java) - testGetProgrammingLanguageUtil(listOf("sql", "Sql"), listOf("sql"), CodeWhispererSql::class.java) - testGetProgrammingLanguageUtil(listOf("plain_text"), listOf("txt"), CodeWhispererPlainText::class.java) - testGetProgrammingLanguageUtil(listOf("c++"), listOf("cpp", "c++", "cc"), CodeWhispererCpp::class.java) - testGetProgrammingLanguageUtil(listOf("c"), listOf("c", "h"), CodeWhispererC::class.java) - testGetProgrammingLanguageUtil(listOf("Shell Script"), listOf("sh"), CodeWhispererShell::class.java) - testGetProgrammingLanguageUtil(listOf("Rust"), listOf("rs"), CodeWhispererRust::class.java) + testGetProgrammingLanguageUtil(listOf("java", "Java", "JAVA"), listOf("java")) + testGetProgrammingLanguageUtil(listOf("python", "Python"), listOf("py")) + testGetProgrammingLanguageUtil(listOf("javascript", "JavaScript"), listOf("js")) + testGetProgrammingLanguageUtil(listOf("jsx harmony"), listOf("jsx")) + testGetProgrammingLanguageUtil(listOf("typescript jsx"), listOf("tsx")) + testGetProgrammingLanguageUtil(listOf("typescript", "TypeScript"), listOf("ts")) + testGetProgrammingLanguageUtil(listOf("c#", "C#"), listOf("cs")) + testGetProgrammingLanguageUtil(listOf("go", "Go"), listOf("go")) + testGetProgrammingLanguageUtil(listOf("kotlin", "Kotlin"), listOf("kt")) + testGetProgrammingLanguageUtil(listOf("php", "Php"), listOf("php")) + testGetProgrammingLanguageUtil(listOf("ruby", "Ruby"), listOf("rb")) + testGetProgrammingLanguageUtil(listOf("scala", "Scala"), listOf("scala")) + testGetProgrammingLanguageUtil(listOf("sql", "Sql"), listOf("sql")) + testGetProgrammingLanguageUtil(listOf("plain_text"), listOf("txt")) + testGetProgrammingLanguageUtil(listOf("c++"), listOf("cpp", "c++", "cc")) + testGetProgrammingLanguageUtil(listOf("c"), listOf("c", "h")) + testGetProgrammingLanguageUtil(listOf("Shell Script"), listOf("sh")) + testGetProgrammingLanguageUtil(listOf("Rust"), listOf("rs")) + testGetProgrammingLanguageUtil(listOf("Dart"), listOf("dart")) + testGetProgrammingLanguageUtil(listOf("Lua"), listOf("lua", "wlua")) + testGetProgrammingLanguageUtil(listOf("Powershell"), listOf("ps1", "psm1")) + testGetProgrammingLanguageUtil(listOf("R"), listOf("r")) + testGetProgrammingLanguageUtil(listOf("Swift"), listOf("swift")) + testGetProgrammingLanguageUtil(listOf("SystemVerilog"), listOf("sv", "svh", "vh")) + testGetProgrammingLanguageUtil(listOf("Vue"), listOf("vue")) } @Test @@ -102,10 +184,9 @@ class CodeWhispererLanguageManagerTest { assertThat(manager.getLanguage(psiFileMock)).isInstanceOf(CodeWhispererPython::class.java) } - private fun testGetProgrammingLanguageUtil( + private inline fun testGetProgrammingLanguageUtil( fileTypeNames: List, fileExtensions: List?, - expectedLanguage: Class, ) { fileExtensions?.forEach { fileExtension -> fileTypeNames.forEach { fileTypeName -> @@ -116,22 +197,129 @@ class CodeWhispererLanguageManagerTest { on { fileType } doReturn fileTypeMock on { extension } doReturn fileExtension } - assertThat(manager.getLanguage(vFileMock)).isInstanceOf(expectedLanguage) + assertThat(manager.getLanguage(vFileMock)).isInstanceOf(T::class.java) } } } } class CodeWhispererProgrammingLanguageTest { + @Rule + @JvmField + val applicationRule = ApplicationRule() + + val suts = listOf( + CodeWhispererC.INSTANCE, + CodeWhispererCpp.INSTANCE, + CodeWhispererCsharp.INSTANCE, + CodeWhispererDart.INSTANCE, + CodeWhispererGo.INSTANCE, + CodeWhispererJava.INSTANCE, + CodeWhispererJavaScript.INSTANCE, + CodeWhispererJson.INSTANCE, + CodeWhispererJsx.INSTANCE, + CodeWhispererKotlin.INSTANCE, + CodeWhispererLua.INSTANCE, + CodeWhispererPhp.INSTANCE, + CodeWhispererPlainText.INSTANCE, + CodeWhispererPowershell.INSTANCE, + CodeWhispererPython.INSTANCE, + CodeWhispererR.INSTANCE, + CodeWhispererRuby.INSTANCE, + CodeWhispererRust.INSTANCE, + CodeWhispererScala.INSTANCE, + CodeWhispererShell.INSTANCE, + CodeWhispererSql.INSTANCE, + CodeWhispererSwift.INSTANCE, + CodeWhispererSystemVerilog.INSTANCE, + CodeWhispererTf.INSTANCE, + CodeWhispererTsx.INSTANCE, + CodeWhispererTypeScript.INSTANCE, + CodeWhispererUnknownLanguage.INSTANCE, + CodeWhispererVue.INSTANCE, + CodeWhispererYaml.INSTANCE, + ) + class TestLanguage : CodeWhispererProgrammingLanguage() { override val languageId: String = "test-language" override fun toTelemetryType(): CodewhispererLanguage = CodewhispererLanguage.Unknown } @Test - fun `test language isSupport`() { - EP_NAME.extensionList.forEach { language -> - assertThat(language.isCodeCompletionSupported()).isTrue + fun `test language inline completion support`() { + suts.forEach { sut -> + val expected = when (sut) { + // supported + is CodeWhispererC, + is CodeWhispererCpp, + is CodeWhispererCsharp, + is CodeWhispererGo, + is CodeWhispererJava, + is CodeWhispererJavaScript, + is CodeWhispererJson, + is CodeWhispererJsx, + is CodeWhispererKotlin, + is CodeWhispererPhp, + is CodeWhispererPython, + is CodeWhispererRuby, + is CodeWhispererRust, + is CodeWhispererScala, + is CodeWhispererShell, + is CodeWhispererSql, + is CodeWhispererTf, + is CodeWhispererTsx, + is CodeWhispererTypeScript, + is CodeWhispererYaml, + is CodeWhispererDart, + is CodeWhispererLua, + is CodeWhispererPowershell, + is CodeWhispererR, + is CodeWhispererSwift, + is CodeWhispererSystemVerilog, + is CodeWhispererVue, + -> true + + // not supported + is CodeWhispererPlainText, is CodeWhispererUnknownLanguage -> false + + else -> false + } + + assertThat(sut.isCodeCompletionSupported()).isEqualTo(expected) + } + } + + @Test + fun `test language crossfile support`() { + suts.forEach { sut -> + val expected = when (sut) { + is CodeWhispererJava, + is CodeWhispererJavaScript, + is CodeWhispererJsx, + is CodeWhispererPython, + is CodeWhispererTsx, + is CodeWhispererTypeScript, + -> true + + else -> false + } + + assertThat(sut.isSupplementalContextSupported()).isEqualTo(expected) + } + } + + @Test + fun `test language utg support`() { + suts.forEach { sut -> + val expected = when (sut) { + is CodeWhispererJava, + is CodeWhispererPython, + -> true + + else -> false + } + + assertThat(sut.isUTGSupported()).isEqualTo(expected) } } diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts index d89201d35e4..9752c44e99d 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/cwChatConnector.ts @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItem, ChatItemAction, ChatItemType, FeedbackPayload } from '@aws/mynah-ui-chat' +import { ChatItemAction, ChatItemType, FeedbackPayload } from '@aws/mynah-ui-chat' import { ExtensionMessage } from '../commands' import { CodeReference } from './amazonqCommonsConnector' import { TabOpenType, TabsStorage } from '../storages/tabsStorage' import { FollowUpGenerator } from '../followUps/generator' +import { CWCChatItem } from "../connector"; interface ChatPayload { chatMessage: string @@ -17,8 +18,8 @@ interface ChatPayload { export interface ConnectorProps { sendMessageToExtension: (message: ExtensionMessage) => void onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void - onChatAnswerReceived?: (tabID: string, message: ChatItem) => void - onCWCContextCommandMessage: (message: ChatItem, command?: string) => string | undefined + onChatAnswerReceived?: (tabID: string, message: CWCChatItem) => void + onCWCContextCommandMessage: (message: CWCChatItem, command?: string) => string | undefined onError: (tabID: string, message: string, title: string) => void onWarning: (tabID: string, message: string, title: string) => void onOpenSettingsMessage: (tabID: string) => void @@ -98,7 +99,8 @@ export class Connector { codeReference?: CodeReference[], eventId?: string, codeBlockIndex?: number, - totalCodeBlocks?: number + totalCodeBlocks?: number, + userIntent?: string, ): void => { this.sendMessageToExtension({ tabID: tabID, @@ -111,6 +113,7 @@ export class Connector { eventId, codeBlockIndex, totalCodeBlocks, + userIntent }) } @@ -122,7 +125,8 @@ export class Connector { codeReference?: CodeReference[], eventId?: string, codeBlockIndex?: number, - totalCodeBlocks?: number + totalCodeBlocks?: number, + userIntent?: string, ): void => { this.sendMessageToExtension({ tabID: tabID, @@ -135,6 +139,7 @@ export class Connector { eventId, codeBlockIndex, totalCodeBlocks, + userIntent }) } @@ -258,13 +263,14 @@ export class Connector { } : undefined - const answer: ChatItem = { + const answer: CWCChatItem = { type: messageData.messageType, messageId: messageData.messageId ?? messageData.triggerID, body: messageData.message, followUp: followUps, canBeVoted: true, codeReference: messageData.codeReference, + userIntent: messageData.userIntent, } // If it is not there we will not set it @@ -291,12 +297,13 @@ export class Connector { return } if (messageData.messageType === ChatItemType.ANSWER) { - const answer: ChatItem = { + const answer: CWCChatItem = { type: messageData.messageType, body: undefined, relatedContent: undefined, messageId: messageData.messageId, codeReference: messageData.codeReference, + userIntent: messageData.userIntent, followUp: messageData.followUps !== undefined && messageData.followUps.length > 0 ? { diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts index 77ac82f7e70..efe6e6fb6a2 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/connector.ts @@ -30,6 +30,10 @@ export interface ChatPayload { chatCommand?: string } +export interface CWCChatItem extends ChatItem { + userIntent?: string +} + export interface ConnectorProps { sendMessageToExtension: (message: ExtensionMessage) => void onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void @@ -230,7 +234,8 @@ export class Connector { codeReference?: CodeReference[], eventId?: string, codeBlockIndex?: number, - totalCodeBlocks?: number + totalCodeBlocks?: number, + userIntent?: string ): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': @@ -242,7 +247,8 @@ export class Connector { codeReference, eventId, codeBlockIndex, - totalCodeBlocks + totalCodeBlocks, + userIntent ) break case 'featuredev': @@ -259,7 +265,8 @@ export class Connector { codeReference?: CodeReference[], eventId?: string, codeBlockIndex?: number, - totalCodeBlocks?: number + totalCodeBlocks?: number, + userIntent?: string ): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': @@ -271,7 +278,8 @@ export class Connector { codeReference, eventId, codeBlockIndex, - totalCodeBlocks + totalCodeBlocks, + userIntent ) break case 'featuredev': diff --git a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts index cd5f572ba7e..72f87ef5a76 100644 --- a/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts +++ b/plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts @@ -2,8 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -import { Connector } from './connector' -import { ChatItem, ChatItemType, MynahIcons, MynahUI, MynahUIDataModel, NotificationType } from '@aws/mynah-ui-chat' +import { Connector, CWCChatItem } from './connector' +import {ChatItem, ChatItemType, MynahIcons, MynahUI, MynahUIDataModel, NotificationType, ReferenceTrackerInformation} from '@aws/mynah-ui-chat' import './styles/dark.scss' import { TabsStorage, TabType } from './storages/tabsStorage' import { WelcomeFollowupType } from './apps/amazonqCommonsConnector' @@ -17,12 +17,15 @@ import { MessageController } from './messages/controller' import { getActions, getDetails } from './diffTree/actions' import { DiffTreeFileInfo } from './diffTree/types' import './styles.css' +import {CodeSelectionType} from "@aws/mynah-ui-chat/dist/static"; export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeTransformInitEnabled: boolean) => { // eslint-disable-next-line prefer-const let mynahUI: MynahUI // eslint-disable-next-line prefer-const let connector: Connector + const messageUserIntentMap = new Map() + const tabsStorage = new TabsStorage({ onTabTimeout: tabID => { mynahUI.addChatItem(tabID, { @@ -239,7 +242,7 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT sendMessageToExtension: message => { ideApi.postMessage(message) }, - onChatAnswerReceived: (tabID: string, item: ChatItem) => { + onChatAnswerReceived: (tabID: string, item: CWCChatItem) => { if (item.type === ChatItemType.ANSWER_PART || item.type === ChatItemType.CODE_RESULT) { mynahUI.updateLastChatAnswer(tabID, { ...(item.messageId !== undefined ? { messageId: item.messageId } : {}), @@ -251,6 +254,9 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT ? { type: ChatItemType.CODE_RESULT, fileList: item.fileList } : {}), }) + if (item.messageId !== undefined && item.userIntent !== undefined) { + messageUserIntentMap.set(item.messageId, item.userIntent) + } return } @@ -437,31 +443,52 @@ export const createMynahUI = (ideApi: any, featureDevInitEnabled: boolean, codeT content: 'Thanks for your feedback.', }) }, - onCodeInsertToCursorPosition: connector.onCodeInsertToCursorPosition, - onCopyCodeToClipboard: ( - tabId, - messageId, - code, - type, - referenceTrackerInfo, - eventId, - codeBlockIndex, - totalCodeBlocks + onCodeBlockActionClicked: ( + tabId: string, + messageId: string, + actionId: string, + data?: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number ) => { - connector.onCopyCodeToClipboard( - tabId, - messageId, - code, - type, - referenceTrackerInfo, - eventId, - codeBlockIndex, - totalCodeBlocks - ) - mynahUI.notify({ - type: NotificationType.SUCCESS, - content: 'Selected code is copied to clipboard', - }) + switch (actionId) { + case 'insert-to-cursor': + connector.onCodeInsertToCursorPosition( + tabId, + messageId, + code, + type, + referenceTrackerInformation, + eventId, + codeBlockIndex, + totalCodeBlocks, + messageUserIntentMap.get(messageId) ?? undefined + ) + break + case 'copy': + connector.onCopyCodeToClipboard( + tabId, + messageId, + code, + type, + referenceTrackerInformation, + eventId, + codeBlockIndex, + totalCodeBlocks, + messageUserIntentMap.get(messageId) ?? undefined + ) + mynahUI.notify({ + type: NotificationType.SUCCESS, + content: 'Selected code is copied to clipboard', + }) + break + default: + break + } }, onChatItemEngagement: connector.triggerSuggestionEngagement, onSourceLinkClick: (tabId, messageId, link, mouseEvent) => { diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-dart.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-dart.xml new file mode 100644 index 00000000000..e02e988ac6c --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-dart.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-lua.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-lua.xml new file mode 100644 index 00000000000..05a202e5829 --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-lua.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-powershell.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-powershell.xml new file mode 100644 index 00000000000..4fac40f59b6 --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-powershell.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-r.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-r.xml new file mode 100644 index 00000000000..08a46e5b783 --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-r.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-swift.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-swift.xml new file mode 100644 index 00000000000..1b8e657c274 --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-swift.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-systemverfilog.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-systemverfilog.xml new file mode 100644 index 00000000000..d7a94b957ad --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-systemverfilog.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-vue.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-vue.xml new file mode 100644 index 00000000000..825a9212410 --- /dev/null +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-vue.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 243a18dff8a..f4ce6dfac23 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -49,19 +49,26 @@ aws.toolkit.core com.intellij.modules.lang + Dart com.intellij.database org.jetbrains.plugins.go com.intellij.java org.jetbrains.kotlin + com.tang JavaScriptDebugger com.jetbrains.php + com.intellij.plugin.adernov.powershell com.intellij.modules.python + R4Intellij com.intellij.modules.rider org.jetbrains.plugins.ruby com.jetbrains.rust org.rust.lang org.intellij.scala + com.intellij.swift com.jetbrains.sh + studio.edaphic.sv + org.jetbrains.plugins.vue com.intellij.cwm.guest com.intellij.jetbrains.client diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ConfigFilesFacade.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ConfigFilesFacade.kt index 6ab051dc720..5e5240ed65c 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ConfigFilesFacade.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ConfigFilesFacade.kt @@ -41,6 +41,7 @@ interface ConfigFilesFacade { fun updateSectionInConfig(sectionName: String, profile: Profile) fun deleteSsoConnectionFromConfig(sessionName: String) + fun deleteSsoProfileScopesFromConfig(sessionName: String) } class DefaultConfigFilesFacade( @@ -197,6 +198,31 @@ class DefaultConfigFilesFacade( } } + override fun deleteSsoProfileScopesFromConfig(sessionName: String) { + val filePath = configPath + val lines = filePath.inputStreamIfExists()?.reader()?.readLines().orEmpty().toMutableList() + val ssoHeaderLine = lines.indexOfFirst { it.startsWith("[${SsoSessionConstants.SSO_SESSION_SECTION_NAME} $sessionName]") } + if (ssoHeaderLine == -1) return + val nextHeaderLine = lines.subList(ssoHeaderLine + 1, lines.size).indexOfFirst { it.startsWith("[") } + val endIndex = if (nextHeaderLine == -1) lines.size else ssoHeaderLine + nextHeaderLine + 1 + + // Find and remove the sso_registration_scopes line + for (i in ssoHeaderLine until endIndex) { + if (lines[i].trim().startsWith("sso_registration_scopes=")) { + lines.removeAt(i) + break + } + } + + filePath.writeText(lines.joinToString("\n")) + + val applicationManager = ApplicationManager.getApplication() + if (applicationManager != null && !applicationManager.isUnitTestMode) { + FileDocumentManager.getInstance().saveAllDocuments() + ProfileWatcher.getInstance().forceRefresh() + } + } + private fun getCorrespondingSsoSessionProfilePosition(updatedArray: List, sessionName: String): List { var content = updatedArray val finalContent = mutableListOf() diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ToolkitAuthManager.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ToolkitAuthManager.kt index 48b16392245..9c9e741f973 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ToolkitAuthManager.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/ToolkitAuthManager.kt @@ -20,6 +20,7 @@ import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.core.credentials.pinning.FeatureWithPinnedConnection import software.aws.toolkits.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifierSso +import software.aws.toolkits.jetbrains.core.credentials.profiles.ProfileWatcher import software.aws.toolkits.jetbrains.core.credentials.profiles.SsoSessionConstants.SSO_SESSION_SECTION_NAME import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider @@ -204,9 +205,13 @@ fun loginSso( fun logoutFromSsoConnection(project: Project?, connection: AwsBearerTokenConnection, callback: () -> Unit = {}) { try { ToolkitAuthManager.getInstance().deleteConnection(connection.id) + ProfileWatcher.getInstance().forceRefresh() + project?.let { ToolkitConnectionManager.getInstance(it).switchConnection(null) } + if (connection is ProfileSsoManagedBearerSsoConnection) { deleteSsoConnection(connection) } + } finally { callback() } @@ -338,7 +343,7 @@ fun deleteSsoConnection(connection: ProfileSsoManagedBearerSsoConnection) = fun deleteSsoConnection(connection: CredentialIdentifier) = deleteSsoConnection(getSsoSessionProfileNameFromCredentials(connection)) -fun deleteSsoConnection(sessionName: String) = DefaultConfigFilesFacade().deleteSsoConnectionFromConfig(sessionName) +fun deleteSsoConnection(sessionName: String) = DefaultConfigFilesFacade().deleteSsoProfileScopesFromConfig(sessionName) private fun getSsoSessionProfileNameFromCredentials(connection: CredentialIdentifier): String { connection as ProfileCredentialsIdentifierSso diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/actions/SsoLogoutAction.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/actions/SsoLogoutAction.kt index 1cda785a815..6c7e39a48ac 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/actions/SsoLogoutAction.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/actions/SsoLogoutAction.kt @@ -6,25 +6,13 @@ package software.aws.toolkits.jetbrains.core.credentials.actions import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.DumbAwareAction -import com.intellij.openapi.ui.MessageDialogBuilder import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection -import software.aws.toolkits.jetbrains.core.credentials.ProfileSsoManagedBearerSsoConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.deleteSsoConnection import software.aws.toolkits.jetbrains.core.credentials.logoutFromSsoConnection import software.aws.toolkits.resources.AwsCoreBundle class SsoLogoutAction(private val value: AwsBearerTokenConnection) : DumbAwareAction(AwsCoreBundle.message("credentials.individual_identity.signout")) { override fun actionPerformed(e: AnActionEvent) { - if (value is ProfileSsoManagedBearerSsoConnection) { - val confirmDeletion = MessageDialogBuilder.okCancel( - AwsCoreBundle.message("gettingstarted.auth.idc.sign.out.confirmation.title"), - AwsCoreBundle.message("gettingstarted.auth.idc.sign.out.confirmation") - ).yesText(AwsCoreBundle.message("general.confirm")).ask(e.project) - if (confirmDeletion) { - deleteSsoConnection(value) - } - } logoutFromSsoConnection(e.project, value) ApplicationManager.getApplication().messageBus.syncPublisher( ToolkitConnectionManagerListener.TOPIC diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt index 13e31e993c9..7a6a537fd0d 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/credentials/DefaultConfigFilesFacadeTest.kt @@ -308,292 +308,6 @@ class DefaultConfigFilesFacadeTest { ) } - @Test - fun `delete session from config on sign out - only sso-session`() { - val baseFolder = folderRule.newFolder() - val config = Paths.get(baseFolder.absolutePath, ".aws", "config") - config.createParentDirectories() - config.writeText( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session2] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [sso-session session3] - """.trimIndent() - ) - val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") - val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) - sut.deleteSsoConnectionFromConfig("session2") - assertThat(config).hasContent( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - } - - @Test - fun `delete session from config on sign out`() { - val baseFolder = folderRule.newFolder() - val config = Paths.get(baseFolder.absolutePath, ".aws", "config") - config.createParentDirectories() - config.writeText( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session2] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session2-123-admin] - sso_session=session2 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") - val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) - sut.deleteSsoConnectionFromConfig("session2") - assertThat(config).hasContent( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - } - - @Test - fun `delete session from config on sign out - profile name is different from session name`() { - val baseFolder = folderRule.newFolder() - val config = Paths.get(baseFolder.absolutePath, ".aws", "config") - config.createParentDirectories() - config.writeText( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session2] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile othername-with-same-session] - sso_session=session2 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") - val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) - sut.deleteSsoConnectionFromConfig("session2") - assertThat(config).hasContent( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - } - - @Test - fun `delete session from config on sign out - multiple profiles with same prefix`() { - val baseFolder = folderRule.newFolder() - val config = Paths.get(baseFolder.absolutePath, ".aws", "config") - config.createParentDirectories() - config.writeText( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session2] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session2-123-admin] - aws_access_key=abjcbd - aws_secret_access_key=123 - [profile session2-123-admin] - sso_session=session2 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") - val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) - sut.deleteSsoConnectionFromConfig("session2") - assertThat(config).hasContent( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [profile session2-123-admin] - aws_access_key=abjcbd - aws_secret_access_key=123 - [sso-session session3] - """.trimIndent() - ) - } - - @Test - fun `delete session from config on sign out - multiple profiles in the same session`() { - val baseFolder = folderRule.newFolder() - val config = Paths.get(baseFolder.absolutePath, ".aws", "config") - config.createParentDirectories() - config.writeText( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [sso-session session2] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session2-123-admin] - aws_access_key=abjcbd - aws_secret_access_key=123 - [profile session2-123-admin] - sso_session=session2 - sso_account_id=123 - sso_role_name= admin - [profile session2-345-admin] - sso_session=session2 - sso_account_id=345 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") - val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) - sut.deleteSsoConnectionFromConfig("session2") - assertThat(config).hasContent( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [profile session2-123-admin] - aws_access_key=abjcbd - aws_secret_access_key=123 - [sso-session session3] - """.trimIndent() - ) - } - - @Test - fun `delete session from config on sign out - multiple profiles in the same session with profile before sso-session`() { - val baseFolder = folderRule.newFolder() - val config = Paths.get(baseFolder.absolutePath, ".aws", "config") - config.createParentDirectories() - config.writeText( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [profile session2-345-admin] - sso_session=session2 - sso_account_id=345 - sso_role_name= admin - [sso-session session2] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session2-123-admin] - aws_access_key=abjcbd - aws_secret_access_key=123 - [profile session2-123-admin] - sso_session=session2 - sso_account_id=123 - sso_role_name= admin - [sso-session session3] - """.trimIndent() - ) - val creds = Paths.get(baseFolder.absolutePath, ".aws", "credentials") - val sut = DefaultConfigFilesFacade(configPath = config, credentialsPath = creds) - sut.deleteSsoConnectionFromConfig("session2") - assertThat(config).hasContent( - """ - [sso-session session1] - sso_start_url=https://start - sso_region=us-west-2 - sso_registration_scopes=scope1, scope2 - [profile session1-123-admin] - sso_session=session1 - sso_account_id=123 - sso_role_name= admin - [profile session2-123-admin] - aws_access_key=abjcbd - aws_secret_access_key=123 - [sso-session session3] - """.trimIndent() - ) - } - private inline fun assumeNoException(block: () -> Unit) { try { block() diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 50418a9c139..b575a7213df 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -72,6 +72,7 @@ amazonqFeatureDev.exception.request_failed=Request failed amazonqFeatureDev.exception.retry_request_failed=Retry request failed amazonqFeatureDev.exception.throttling=I'm sorry, I'm experiencing high demand at the moment and can't generate your code. This attempt won't count toward usage limits. Please try again. amazonqFeatureDev.exception.upload_code=I'm sorry, I couldn't upload your workspace artifacts to Amazon S3 to help you with this task. You might need to allow access to the S3 bucket. For more information, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security_iam_manage-access-with-policies.html#data-perimeters) or contact your network or organization administrator. +amazonqFeatureDev.exception.upload_url_expiry=I'm sorry, I wasn't able to generate code. A connection timed out or became unavailable. Please try again or check the following:\n\n- Exclude non-essential files in your workspace's `.gitignore`.\n\n- Check that your network connection is stable. amazonqFeatureDev.follow_instructions_for_authentication=Follow instructions to re-authenticate ... amazonqFeatureDev.follow_up.close_session=No, thanks amazonqFeatureDev.follow_up.incorrect_source_folder=The folder you chose isn't in your open workspace folder. You can add this folder to your workspace, or choose a folder in your open workspace. diff --git a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt index e18e67d02ff..af79c7e543f 100644 --- a/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt +++ b/plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/editor/GettingStartedPanel.kt @@ -44,7 +44,6 @@ import software.aws.toolkits.jetbrains.core.credentials.ProfileSsoManagedBearerS import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener -import software.aws.toolkits.jetbrains.core.credentials.deleteSsoConnection import software.aws.toolkits.jetbrains.core.credentials.logoutFromSsoConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeCatalystConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.ConnectionPinningManagerListener @@ -527,17 +526,6 @@ class GettingStartedPanel( val validConnection = activeConnection() val connection = validConnection.activeConnectionBearer - if (connection is ProfileSsoManagedBearerSsoConnection) { - if (validConnection.connectionType == ActiveConnectionType.IAM_IDC) { - val confirmDeletion = MessageDialogBuilder.okCancel( - message("gettingstarted.auth.idc.sign.out.confirmation.title"), - message("gettingstarted.auth.idc.sign.out.confirmation") - ).yesText(message("general.confirm")).ask(project) - if (confirmDeletion) { - deleteSsoConnection(connection) - } - } - } if (connection != null) { logoutFromSsoConnection(project, connection) { controlPanelVisibility(panelConnected, panelNotConnected) @@ -671,16 +659,6 @@ class GettingStartedPanel( link(message("toolkit.login.aws_builder_id.already_connected.reconnect")) { val activeConnection = checkIamConnectionValidity(project) val connection = activeConnection.activeConnectionIam - if (connection != null) { - val confirmDeletion = MessageDialogBuilder.okCancel( - message("gettingstarted.auth.idc.sign.out.confirmation.title"), - message("gettingstarted.auth.idc.sign.out.confirmation") - ).yesText(message("general.confirm")).ask(project) - if (confirmDeletion) { - deleteSsoConnection(connection) - controlPanelVisibility(panelConnected, panelNotConnected) - } - } } }.visible(checkIamConnectionValidity(project).connectionType == ActiveConnectionType.IAM_IDC) row { @@ -739,16 +717,6 @@ class GettingStartedPanel( link(message("toolkit.login.aws_builder_id.already_connected.reconnect")) { val activeConnection = checkIamConnectionValidity(project) val connection = activeConnection.activeConnectionIam - if (connection != null) { - val confirmDeletion = MessageDialogBuilder.okCancel( - message("gettingstarted.auth.idc.sign.out.confirmation.title"), - message("gettingstarted.auth.idc.sign.out.confirmation") - ).yesText(message("general.confirm")).ask(project) - if (confirmDeletion) { - deleteSsoConnection(connection) - controlPanelVisibility(panelConnected, panelNotConnected) - } - } } }.visible(checkIamConnectionValidity(project).connectionType == ActiveConnectionType.IAM_IDC) @@ -891,17 +859,7 @@ class GettingStartedPanel( val validConnection = checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) val connection = validConnection.activeConnectionBearer - if (connection is ProfileSsoManagedBearerSsoConnection) { - if (validConnection.connectionType == ActiveConnectionType.IAM_IDC) { - val confirmDeletion = MessageDialogBuilder.okCancel( - message("gettingstarted.auth.idc.sign.out.confirmation.title"), - message("gettingstarted.auth.idc.sign.out.confirmation") - ).yesText(message("general.confirm")).ask(project) - if (confirmDeletion) { - deleteSsoConnection(connection) - } - } - } + if (connection != null) { logoutFromSsoConnection(project, connection) { controlPanelVisibility(panelConnected, panelNotConnected) @@ -982,15 +940,6 @@ class GettingStartedPanel( val validConnection = checkBearerConnectionValidity(project, BearerTokenFeatureSet.CODEWHISPERER) val connection = validConnection.activeConnectionBearer if (connection is ProfileSsoManagedBearerSsoConnection) { - if (validConnection.connectionType == ActiveConnectionType.IAM_IDC) { - val confirmDeletion = MessageDialogBuilder.okCancel( - message("gettingstarted.auth.idc.sign.out.confirmation.title"), - message("gettingstarted.auth.idc.sign.out.confirmation") - ).yesText(message("general.confirm")).ask(project) - if (confirmDeletion) { - deleteSsoConnection(connection) - } - } logoutFromSsoConnection(project, connection) { controlPanelVisibility(panelConnected, panelNotConnected) }