Skip to content

Commit

Permalink
Integrate browser based views in ViewContainer (#359)
Browse files Browse the repository at this point in the history
* Pass populated AWS_CA_BUNDLE env var to Flare (#341)

* Add UI notification to alert user of deprecated manifest version (#312)

* Revert commit 'Add UI notification to alert user of deprecated manifest version'

* Revert 'Pass populated AWS_CA_BUNDLE env var to Flare'

* Rebase changes from Browser Provider PR

* Fix ViewRouter

* Integrate browser based views in ViewContainer

* Fix checkstyle issues

* Fix AmazonQBrowserProvider tests

* Fix bug due to display sync exec call

* Add semaphore locking to container to prevent race conditions

* Add browser focus handling

* Add state checking methods to LspState

* Clean up code

* Remove unnecessary parent assignment from chat webview

* Refactor ViewVisibilityManager to default to one view

* Add accidentally removed telemetry emissions

* Update plugin descriptor (#360)

* Move semaphore locking/unlocking to try/finally block

---------

Co-authored-by: Jonathan Breedlove <[email protected]>
Co-authored-by: Nicolas <[email protected]>
  • Loading branch information
3 people committed Feb 25, 2025
1 parent dde99dc commit f076a99
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 28 deletions.
32 changes: 5 additions & 27 deletions plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,40 +50,15 @@
<extension point="org.eclipse.ui.activities">
<!-- These activities and activityPatternBindings prevent the view from showing up in the Show View menu. Logic exists that will filter these views out. -->
<!-- ToolkitLoginWebview is intentionally excluded here as this should be the only view that should show in the menu. -->
<activity id="software.aws.toolkits.eclipse.amazonq.activity.AmazonQChatWebview" name="Amazon Q Chat">
</activity>
<activity id="software.aws.toolkits.eclipse.amazonq.activity.AmazonQCodeReferenceView" name="Amazon Q Code Reference">
</activity>
<activity id="software.aws.toolkits.eclipse.amazonq.activity.DependencyMissingView" name="Amazon Q Dependency Missing">
</activity>
<activity id="software.aws.toolkits.eclipse.amazonq.activity.ReauthenticateView" name="Amazon Q Reauthenticate">
</activity>
<activity id="software.aws.toolkits.eclipse.amazonq.activity.ChatAssetMissingView" name="Amazon Q Chat Missing">
</activity>
<activityPatternBinding
activityId="software.aws.toolkits.eclipse.amazonq.activity.AmazonQChatWebview"
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.AmazonQChatWebview">
</activityPatternBinding>
<activityPatternBinding
activityId="software.aws.toolkits.eclipse.amazonq.activity.AmazonQCodeReferenceView"
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.AmazonQCodeReferenceView">
</activityPatternBinding>
<activityPatternBinding
activityId="software.aws.toolkits.eclipse.amazonq.activity.DependencyMissingView"
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.DependencyMissingView">
</activityPatternBinding>
<activityPatternBinding
activityId="software.aws.toolkits.eclipse.amazonq.activity.ReauthenticateView"
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.ReauthenticateView">
</activityPatternBinding>
<activityPatternBinding
activityId="software.aws.toolkits.eclipse.amazonq.activity.ChatAssetMissingView"
activityId="software.aws.toolkits.eclipse.amazonq.activity.AmazonQViewContainer"
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.ChatAssetMissingView">
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.AmazonQViewContainer">
</activityPatternBinding>
</extension>
<extension
Expand All @@ -97,6 +72,7 @@
relationship="right"
visible="false">
</view>
<<<<<<< HEAD
<view
id="software.aws.toolkits.eclipse.amazonq.views.AmazonQChatWebview"
relative="software.aws.toolkits.eclipse.amazonq.views.ToolkitLoginWebview"
Expand Down Expand Up @@ -184,6 +160,8 @@
relationship="right"
visible="false">
</view>
=======
>>>>>>> 4c33324 (Integrate browser based views in ViewContainer (#359))
</perspectiveExtension>
</extension>
<extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,221 @@ private Optional<String> resolveContent() {
""", chatJsPath, chatJsPath, generateCss(), generateJS(chatJsPath)));
}

private String generateCss() {
return """
<style>
body,
html {
background-color: var(--mynah-color-bg);
color: var(--mynah-color-text-default);
height: 100vh;
width: 100%%;
overflow: hidden;
margin: 0;
padding: 0;
}
.mynah-ui-icon-plus,
.mynah-ui-icon-cancel {
-webkit-mask-size: 155% !important;
mask-size: 155% !important;
mask-position: center;
scale: 60%;
}
.code-snippet-close-button i.mynah-ui-icon-cancel,
.mynah-chat-item-card-related-content-show-more i.mynah-ui-icon-down-open {
-webkit-mask-size: 195.5% !important;
mask-size: 195.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;
mask-position: center;
}
textarea:placeholder-shown {
line-height: 1.5rem;
}
.mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part > .mynah-ui-spinner-logo-mask.text {
opacity: 1 !important;
}
</style>
""";
}

private String generateJS(final String jsEntrypoint) {
var chatQuickActionConfig = generateQuickActionConfig();
var disclaimerAcknowledged = Activator.getPluginStore().get(PluginStoreKeys.CHAT_DISCLAIMER_ACKNOWLEDGED);
return String.format("""
<script type="text/javascript" src="%s" defer></script>
<script type="text/javascript">
%s
const init = () => {
waitForFunction('ideCommand')
.then(() => {
amazonQChat.createChat({
postMessage: (message) => {
ideCommand(JSON.stringify(message));
}
},
{
quickActionCommands: %s,
disclaimerAcknowledged: %b
});
})
.catch(error => console.error('Error initializing chat:', error));
}
window.addEventListener('load', init);
%s
%s
%s
%s
</script>
""", jsEntrypoint, getWaitFunction(), chatQuickActionConfig, "true".equals(disclaimerAcknowledged),
getArrowKeyBlockingFunction(), getSelectAllAndCopySupportFunctions(), getPreventEmptyPopupFunction(),
getFocusOnChatPromptFunction());
}

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 && node.matches(selector)) {
attachEventListeners(node);
}
// Check for nested matches
if (node.querySelectorAll) {
const buttons = node.querySelectorAll(selector); // Missing selector parameter
buttons.forEach(attachEventListeners);
}
}
});
});
} catch (error) {
console.error('Error in mutation observer:', error);
}
});
function attachEventListeners(element) {
if (!element || element.dataset.hasListener) return; // Prevent duplicate listeners
const handleMouseOver = function(event) {
const textSpan = this.querySelector('span.mynah-button-label');
if (textSpan && textSpan.scrollWidth <= textSpan.offsetWidth) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
}
};
element.addEventListener('mouseover', handleMouseOver, true);
element.dataset.hasListener = 'true';
}
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
""".formatted(selector);
}

private String getFocusOnChatPromptFunction() {
return """
window.addEventListener('load', () => {
const chatContainer = document.querySelector('.mynah-chat-prompt');
if (chatContainer) {
chatContainer.addEventListener('click', (event) => {
if (!event.target.closest('.mynah-chat-prompt-input')) {
keepFocusOnPrompt();
}
});
}
});
""";
}

private String generateCss() {
return """
<style>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public AmazonQBrowserProvider() {
// Test constructor that accepts a platform
public AmazonQBrowserProvider(final PluginPlatform platform) {
this.pluginPlatform = platform;
<<<<<<< HEAD
=======
Display.getDefault().asyncExec(this::publishBrowserCompatibilityState);
>>>>>>> 4c33324 (Integrate browser based views in ViewContainer (#359))
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ public Object function(final Object[] arguments) {
}
};

addFocusListener(parent, browser);

// Inject chat theme after mynah-ui has loaded
browser.addProgressListener(new ProgressAdapter() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IViewSite;

import software.aws.toolkits.eclipse.amazonq.providers.browser.AmazonQBrowserProvider;
import software.aws.toolkits.eclipse.amazonq.util.ThemeDetector;
Expand All @@ -18,6 +19,8 @@ public abstract class AmazonQView extends BaseAmazonQView {
private AmazonQBrowserProvider browserProvider;
private static final ThemeDetector THEME_DETECTOR = new ThemeDetector();

private IViewSite viewSite;

protected AmazonQView() {
this.browserProvider = new AmazonQBrowserProvider();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.part.ViewPart;

import io.reactivex.rxjava3.disposables.Disposable;
import software.aws.toolkits.eclipse.amazonq.broker.api.EventObserver;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.views.router.AmazonQViewType;
Expand All @@ -26,8 +27,14 @@ public final class AmazonQViewContainer extends ViewPart implements EventObserve
private Composite parentComposite;
private volatile StackLayout layout;
private Map<AmazonQViewType, BaseAmazonQView> views;
<<<<<<< HEAD
private volatile AmazonQViewType activeViewType;
private volatile BaseAmazonQView currentView;
=======
private AmazonQViewType activeViewType;
private BaseAmazonQView currentView;
private Disposable activeViewTypeSubscription;
>>>>>>> 4c33324 (Integrate browser based views in ViewContainer (#359))
private final ReentrantLock containerLock;

public AmazonQViewContainer() {
Expand Down Expand Up @@ -57,20 +64,33 @@ public void createPartControl(final Composite parent) {
parent.setLayout(gridLayout);

parentComposite = parent;
<<<<<<< HEAD

updateChildView();
=======

setupStaticMenuActions();
updateChildView();
}

private void setupStaticMenuActions() {
new AmazonQStaticActions(getViewSite());
>>>>>>> 4c33324 (Integrate browser based views in ViewContainer (#359))
}

private void updateChildView() {
Display.getDefault().asyncExec(() -> {
try {
containerLock.lock();
BaseAmazonQView newView = views.get(activeViewType);

if (currentView != null) {
<<<<<<< HEAD
if (currentView instanceof AmazonQChatWebview) {
((AmazonQChatWebview) currentView).disposeBrowserState();
}
=======
>>>>>>> 4c33324 (Integrate browser based views in ViewContainer (#359))
Control[] children = parentComposite.getChildren();
for (Control child : children) {
if (child != null && !child.isDisposed()) {
Expand All @@ -81,7 +101,14 @@ private void updateChildView() {
currentView.dispose();
}

<<<<<<< HEAD
newView.setViewSite(getViewSite());
=======
if (activeViewType == AmazonQViewType.CHAT_VIEW
|| activeViewType == AmazonQViewType.TOOLKIT_LOGIN_VIEW) {
((AmazonQView) newView).setViewSite(getViewSite());
}
>>>>>>> 4c33324 (Integrate browser based views in ViewContainer (#359))

Composite newViewComposite = newView.setupView(parentComposite);
GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, true, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private void refreshActiveView(final PluginState pluginState) {

if (pluginState.browserCompatibilityState() == BrowserCompatibilityState.DEPENDENCY_MISSING) {
newActiveView = AmazonQViewType.DEPENDENCY_MISSING_VIEW;
} else if (pluginState.lspState() == LspState.FAILED) {
} else if (pluginState.lspState().hasFailed()) {
newActiveView = AmazonQViewType.LSP_STARTUP_FAILED_VIEW;
} else if (pluginState.chatWebViewAssetState() == ChatWebViewAssetState.DEPENDENCY_MISSING
|| pluginState.toolkitLoginWebViewAssetState() == ToolkitLoginWebViewAssetState.DEPENDENCY_MISSING) {
Expand Down

0 comments on commit f076a99

Please sign in to comment.