Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to worry about now, but I may try to convert this to showNotifications (if Flare will agree) and send an array of Notification instead of just one.


void didChangeConfiguration(Object object);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -148,6 +152,20 @@ public final CompletableFuture<ShowDocumentResult> 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) {
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ private Map<String, Object> getInitializationOptions(final ClientMetadata metada
Map<String, Object> initOptions = new HashMap<>();
Map<String, Object> awsInitOptions = new HashMap<>();
Map<String, Object> extendedClientInfoOptions = new HashMap<>();
Map<String, Object> awsClientCapabilitiesOptions = new HashMap<>();
Map<String, String> windowOptions = new HashMap<>();
Map<String, String> extensionOptions = new HashMap<>();
extensionOptions.put("name", USER_AGENT_CLIENT_NAME);
extensionOptions.put("version", metadata.getPluginVersion());
extendedClientInfoOptions.put("extension", extensionOptions);
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public QLspConnectionProvider() throws IOException {
var serverCommand = Paths.get(lspInstallResult.getServerDirectory(), lspInstallResult.getServerCommand());
List<String> commands = new ArrayList<>();
commands.add(serverCommand.toString());
commands.add(lspInstallResult.getServerCommandArgs());
//commands.add(lspInstallResult.getServerCommandArgs());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to add a comment here about you're doing this for development on the feature branch and this should not be checked into main. We don't want this making it into prod.

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);
Expand All @@ -55,6 +58,7 @@ protected final void addEnvironmentVariables(final Map<String, String> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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) { }
Original file line number Diff line number Diff line change
@@ -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) { }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notification actions may include a URI property (wait for the Flare protocol updates).

Original file line number Diff line number Diff line change
@@ -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) { }
Original file line number Diff line number Diff line change
@@ -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<NotificationAction> actions
) { }
Original file line number Diff line number Diff line change
@@ -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<String> contexts = new ArrayList<>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a HashSet for contexts.


private final Map<String, List<String>> 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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider splitting this to explicit instantiation instead of lazy instantiation so that not all code that wants to call getInstance must also have an instance of AmazonQLspClient easily available as well.

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()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider defining these as constants in a single location (e.g. constants at the top of this class or a separate NotificationClientStates class) so they're easily discoverable and documentable in the code. The runbook will want to link to the code for explicit client state definitions and descriptions rather than being reproduced (yet another place to keep in sync) in the runbook.

Also consider camelCase for the literal client state names as people tend not to like SCREAMING_SNAKE_CASE anymore.


// 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<String> scopes) {
List<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<String, Object> getConfiguration() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this needs to be public. Will client code need this?

The format of this will be a little different, but doesn't need to be updated now.

Map<String, Object> 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");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: Capitalize "sent"

} 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;
}

}
Loading