Skip to content

Commit 7e8933a

Browse files
pras0131Paras
andauthored
Trigger customization notifications on ide, stacking multiple notifications vertically and refactor/add support for more methods in PluginStore (#48)
Notifications support (trigger on customization update), vertically stack notifications, add support for getObject/putObject in PluginStore and some refactoring --------- Co-authored-by: Paras <[email protected]>
1 parent 68d772e commit 7e8933a

File tree

6 files changed

+135
-25
lines changed

6 files changed

+135
-25
lines changed

plugin/src/software/aws/toolkits/eclipse/amazonq/configuration/PluginStore.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33

44
package software.aws.toolkits.eclipse.amazonq.configuration;
55

6+
import java.nio.charset.StandardCharsets;
67
import java.util.prefs.BackingStoreException;
78
import java.util.prefs.Preferences;
9+
import com.google.gson.Gson;
810

911
import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;
1012

1113
public final class PluginStore {
1214
private static final Preferences PREFERENCES = Preferences.userRoot().node("software.aws.toolkits.eclipse");
15+
private static final Gson GSON = new Gson();
1316
private PluginStore() {
1417
// Prevent instantiation
1518
}
@@ -31,4 +34,23 @@ public static void remove(final String key) {
3134
PREFERENCES.remove(key);
3235
}
3336

37+
public static <T> void putObject(final String key, final T value) {
38+
String jsonValue = GSON.toJson(value);
39+
byte[] byteValue = jsonValue.getBytes(StandardCharsets.UTF_8);
40+
PREFERENCES.putByteArray(key, byteValue);
41+
try {
42+
PREFERENCES.flush();
43+
} catch (BackingStoreException e) {
44+
PluginLogger.warn(String.format("Error while saving entry to a preference store - key: %s, value: %s", key, value), e);
45+
}
46+
}
47+
48+
public static <T> T getObject(final String key, final Class<T> type) {
49+
byte[] byteValue = PREFERENCES.getByteArray(key, null);
50+
if (byteValue == null) {
51+
return null;
52+
}
53+
String jsonValue = new String(byteValue, StandardCharsets.UTF_8);
54+
return GSON.fromJson(jsonValue, type);
55+
}
3456
}

plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspClientImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.HashMap;
88
import java.util.List;
99
import java.util.Map;
10+
import java.util.Objects;
1011
import java.util.concurrent.CompletableFuture;
1112

1213
import org.eclipse.lsp4e.LanguageClientImpl;
@@ -18,6 +19,7 @@
1819
import software.aws.toolkits.eclipse.amazonq.lsp.model.ConnectionMetadata;
1920
import software.aws.toolkits.eclipse.amazonq.lsp.model.SsoProfileData;
2021
import software.aws.toolkits.eclipse.amazonq.util.Constants;
22+
import software.aws.toolkits.eclipse.amazonq.views.model.Customization;
2123
import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;
2224
import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils;
2325

@@ -42,9 +44,9 @@ public final CompletableFuture<List<Object>> configuration(final ConfigurationPa
4244
List<Object> output = new ArrayList<>();
4345
configurationParams.getItems().forEach(item -> {
4446
if (item.getSection().equals(Constants.LSP_CONFIGURATION_KEY)) {
45-
String customizationArn = PluginStore.get(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY);
47+
Customization storedCustomization = PluginStore.getObject(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, Customization.class);
4648
Map<String, String> customization = new HashMap<>();
47-
customization.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, customizationArn);
49+
customization.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, Objects.nonNull(storedCustomization) ? storedCustomization.getArn() : null);
4850
output.add(customization);
4951
} else {
5052
output.add(null);

plugin/src/software/aws/toolkits/eclipse/amazonq/util/Constants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ private Constants() {
1212
public static final String CUSTOMIZATION_STORAGE_INTERNAL_KEY = "aws.q.customization.eclipse";
1313
public static final String LSP_CUSTOMIZATION_CONFIGURATION_KEY = "customization";
1414
public static final String LSP_CONFIGURATION_KEY = "aws.q";
15+
public static final String IDE_CUSTOMIZATION_NOTIFICATION_TITLE = "Amazon Q Customization";
16+
public static final String IDE_CUSTOMIZATION_NOTIFICATION_BODY_TEMPLATE = "Amazon Q inline suggestions are now coming from the %s";
17+
public static final String DEFAULT_Q_FOUNDATION_DISPLAY_NAME = "Amazon Q foundation (Default)";
1518

1619
}

plugin/src/software/aws/toolkits/eclipse/amazonq/util/ToolkitNotification.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import org.eclipse.swt.SWT;
77
import org.eclipse.swt.graphics.Image;
88
import org.eclipse.swt.graphics.Point;
9+
import org.eclipse.swt.graphics.Rectangle;
910
import org.eclipse.swt.layout.GridData;
1011
import org.eclipse.swt.layout.GridLayout;
1112
import org.eclipse.swt.widgets.Composite;
1213
import org.eclipse.swt.widgets.Display;
1314
import org.eclipse.swt.widgets.Label;
15+
import org.eclipse.swt.widgets.Monitor;
1416
import org.eclipse.ui.ISharedImages;
1517
import org.eclipse.ui.PlatformUI;
18+
import java.util.concurrent.CopyOnWriteArrayList;
1619
import org.eclipse.jface.resource.ImageDescriptor;
1720
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup;
1821

@@ -21,6 +24,11 @@ public final class ToolkitNotification extends AbstractNotificationPopup {
2124
private final String title;
2225
private final String description;
2326
private Image infoIcon;
27+
private static CopyOnWriteArrayList<ToolkitNotification> activeNotifications = new CopyOnWriteArrayList<>();
28+
private static final int MAX_WIDTH = 400;
29+
private static final int MIN_HEIGHT = 100;
30+
private static final int PADDING_EDGE = 5;
31+
private static final int NOTIFICATIONS_GAP = 5;
2432

2533
public ToolkitNotification(final Display display, final String title, final String description) {
2634
super(display);
@@ -56,12 +64,50 @@ protected String getPopupShellTitle() {
5664
}
5765

5866
@Override
59-
protected Point getInitialSize() {
60-
return new Point(60, 20);
67+
protected void initializeBounds() {
68+
Rectangle clArea = getPrimaryClientArea();
69+
Point initialSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
70+
int height = Math.max(initialSize.y, MIN_HEIGHT);
71+
int width = Math.min(initialSize.x, MAX_WIDTH);
72+
Point size = new Point(width, height);
73+
// Calculate the position for the new notification
74+
int x = clArea.x + clArea.width - size.x - PADDING_EDGE;
75+
int y = clArea.height + clArea.y - size.y - PADDING_EDGE;
76+
for (ToolkitNotification notification : activeNotifications) {
77+
if (!notification.getShell().isDisposed()) {
78+
y -= notification.getShell().getSize().y + NOTIFICATIONS_GAP;
79+
}
80+
}
81+
getShell().setLocation(x, y);
82+
getShell().setSize(size);
83+
activeNotifications.add(this);
84+
}
85+
86+
private Rectangle getPrimaryClientArea() {
87+
Monitor primaryMonitor = getShell().getDisplay().getPrimaryMonitor();
88+
return primaryMonitor != null ? primaryMonitor.getClientArea() : getShell().getDisplay().getClientArea();
89+
}
90+
91+
private void repositionNotifications() {
92+
Rectangle clArea = getPrimaryClientArea();
93+
Point initialSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
94+
int height = Math.max(initialSize.y, MIN_HEIGHT);
95+
int width = Math.min(initialSize.x, MAX_WIDTH);
96+
int x = clArea.x + clArea.width - width - PADDING_EDGE;
97+
int y = clArea.height + clArea.y - height - PADDING_EDGE;
98+
for (ToolkitNotification notification : activeNotifications) {
99+
if (!notification.getShell().isDisposed()) {
100+
Point size = notification.getShell().getSize();
101+
notification.getShell().setLocation(x, y);
102+
y -= size.y + NOTIFICATIONS_GAP;
103+
}
104+
}
61105
}
62106

63107
@Override
64108
public boolean close() {
109+
activeNotifications.remove(this);
110+
repositionNotifications();
65111
if (this.infoIcon != null && !this.infoIcon.isDisposed()) {
66112
this.infoIcon.dispose();
67113
}

plugin/src/software/aws/toolkits/eclipse/amazonq/views/CustomizationDialog.java

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
import java.util.HashMap;
88
import java.util.List;
99
import java.util.Map;
10+
import java.util.Objects;
1011
import java.util.concurrent.CompletableFuture;
1112
import java.util.concurrent.ExecutionException;
1213

1314
import org.eclipse.jface.dialogs.Dialog;
1415
import org.eclipse.jface.dialogs.IDialogConstants;
1516
import org.eclipse.swt.SWT;
17+
import org.eclipse.swt.events.MouseAdapter;
18+
import org.eclipse.swt.events.MouseEvent;
1619
import org.eclipse.swt.events.SelectionAdapter;
1720
import org.eclipse.swt.events.SelectionEvent;
1821
import org.eclipse.swt.graphics.Font;
@@ -34,7 +37,9 @@
3437
import software.aws.toolkits.eclipse.amazonq.util.Constants;
3538
import software.aws.toolkits.eclipse.amazonq.util.PluginLogger;
3639
import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils;
40+
import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification;
3741
import software.aws.toolkits.eclipse.amazonq.views.model.Customization;
42+
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup;
3843

3944
public final class CustomizationDialog extends Dialog {
4045

@@ -45,7 +50,7 @@ public final class CustomizationDialog extends Dialog {
4550
private Font boldFont;
4651
private List<Customization> customizationsResponse;
4752
private ResponseSelection responseSelection;
48-
private String selectedCustomisationArn;
53+
private Customization selectedCustomization;
4954

5055
public enum ResponseSelection {
5156
AMAZON_Q_FOUNDATION_DEFAULT,
@@ -80,6 +85,10 @@ private final class CustomRadioButton extends Composite {
8085
public Button getRadioButton() {
8186
return radioButton;
8287
}
88+
89+
public Label getTextLabel() {
90+
return textLabel;
91+
}
8392
}
8493

8594
public CustomizationDialog(final Shell parentShell) {
@@ -102,12 +111,19 @@ public ResponseSelection getResponseSelection() {
102111
return this.responseSelection;
103112
}
104113

105-
public void setSelectedCustomizationArn(final String arn) {
106-
this.selectedCustomisationArn = arn;
114+
public void setSelectedCustomization(final Customization customization) {
115+
this.selectedCustomization = customization;
116+
}
117+
118+
public Customization getSelectedCustomization() {
119+
return this.selectedCustomization;
107120
}
108121

109-
public String getSelectedCustomizationArn() {
110-
return this.selectedCustomisationArn;
122+
private void showNotification(final String customizationName) {
123+
AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(),
124+
Constants.IDE_CUSTOMIZATION_NOTIFICATION_TITLE,
125+
String.format(Constants.IDE_CUSTOMIZATION_NOTIFICATION_BODY_TEMPLATE, customizationName));
126+
notification.open();
111127
}
112128

113129
@Override
@@ -118,16 +134,17 @@ protected void createButtonsForButtonBar(final Composite parent) {
118134

119135
@Override
120136
protected void okPressed() {
121-
PluginLogger.info(String.format("Select pressed with responseSelection:%s and selectedArn:%s", this.responseSelection, this.selectedCustomisationArn));
122137
if (this.responseSelection.equals(ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT)) {
123138
PluginStore.remove(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY);
124-
} else if (this.selectedCustomisationArn != null) {
125-
PluginStore.put(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, this.selectedCustomisationArn);
139+
Display.getCurrent().asyncExec(() -> showNotification(Constants.DEFAULT_Q_FOUNDATION_DISPLAY_NAME));
140+
} else if (Objects.nonNull(this.getSelectedCustomization()) && StringUtils.isNotBlank(this.getSelectedCustomization().getName())) {
141+
PluginStore.putObject(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, this.getSelectedCustomization());
126142
Map<String, Object> updatedSettings = new HashMap<>();
127143
Map<String, String> internalMap = new HashMap<>();
128-
internalMap.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, this.selectedCustomisationArn);
144+
internalMap.put(Constants.LSP_CUSTOMIZATION_CONFIGURATION_KEY, this.getSelectedCustomization().getArn());
129145
updatedSettings.put(Constants.LSP_CONFIGURATION_KEY, internalMap);
130146
ThreadingUtils.executeAsyncTask(() -> CustomizationUtil.triggerChangeConfigurationNotification(updatedSettings));
147+
Display.getCurrent().asyncExec(() -> showNotification(String.format("%s customization", this.getSelectedCustomization().getName())));
131148
}
132149
super.okPressed();
133150
}
@@ -179,10 +196,10 @@ private void updateComboOnUIThread(final List<Customization> customizations) {
179196
int defaultSelectedDropdownIndex = -1;
180197
for (int index = 0; index < customizations.size(); index++) {
181198
addFormattedOption(combo, customizations.get(index).getName(), customizations.get(index).getDescription());
182-
combo.setData(String.format("%s", index), customizations.get(index).getArn());
199+
combo.setData(String.format("%s", index), customizations.get(index));
183200
if (this.responseSelection.equals(ResponseSelection.CUSTOMIZATION)
184-
&& StringUtils.isNotBlank(this.selectedCustomisationArn)
185-
&& this.selectedCustomisationArn.equals(customizations.get(index).getArn())) {
201+
&& Objects.nonNull(this.getSelectedCustomization())
202+
&& this.getSelectedCustomization().getArn().equals(customizations.get(index).getArn())) {
186203
defaultSelectedDropdownIndex = index;
187204
}
188205
}
@@ -197,9 +214,9 @@ private void updateComboOnUIThread(final List<Customization> customizations) {
197214
public void widgetSelected(final SelectionEvent e) {
198215
int selectedIndex = combo.getSelectionIndex();
199216
String selectedOption = combo.getItem(selectedIndex);
200-
String selectedCustomizationArn = (String) combo.getData(String.valueOf(selectedIndex));
201-
CustomizationDialog.this.selectedCustomisationArn = selectedCustomizationArn;
202-
PluginLogger.info(String.format("Selected option:%s with arn:%s", selectedOption, selectedCustomizationArn));
217+
Customization selectedCustomization = (Customization) combo.getData(String.valueOf(selectedIndex));
218+
CustomizationDialog.this.setSelectedCustomization(selectedCustomization);
219+
PluginLogger.info(String.format("Selected option:%s with arn:%s", selectedOption, selectedCustomization.getArn()));
203220
}
204221
});
205222
}
@@ -254,7 +271,17 @@ protected Control createDialogArea(final Composite parent) {
254271
public void widgetSelected(final SelectionEvent e) {
255272
customizationButton.getRadioButton().setSelection(false);
256273
responseSelection = ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT;
257-
selectedCustomisationArn = null;
274+
setSelectedCustomization(null);
275+
combo.setEnabled(false);
276+
}
277+
});
278+
defaultAmazonQFoundationButton.getTextLabel().addMouseListener(new MouseAdapter() {
279+
@Override
280+
public void mouseDown(final MouseEvent e) {
281+
customizationButton.getRadioButton().setSelection(false);
282+
defaultAmazonQFoundationButton.getRadioButton().setSelection(true);
283+
responseSelection = ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT;
284+
setSelectedCustomization(null);
258285
combo.setEnabled(false);
259286
}
260287
});
@@ -266,6 +293,15 @@ public void widgetSelected(final SelectionEvent e) {
266293
combo.setEnabled(true);
267294
}
268295
});
296+
customizationButton.getTextLabel().addMouseListener(new MouseAdapter() {
297+
@Override
298+
public void mouseDown(final MouseEvent e) {
299+
defaultAmazonQFoundationButton.getRadioButton().setSelection(false);
300+
customizationButton.getRadioButton().setSelection(true);
301+
responseSelection = ResponseSelection.CUSTOMIZATION;
302+
combo.setEnabled(true);
303+
}
304+
});
269305
createDropdownForCustomizations(container);
270306
createSeparator(container);
271307
return container;

plugin/src/software/aws/toolkits/eclipse/amazonq/views/actions/CustomizationDialogContributionItem.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package software.aws.toolkits.eclipse.amazonq.views.actions;
55

6+
import java.util.Objects;
67
import org.eclipse.jface.action.ContributionItem;
78
import org.eclipse.swt.SWT;
89
import org.eclipse.swt.events.SelectionAdapter;
@@ -13,11 +14,11 @@
1314
import org.eclipse.swt.widgets.Shell;
1415
import org.eclipse.ui.IViewSite;
1516
import jakarta.inject.Inject;
16-
import software.amazon.awssdk.utils.StringUtils;
1717
import software.aws.toolkits.eclipse.amazonq.configuration.PluginStore;
1818
import software.aws.toolkits.eclipse.amazonq.util.AuthStatusChangedListener;
1919
import software.aws.toolkits.eclipse.amazonq.util.Constants;
2020
import software.aws.toolkits.eclipse.amazonq.views.CustomizationDialog;
21+
import software.aws.toolkits.eclipse.amazonq.views.model.Customization;
2122
import software.aws.toolkits.eclipse.amazonq.views.CustomizationDialog.ResponseSelection;
2223

2324
public final class CustomizationDialogContributionItem extends ContributionItem implements AuthStatusChangedListener {
@@ -53,13 +54,13 @@ public void fill(final Menu menu, final int index) {
5354
@Override
5455
public void widgetSelected(final SelectionEvent e) {
5556
CustomizationDialog dialog = new CustomizationDialog(shell);
56-
String storedCustomizationArn = PluginStore.get(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY);
57-
if (StringUtils.isBlank(storedCustomizationArn)) {
57+
Customization storedCustomization = PluginStore.getObject(Constants.CUSTOMIZATION_STORAGE_INTERNAL_KEY, Customization.class);
58+
if (Objects.isNull(storedCustomization)) {
5859
dialog.setResponseSelection(ResponseSelection.AMAZON_Q_FOUNDATION_DEFAULT);
59-
dialog.setSelectedCustomizationArn(null);
60+
dialog.setSelectedCustomization(null);
6061
} else {
6162
dialog.setResponseSelection(ResponseSelection.CUSTOMIZATION);
62-
dialog.setSelectedCustomizationArn(storedCustomizationArn);
63+
dialog.setSelectedCustomization(storedCustomization);
6364
}
6465
dialog.open();
6566
}

0 commit comments

Comments
 (0)