From 07309750101d624c8a336363455225eedf9be4a9 Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Tue, 4 Mar 2025 14:31:21 -0800 Subject: [PATCH 1/7] Initial implementation of notifications feature without connection to local server --- .../eclipse/amazonq/lsp/AmazonQLspClient.java | 4 ++ .../amazonq/lsp/AmazonQLspClientImpl.java | 43 +++++++++++++++++++ .../amazonq/lsp/model/EventIdentifier.java | 3 ++ .../amazonq/lsp/model/NotificationAction.java | 3 ++ .../lsp/model/NotificationContent.java | 4 ++ .../amazonq/lsp/model/NotificationParams.java | 13 ++++++ 6 files changed, 70 insertions(+) create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java 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..aa8e33839 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,8 @@ public interface AmazonQLspClient extends LanguageClient { @JsonNotification("aws/identity/ssoTokenChanged") void ssoTokenChanged(SsoTokenChangedParams params); + + @JsonNotification("aws/window/showNotification") + void showNotification(NotificationParams params); } 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..4088c1860 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,10 @@ 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 org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup; +import software.aws.toolkits.eclipse.amazonq.util.PersistentToolkitNotification; +import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification; + @SuppressWarnings("restriction") public class AmazonQLspClientImpl extends LanguageClientImpl implements AmazonQLspClient { @@ -170,4 +176,41 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) { Activator.getLogger().error("Error processing " + kind + " ssoTokenChanged notification", ex); } } + + @Override + public void showNotification(NotificationParams params) { + Display.getDefault().asyncExec(() -> { + String title = params.content().title() != null ? + params.content().title() : "AWS Notification"; // Default title + String message = params.content().text(); + + // Option 1: Simple popup (non-persistent) + AbstractNotificationPopup notification = new ToolkitNotification( + Display.getCurrent(), + title, + message + ); + notification.open(); + + // Option 2: Persistent notification with "Don't show again" checkbox + // showPersistentNotification(title, message); + }); + } + + + private 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.open(); + } } 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..6de8a6f74 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java @@ -0,0 +1,3 @@ +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public record EventIdentifier(String id) {} \ No newline at end of file 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..47f1a8c23 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java @@ -0,0 +1,3 @@ +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..aa3feb599 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java @@ -0,0 +1,4 @@ +package software.aws.toolkits.eclipse.amazonq.lsp.model; + + +public record NotificationContent(String text, String title) {} \ No newline at end of file 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..e754b65a1 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java @@ -0,0 +1,13 @@ +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +import java.util.List; +import java.util.Optional; + +import org.eclipse.lsp4j.MessageType; + +public record NotificationParams( + MessageType type, + NotificationContent content, + Optional id, + Optional> actions +) { } \ No newline at end of file From ff4e3bf854933426440d73bfa753c350482aed73 Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Wed, 5 Mar 2025 16:14:57 -0800 Subject: [PATCH 2/7] Connection to local server established and removed errors related to Optional being used in declaration --- .../eclipse/amazonq/lsp/AmazonQLspClientImpl.java | 11 ++++++++++- .../eclipse/amazonq/lsp/AmazonQLspServerBuilder.java | 5 +++++ .../lsp/connection/QLspConnectionProvider.java | 6 +++++- .../eclipse/amazonq/lsp/model/NotificationParams.java | 5 ++--- 4 files changed, 22 insertions(+), 5 deletions(-) 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 4088c1860..fdc67cba3 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java @@ -179,11 +179,20 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) { @Override public void showNotification(NotificationParams params) { - Display.getDefault().asyncExec(() -> { + Activator.getLogger().info("Received notification JSON: " + params.toString()); + Display.getDefault().asyncExec(() -> { String title = params.content().title() != null ? params.content().title() : "AWS Notification"; // Default title String message = params.content().text(); + // Null check for actions + if (params.actions() != null) { + // Handle actions if present + } + // Null check for id + if (params.id() != null) { + // Handle id if present + } // Option 1: Simple popup (non-persistent) AbstractNotificationPopup notification = new ToolkitNotification( Display.getCurrent(), 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/NotificationParams.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java index e754b65a1..e0d7ed9bb 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java @@ -1,13 +1,12 @@ package software.aws.toolkits.eclipse.amazonq.lsp.model; import java.util.List; -import java.util.Optional; import org.eclipse.lsp4j.MessageType; public record NotificationParams( MessageType type, NotificationContent content, - Optional id, - Optional> actions + String id, + List actions ) { } \ No newline at end of file From 005b6ee9c9fa0a754bcf7707982535187da5229c Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Tue, 11 Mar 2025 12:34:18 -0700 Subject: [PATCH 3/7] Different types of severity added --- .../amazonq/lsp/AmazonQLspClientImpl.java | 110 +++++++++++------- 1 file changed, 71 insertions(+), 39 deletions(-) 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 fdc67cba3..d824e5bed 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java @@ -21,6 +21,8 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.lsp4j.MessageType; import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment; import software.aws.toolkits.eclipse.amazonq.chat.ChatCommunicationManager; @@ -177,49 +179,79 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) { } } + public enum NotificationSeverity { + LOW, MEDIUM, HIGH + } + @Override public void showNotification(NotificationParams params) { - Activator.getLogger().info("Received notification JSON: " + params.toString()); - Display.getDefault().asyncExec(() -> { + + Activator.getLogger().info("Received notification JSON: " + params.type().toString()); + Display.getDefault().asyncExec(() -> { String title = params.content().title() != null ? - params.content().title() : "AWS Notification"; // Default title + params.content().title() : "AWS Notification"; String message = params.content().text(); - // Null check for actions - if (params.actions() != null) { - // Handle actions if present - } - - // Null check for id - if (params.id() != null) { - // Handle id if present - } - // Option 1: Simple popup (non-persistent) - AbstractNotificationPopup notification = new ToolkitNotification( - Display.getCurrent(), - title, - message - ); - notification.open(); - - // Option 2: Persistent notification with "Don't show again" checkbox - // showPersistentNotification(title, message); + NotificationSeverity severity = determineNotificationSeverity(params.type()); + handleNotification(severity, title, message); }); } - - - private 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.open(); + + private NotificationSeverity determineNotificationSeverity(MessageType type) { + if (type == MessageType.Error) return NotificationSeverity.HIGH; + if (type == MessageType.Warning) return NotificationSeverity.MEDIUM; + if (type == MessageType.Info) return NotificationSeverity.LOW; + return null; + } + + private void handleNotification(NotificationSeverity severity, String title, String message) { + if (severity == null) { + severity = NotificationSeverity.LOW; // Default to LOW if severity is null + Activator.getLogger().info("Null severity detected, defaulting to LOW"); + } + + switch (severity) { + case LOW: + AbstractNotificationPopup transientNotification = new ToolkitNotification( + Display.getCurrent(), title, message + ); + transientNotification.open(); + break; + + case MEDIUM: + AbstractNotificationPopup persistentNotification = new PersistentToolkitNotification( + Display.getCurrent(), + title, + message, + checked -> { + if (checked) { + Activator.getPluginStore().put("notificationSkipFlag", "true"); + } else { + Activator.getPluginStore().remove("notificationSkipFlag"); + } + } + ); + persistentNotification.setDelayClose(60000); + persistentNotification.open(); + break; + + case HIGH: + Display.getDefault().syncExec(() -> { + MessageDialog dialog = new MessageDialog( + Display.getCurrent().getActiveShell(), + title, + null, + message, + MessageDialog.WARNING, + new String[] {"Acknowledge"}, + 0 + ); + dialog.open(); + }); + break; + + default: + Activator.getLogger().warn("Unexpected severity level encountered"); + break; + } } -} +} \ No newline at end of file From a74746748f3d531111aeaf57894170dc18a765e6 Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Tue, 11 Mar 2025 12:58:14 -0700 Subject: [PATCH 4/7] Checkstyle corrections --- .../amazonq/lsp/AmazonQLspClientImpl.java | 38 ++++++++++++------- .../amazonq/lsp/model/EventIdentifier.java | 2 +- .../amazonq/lsp/model/NotificationAction.java | 2 +- .../lsp/model/NotificationContent.java | 2 +- 4 files changed, 28 insertions(+), 16 deletions(-) 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 d824e5bed..e401e912e 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java @@ -178,32 +178,44 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) { Activator.getLogger().error("Error processing " + kind + " ssoTokenChanged notification", ex); } } - public enum NotificationSeverity { LOW, MEDIUM, HIGH } + /** + * Shows a notification to the user with the specified content and severity. + * This method is designed to be overridden by subclasses that need to customize + * the notification behavior. + * + * @param params The notification parameters containing the message type and content + */ @Override - public void showNotification(NotificationParams params) { - - Activator.getLogger().info("Received notification JSON: " + params.type().toString()); + public final void showNotification(final NotificationParams params) { + Activator.getLogger().info("Received notification JSON: " + params.type().toString()); Display.getDefault().asyncExec(() -> { - String title = params.content().title() != null ? - params.content().title() : "AWS Notification"; + String title = params.content().title() != null + ? params.content().title() + : "AWS Notification"; String message = params.content().text(); NotificationSeverity severity = determineNotificationSeverity(params.type()); handleNotification(severity, title, message); }); } - private NotificationSeverity determineNotificationSeverity(MessageType type) { - if (type == MessageType.Error) return NotificationSeverity.HIGH; - if (type == MessageType.Warning) return NotificationSeverity.MEDIUM; - if (type == MessageType.Info) return NotificationSeverity.LOW; - return null; + private NotificationSeverity determineNotificationSeverity(final MessageType type) { + if (type == MessageType.Error) { + return NotificationSeverity.HIGH; + } + if (type == MessageType.Warning) { + return NotificationSeverity.MEDIUM; + } + if (type == MessageType.Info) { + return NotificationSeverity.LOW; + } + return null; } - private void handleNotification(NotificationSeverity severity, String title, String message) { + private void handleNotification(NotificationSeverity severity, final String title, final String message) { if (severity == null) { severity = NotificationSeverity.LOW; // Default to LOW if severity is null Activator.getLogger().info("Null severity detected, defaulting to LOW"); @@ -254,4 +266,4 @@ private void handleNotification(NotificationSeverity severity, String title, Str break; } } -} \ No newline at end of file +} 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 index 6de8a6f74..8e34b3ec0 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java @@ -1,3 +1,3 @@ package software.aws.toolkits.eclipse.amazonq.lsp.model; -public record EventIdentifier(String id) {} \ No newline at end of file +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 index 47f1a8c23..ec76451c4 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java @@ -1,3 +1,3 @@ package software.aws.toolkits.eclipse.amazonq.lsp.model; -public record NotificationAction(String text, String type) {} +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 index aa3feb599..a8fedfc4e 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java @@ -1,4 +1,4 @@ package software.aws.toolkits.eclipse.amazonq.lsp.model; -public record NotificationContent(String text, String title) {} \ No newline at end of file +public record NotificationContent(String text, String title) { } From 85a3f4b8a1974f668b4d4e815a553319c01ff08c Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Wed, 12 Mar 2025 11:36:29 -0700 Subject: [PATCH 5/7] Notification configuration added --- .../NotificationConfiguration.java | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java 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..34b48c0f4 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java @@ -0,0 +1,158 @@ +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 org.eclipse.lsp4j.jsonrpc.services.JsonNotification; + +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", 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) { + String 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 From d89007e57395b671154c63be9e8c21587c6cb32a Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Wed, 12 Mar 2025 11:39:04 -0700 Subject: [PATCH 6/7] Added didChangeConfiguration to AmazonQLspClient.java --- .../aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java | 2 ++ 1 file changed, 2 insertions(+) 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 aa8e33839..a3a54ac8f 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClient.java @@ -24,4 +24,6 @@ public interface AmazonQLspClient extends LanguageClient { @JsonNotification("aws/window/showNotification") void showNotification(NotificationParams params); + void didChangeConfiguration(Object object); + } From 6c5a9cd49f332d3fce8c485f6f10d2c7da7a0c29 Mon Sep 17 00:00:00 2001 From: Aseem Sharma Date: Fri, 14 Mar 2025 12:36:08 -0700 Subject: [PATCH 7/7] Resolved comments on PR#390, Resolved errors in NotificationConfiguration --- .../amazonq/lsp/AmazonQLspClientImpl.java | 120 ++++++------------ .../amazonq/lsp/model/EventIdentifier.java | 3 + .../amazonq/lsp/model/NotificationAction.java | 3 + .../lsp/model/NotificationContent.java | 4 +- .../amazonq/lsp/model/NotificationParams.java | 5 +- .../NotificationConfiguration.java | 10 +- .../amazonq/util/ToolkitNotification.java | 45 ++++++- 7 files changed, 103 insertions(+), 87 deletions(-) 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 e401e912e..0bf4484b9 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java @@ -21,8 +21,6 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.lsp4j.MessageType; import software.amazon.awssdk.services.toolkittelemetry.model.Sentiment; import software.aws.toolkits.eclipse.amazonq.chat.ChatCommunicationManager; @@ -41,8 +39,6 @@ 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 org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup; -import software.aws.toolkits.eclipse.amazonq.util.PersistentToolkitNotification; import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification; @@ -156,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) { @@ -178,92 +188,42 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) { Activator.getLogger().error("Error processing " + kind + " ssoTokenChanged notification", ex); } } - public enum NotificationSeverity { - LOW, MEDIUM, HIGH - } - - /** - * Shows a notification to the user with the specified content and severity. - * This method is designed to be overridden by subclasses that need to customize - * the notification behavior. - * - * @param params The notification parameters containing the message type and content - */ + @Override public final void showNotification(final NotificationParams params) { - Activator.getLogger().info("Received notification JSON: " + params.type().toString()); Display.getDefault().asyncExec(() -> { String title = params.content().title() != null ? params.content().title() - : "AWS Notification"; + : "Notification"; String message = params.content().text(); - NotificationSeverity severity = determineNotificationSeverity(params.type()); - handleNotification(severity, title, message); + + // 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 NotificationSeverity determineNotificationSeverity(final MessageType type) { - if (type == MessageType.Error) { - return NotificationSeverity.HIGH; - } - if (type == MessageType.Warning) { - return NotificationSeverity.MEDIUM; - } - if (type == MessageType.Info) { - return NotificationSeverity.LOW; - } - return null; + private void showHighPriorityNotification(String title, String message) { + ToolkitNotification.showBlockingNotification(title, message); } - private void handleNotification(NotificationSeverity severity, final String title, final String message) { - if (severity == null) { - severity = NotificationSeverity.LOW; // Default to LOW if severity is null - Activator.getLogger().info("Null severity detected, defaulting to LOW"); - } - - switch (severity) { - case LOW: - AbstractNotificationPopup transientNotification = new ToolkitNotification( - Display.getCurrent(), title, message - ); - transientNotification.open(); - break; - - case MEDIUM: - AbstractNotificationPopup persistentNotification = new PersistentToolkitNotification( - Display.getCurrent(), - title, - message, - checked -> { - if (checked) { - Activator.getPluginStore().put("notificationSkipFlag", "true"); - } else { - Activator.getPluginStore().remove("notificationSkipFlag"); - } - } - ); - persistentNotification.setDelayClose(60000); - persistentNotification.open(); - break; - - case HIGH: - Display.getDefault().syncExec(() -> { - MessageDialog dialog = new MessageDialog( - Display.getCurrent().getActiveShell(), - title, - null, - message, - MessageDialog.WARNING, - new String[] {"Acknowledge"}, - 0 - ); - dialog.open(); - }); - break; + private void showMediumPriorityNotification(String title, String message) { + ToolkitNotification.showPersistentNotification(title, message); + } - default: - Activator.getLogger().warn("Unexpected severity level encountered"); - break; - } + 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/model/EventIdentifier.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java index 8e34b3ec0..cbf038533 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/EventIdentifier.java @@ -1,3 +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 index ec76451c4..24fdec906 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationAction.java @@ -1,3 +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 index a8fedfc4e..27ae710a4 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationContent.java @@ -1,4 +1,6 @@ -package software.aws.toolkits.eclipse.amazonq.lsp.model; +// 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 index e0d7ed9bb..76168d0f1 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/NotificationParams.java @@ -1,3 +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; import java.util.List; @@ -9,4 +12,4 @@ public record NotificationParams( NotificationContent content, String id, List actions -) { } \ No newline at end of file +) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java index 34b48c0f4..b95bf10b6 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/notification/NotificationConfiguration.java @@ -1,3 +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.notification; import java.util.ArrayList; @@ -10,7 +13,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; import software.aws.toolkits.eclipse.amazonq.lsp.AmazonQLspClient; @@ -30,7 +32,7 @@ public class NotificationConfiguration{ private final List contexts = new ArrayList<>(); - private final Map clientStates = new HashMap<>(); + private final Map> clientStates = new HashMap<>(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final AtomicBoolean pendingUpdate = new AtomicBoolean(false); @@ -63,7 +65,7 @@ private NotificationConfiguration(AmazonQLspClient lspClient) { * Initialize static client states that won't change during the Eclipse session. */ private void initializeStaticClientStates() { - clientStates.put("IDE/VERSION", getEclipseVersion()); + clientStates.put("IDE/VERSION", Collections.singletonList(getEclipseVersion())); // Add other static values as needed // clientStates.put("ANOTHER_STATIC_VALUE", getSomeOtherStaticValue()); @@ -104,7 +106,7 @@ public synchronized void removeContext(String context) { * @param scopes The list of SSO scopes */ public synchronized void setSsoScopes(List scopes) { - String scopesCopy = new ArrayList<>(scopes); + List scopesCopy = new ArrayList<>(scopes); clientStates.put("SSO_SCOPES", scopesCopy); notifyConfigurationChanged(); } 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