diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java index b8acbd5f5..a3a54ac8f 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java @@ -11,6 +11,7 @@ import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.SsoTokenChangedParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.ConnectionMetadata; +import software.aws.toolkits.eclipse.amazonq.lsp.model.NotificationParams; public interface AmazonQLspClient extends LanguageClient { @@ -19,5 +20,10 @@ public interface AmazonQLspClient extends LanguageClient { @JsonNotification("aws/identity/ssoTokenChanged") void ssoTokenChanged(SsoTokenChangedParams params); + + @JsonNotification("aws/window/showNotification") + void showNotification(NotificationParams params); + + void didChangeConfiguration(Object object); } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java index f385dec99..0bf4484b9 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java @@ -18,6 +18,7 @@ import org.eclipse.lsp4j.ProgressParams; import org.eclipse.lsp4j.ShowDocumentParams; import org.eclipse.lsp4j.ShowDocumentResult; +import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; @@ -28,6 +29,7 @@ import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.SsoTokenChangedKind; import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.SsoTokenChangedParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.ConnectionMetadata; +import software.aws.toolkits.eclipse.amazonq.lsp.model.NotificationParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.SsoProfileData; import software.aws.toolkits.eclipse.amazonq.lsp.model.TelemetryEvent; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; @@ -37,6 +39,8 @@ import software.aws.toolkits.eclipse.amazonq.util.ObjectMapperFactory; import software.aws.toolkits.eclipse.amazonq.views.model.Customization; import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils; +import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification; + @SuppressWarnings("restriction") public class AmazonQLspClientImpl extends LanguageClientImpl implements AmazonQLspClient { @@ -148,6 +152,20 @@ public final CompletableFuture showDocument(final ShowDocume } }); } + + @Override + public void didChangeConfiguration(Object object) { + ThreadingUtils.executeAsyncTask(() -> { + try { + Activator.getLogger().info("Configuration changed"); + if (object != null) { + } + } catch (Exception e) { + Activator.getLogger().error("Error processing configuration change", e); + } + }); + } + @Override public final void ssoTokenChanged(final SsoTokenChangedParams params) { @@ -170,4 +188,42 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) { Activator.getLogger().error("Error processing " + kind + " ssoTokenChanged notification", ex); } } -} + + @Override + public final void showNotification(final NotificationParams params) { + Display.getDefault().asyncExec(() -> { + String title = params.content().title() != null + ? params.content().title() + : "Notification"; + String message = params.content().text(); + + // Business logic using MessageType directly + switch(params.type()) { + case Error: + showHighPriorityNotification(title, message); + break; + case Warning: + showMediumPriorityNotification(title, message); + break; + case Info: + showLowPriorityNotification(title, message); + break; + default: + showLowPriorityNotification(title, message); + break; + } + }); + } + + private void showHighPriorityNotification(String title, String message) { + ToolkitNotification.showBlockingNotification(title, message); + } + + private void showMediumPriorityNotification(String title, String message) { + ToolkitNotification.showPersistentNotification(title, message); + } + + private void showLowPriorityNotification(String title, String message) { + ToolkitNotification.showTransientNotification(title, message); + } +} \ No newline at end of file diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServerBuilder.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServerBuilder.java index e28df6eb7..8c340d804 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServerBuilder.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServerBuilder.java @@ -41,6 +41,8 @@ private Map getInitializationOptions(final ClientMetadata metada Map initOptions = new HashMap<>(); Map awsInitOptions = new HashMap<>(); Map extendedClientInfoOptions = new HashMap<>(); + Map awsClientCapabilitiesOptions = new HashMap<>(); + Map windowOptions = new HashMap<>(); Map extensionOptions = new HashMap<>(); extensionOptions.put("name", USER_AGENT_CLIENT_NAME); extensionOptions.put("version", metadata.getPluginVersion()); @@ -48,7 +50,10 @@ private Map getInitializationOptions(final ClientMetadata metada extendedClientInfoOptions.put("clientId", metadata.getClientId()); extendedClientInfoOptions.put("version", metadata.getIdeVersion()); extendedClientInfoOptions.put("name", metadata.getIdeName()); + windowOptions.put("notifications", "true"); + awsClientCapabilitiesOptions.put("window", windowOptions); awsInitOptions.put("clientInfo", extendedClientInfoOptions); + awsInitOptions.put("awsClientCapabilities", awsClientCapabilitiesOptions); initOptions.put("aws", awsInitOptions); return initOptions; } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java index 9da462cfb..49d37060b 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/connection/QLspConnectionProvider.java @@ -36,7 +36,10 @@ public QLspConnectionProvider() throws IOException { var serverCommand = Paths.get(lspInstallResult.getServerDirectory(), lspInstallResult.getServerCommand()); List commands = new ArrayList<>(); commands.add(serverCommand.toString()); - commands.add(lspInstallResult.getServerCommandArgs()); + //commands.add(lspInstallResult.getServerCommandArgs()); + commands.add("--inspect=6012"); + commands.add("/Users/aseemxs/Language-servers/language-servers/app/aws-lsp-notification-runtimes/out/standalone.js"); + commands.add("--nolazy"); commands.add("--stdio"); commands.add("--set-credentials-encryption-key"); setCommands(commands); @@ -55,6 +58,7 @@ protected final void addEnvironmentVariables(final Map env) { } env.put("ENABLE_INLINE_COMPLETION", "true"); env.put("ENABLE_TOKEN_PROVIDER", "true"); + env.put("AWS_Q_ENDPOINT_URL", "https://rts.gamma-us-east-1.codewhisperer.ai.aws.dev/"); } @Override diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java new file mode 100644 index 000000000..cbf038533 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java @@ -0,0 +1,6 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public record EventIdentifier(String id) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java new file mode 100644 index 000000000..24fdec906 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java @@ -0,0 +1,6 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public record NotificationAction(String text, String type) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java new file mode 100644 index 000000000..27ae710a4 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java @@ -0,0 +1,6 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public record NotificationContent(String text, String title) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java new file mode 100644 index 000000000..76168d0f1 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java @@ -0,0 +1,15 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +import java.util.List; + +import org.eclipse.lsp4j.MessageType; + +public record NotificationParams( + MessageType type, + NotificationContent content, + String id, + List actions +) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java new file mode 100644 index 000000000..b95bf10b6 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java @@ -0,0 +1,160 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.notification; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Collections; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + + +import software.aws.toolkits.eclipse.amazonq.plugin.Activator; +import software.aws.toolkits.eclipse.amazonq.lsp.AmazonQLspClient; + +/** + * Maintains client state information for the notification system. + * + * This class tracks both static and dynamic information about the Eclipse environment + * that the notification server uses to determine which notifications to show. + */ +public class NotificationConfiguration{ + + // Singleton, needed one for whole eclipse session + private static NotificationConfiguration instance; + + private final AmazonQLspClient lspClient; + + private final List contexts = new ArrayList<>(); + + private final Map> clientStates = new HashMap<>(); + + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + private final AtomicBoolean pendingUpdate = new AtomicBoolean(false); + private static final int DEBOUNCE_DELAY_MS = 1000; //1 sec debounce + + /** + * Gets the singleton instance of NotificationConfiguration. + * + * @param lspClient The LSP client to use for communication with the server + * @return The NotificationConfiguration instance + */ + public static synchronized NotificationConfiguration getInstance(AmazonQLspClient lspClient) { + if (instance == null) { + instance = new NotificationConfiguration(lspClient); + } + return instance; + } + + /** + * Private constructor to enforce singleton pattern. + * + * @param lspClient The LSP client to use for communication + */ + private NotificationConfiguration(AmazonQLspClient lspClient) { + this.lspClient = lspClient; + initializeStaticClientStates(); + } + + /** + * Initialize static client states that won't change during the Eclipse session. + */ + private void initializeStaticClientStates() { + clientStates.put("IDE/VERSION", Collections.singletonList(getEclipseVersion())); + + // Add other static values as needed + // clientStates.put("ANOTHER_STATIC_VALUE", getSomeOtherStaticValue()); + } + + private String getEclipseVersion() { + //placeholder - need to use the actual Eclipse API to get the version + return org.eclipse.core.runtime.Platform.getProduct().getDefiningBundle().getVersion().toString(); + } + + /** + * Add a context to the active contexts list. + * + * @param context The context to add + */ + public synchronized void addContext(String context) { + if (!contexts.contains(context)) { + contexts.add(context); + notifyConfigurationChanged(); + } + } + + /** + * Remove a context from the active contexts list. + * + * @param context The context to remove + */ + public synchronized void removeContext(String context) { + if (contexts.contains(context)) { + contexts.remove(context); + notifyConfigurationChanged(); + } + } + + /** + * Set SSO scopes. + * + * @param scopes The list of SSO scopes + */ + public synchronized void setSsoScopes(List scopes) { + List scopesCopy = new ArrayList<>(scopes); + clientStates.put("SSO_SCOPES", scopesCopy); + notifyConfigurationChanged(); + } + + /** + * Get the current notification configuration. + * + * @return A map representing the current notification configuration + */ + public synchronized Map getConfiguration() { + Map config = new HashMap<>(); + config.putAll(clientStates); + config.put("CONTEXT", new ArrayList<>(contexts)); + + return config; + } + + /** + * Notify the server of a configuration change in a try catch block using didChangeConfiguration + */ + private void notifyConfigurationChanged() { + if (pendingUpdate.compareAndSet(false, true)) { + scheduler.schedule(() -> { + try { + lspClient.didChangeConfiguration(null); + Activator.getLogger().info("sent didChangeConfiguration notification"); + } catch (Exception e) { + Activator.getLogger().error("Error sending didChangeConfiguration notification", e); + } finally { + pendingUpdate.set(false); + } + }, DEBOUNCE_DELAY_MS, TimeUnit.MILLISECONDS); + } + } + + /** + * Handles the getConfiguration request from the server. + * + * @param section The configuration section requested + * @return The requested configuration section + */ + public Object handleGetConfiguration(String section) { + if ("notifications".equals(section)) { + return getConfiguration(); + } + + // Handle other sections or return null if not recognized + return null; + } + +} \ No newline at end of file diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ToolkitNotification.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ToolkitNotification.java index 1363930b9..181ccea1e 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ToolkitNotification.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/ToolkitNotification.java @@ -17,7 +17,9 @@ import org.eclipse.ui.PlatformUI; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup; +import software.aws.toolkits.eclipse.amazonq.plugin.Activator; public class ToolkitNotification extends AbstractNotificationPopup { @@ -119,4 +121,45 @@ public final boolean close() { } return super.close(); } -} + + public static void showBlockingNotification(String title, String message) { + Display.getDefault().syncExec(() -> { + MessageDialog dialog = new MessageDialog( + Display.getCurrent().getActiveShell(), + title, + null, + message, + MessageDialog.WARNING, + new String[] {"Acknowledge"}, + 0 + ); + dialog.open(); + }); + } + + public static void showPersistentNotification(String title, String message) { + AbstractNotificationPopup notification = new PersistentToolkitNotification( + Display.getCurrent(), + title, + message, + checked -> { + if (checked) { + Activator.getPluginStore().put("notificationSkipFlag", "true"); + } else { + Activator.getPluginStore().remove("notificationSkipFlag"); + } + } + ); + notification.setDelayClose(-1); + notification.open(); + } + + public static void showTransientNotification(String title, String message) { + AbstractNotificationPopup notification = new ToolkitNotification( + Display.getCurrent(), + title, + message + ); + notification.open(); + } +} \ No newline at end of file