diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java index ebe4b33d5..36e79a0cf 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java @@ -3,6 +3,8 @@ package software.aws.toolkits.eclipse.amazonq.views; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; import java.util.List; import java.util.Optional; @@ -26,6 +28,8 @@ import software.aws.toolkits.eclipse.amazonq.lsp.model.QuickActionsCommandGroup; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; import software.aws.toolkits.eclipse.amazonq.util.ObjectMapperFactory; +import software.aws.toolkits.eclipse.amazonq.util.PluginPlatform; +import software.aws.toolkits.eclipse.amazonq.util.PluginUtils; import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils; import software.aws.toolkits.eclipse.amazonq.views.actions.AmazonQCommonActions; @@ -91,6 +95,7 @@ public void completed(final ProgressEvent event) { amazonQCommonActions = getAmazonQCommonActions(); chatCommunicationManager.setChatUiRequestListener(this); + new BrowserFunction(browser, "ideCommand") { @Override public Object function(final Object[] arguments) { @@ -101,6 +106,24 @@ public Object function(final Object[] arguments) { } }; + new BrowserFunction(browser, "isMacOs") { + @Override + public Object function(final Object[] arguments) { + return Boolean.TRUE.equals(PluginUtils.getPlatform() == PluginPlatform.MAC); + } + }; + + new BrowserFunction(browser, "copyToClipboard") { + @Override + public Object function(final Object[] arguments) { + if (arguments.length > 0 && arguments[0] instanceof String) { + StringSelection stringSelection = new StringSelection((String) arguments[0]); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + } + return null; + } + }; + // Inject chat theme after mynah-ui has loaded browser.addProgressListener(new ProgressAdapter() { @Override @@ -210,6 +233,17 @@ private String generateCss() { mask-position: center; scale: 60%; } + .mynah-button.mynah-button-secondary.fill-state-always.code-snippet-close-button + .mynah-ui-clickable-item + .mynah-ui-icon-cancel { + -webkit-mask-size: 187.5% !important; + mask-size: 187.5% !important; + mask-position: center; + aspect-ratio: 1/1; + width: 15px; + height: 15px; + scale: 50% + } .mynah-ui-icon-tabs { -webkit-mask-size: 102% !important; mask-size: 102% !important; @@ -245,9 +279,17 @@ private String generateJS(final String jsEntrypoint) { } window.addEventListener('load', init); + + %s + + %s + + %s + """, jsEntrypoint, getWaitFunction(), chatQuickActionConfig, - "true".equals(disclaimerAcknowledged)); + "true".equals(disclaimerAcknowledged), getArrowKeyBlockingFunction(), + getSelectAllAndCopySupportFunctions(), getPreventEmptyPopupFunction()); } /* @@ -271,6 +313,118 @@ private String serializeQuickActionCommands(final List } } + private String getArrowKeyBlockingFunction() { + return """ + window.addEventListener('load', () => { + const textarea = document.querySelector('textarea.mynah-chat-prompt-input'); + if (textarea) { + textarea.addEventListener('keydown', (event) => { + const cursorPosition = textarea.selectionStart; + const hasText = textarea.value.length > 0; + + // block arrow keys on empty text area + switch (event.key) { + case 'ArrowLeft': + if (!hasText || cursorPosition === 0) { + event.preventDefault(); + event.stopPropagation(); + } + break; + + case 'ArrowRight': + if (!hasText || cursorPosition === textarea.value.length) { + event.preventDefault(); + event.stopPropagation(); + } + break; + } + }); + } + }); + """; + } + + private String getSelectAllAndCopySupportFunctions() { + return """ + window.addEventListener('load', () => { + const textarea = document.querySelector('textarea.mynah-chat-prompt-input'); + if (textarea) { + textarea.addEventListener("keydown", (event) => { + if (((isMacOs() && event.metaKey) || (!isMacOs() && event.ctrlKey)) + && event.key === 'a') { + textarea.select(); + event.preventDefault(); + event.stopPropagation(); + } + }); + } + }); + + window.addEventListener('load', () => { + const textarea = document.querySelector('textarea.mynah-chat-prompt-input'); + if (textarea) { + textarea.addEventListener("keydown", (event) => { + if (((isMacOs() && event.metaKey) || (!isMacOs() && event.ctrlKey)) + && event.key === 'c') { + copyToClipboard(textarea.value); + event.preventDefault(); + event.stopPropagation(); + } + }); + } + }); + """; + } + + private String getPreventEmptyPopupFunction() { + String selector = ".mynah-button" + ".mynah-button-secondary.mynah-button-border" + ".fill-state-always" + + ".mynah-chat-item-followup-question-option" + ".mynah-ui-clickable-item"; + + return """ + const observer = new MutationObserver((mutations) => { + try { + const selector = '%s'; + + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { // Check if it's an element node + // Check for direct match + if (node.matches(selector)) { + attachEventListeners(node); + } + // Check for nested matches + const buttons = node.querySelectorAll(); + buttons.forEach(attachEventListeners); + } + }); + }); + } catch (error) { + console.error('Error in mutation observer:', error); + } + }); + function attachEventListeners(element) { + if (!element || element.dataset.hasListener) return; // Prevent duplicate listeners + element.addEventListener('mouseover', function(event) { + const textSpan = this.querySelector('span.mynah-button-label'); + if (textSpan && textSpan.scrollWidth <= textSpan.offsetWidth) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + }, true); + element.dataset.hasListener = 'true'; + } + try { + observer.observe(document.body, { + childList: true, + subtree: true + }); + } catch (error) { + console.error('Error starting observer:', error); + } + """.formatted(selector); + } + @Override public final void onSendToChatUi(final String message) { String script = "window.postMessage(" + message + ");";