diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index d33a7203..7c500115 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -301,7 +301,14 @@ CompletableFuture.supplyAsync(() -> {
#### Resource Management
-**When** creating SWT resources (fonts, images) → dispose them when done; Colors do not need disposal in modern SWT
+**When** accessing SWT resources (fonts, images) → prefer accessing them via a plug-in's registry; in that case never dispose them
+
+**When** accessing SWT resources via a registry is not possible → you must dispose the resources you create
+yourself: `Image`, `GC`, `Cursor`, `Region`, `Path`, `Pattern`. `Color` and `Font` are auto-disposed in recent
+SWT. Never dispose system colors/fonts, registry-owned images/fonts, or `Display`/`Shell` (framework-managed)
+
+**When** designing a registry → store `ImageDescriptor`s (not eagerly created `Image`s); the registry creates
+and caches the `Image` lazily on first access and disposes it at display shutdown
**When** using shared resources → do not dispose resources from other bundles
@@ -309,6 +316,18 @@ CompletableFuture.supplyAsync(() -> {
**When** using streams/files → use try-with-resources for Eclipse resources (IFile, IDocument)
+##### Image Registries
+
+A plug-in's images can be offered using an `ImageRegistry`. We have `CopilotImages` in
+`com.microsoft.copilot.eclipse.ui` and `CopilotJobsImages` in `com.microsoft.copilot.eclipse.ui.jobs`.
+The registry loads each image once, caches it, and disposes it automatically when the workbench shuts down.
+
+**When** accessing *static* images, always prefer using image registries. Do NOT pass image path `"/icons/...png"`
+string literals around the code base.
+
+**When** disposing → do NOT dispose images obtained from a registry or from shared images; the registry owns
+them. Only dispose images you constructed yourself with `new Image(...)`.
+
#### Error Handling
**When** reporting errors → use Eclipse `IStatus` and `Status` objects
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.classpath b/com.microsoft.copilot.eclipse.ui.jobs.test/.classpath
new file mode 100644
index 00000000..38f401ad
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.classpath
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.project b/com.microsoft.copilot.eclipse.ui.jobs.test/.project
new file mode 100644
index 00000000..9c18f8af
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.project
@@ -0,0 +1,34 @@
+
+
+ com.microsoft.copilot.eclipse.ui.jobs.test
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000..62ef3488
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 00000000..f897a7f1
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF b/com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..54f8cf3e
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: com.microsoft.copilot.eclipse.ui.jobs.test
+Bundle-SymbolicName: com.microsoft.copilot.eclipse.ui.jobs.test;singleton:=true
+Bundle-Version: 0.20.0.qualifier
+Bundle-Vendor: GitHub Copilot
+Bundle-RequiredExecutionEnvironment: JavaSE-17
+Fragment-Host: com.microsoft.copilot.eclipse.ui.jobs
+Automatic-Module-Name: com.microsoft.copilot.eclipse.ui.jobs.test
+Import-Package: org.objenesis;version="[3.4.0,4.0.0)"
+Require-Bundle: junit-jupiter-api;bundle-version="5.10.1",
+ junit-jupiter-params;bundle-version="5.10.1",
+ org.mockito.mockito-core;bundle-version="5.14.2",
+ org.mockito.junit-jupiter;bundle-version="5.10.2",
+ com.microsoft.copilot.eclipse.ui.jobs;bundle-version="0.20.0"
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/build.properties b/com.microsoft.copilot.eclipse.ui.jobs.test/build.properties
new file mode 100644
index 00000000..07c97efe
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/build.properties
@@ -0,0 +1,6 @@
+source.. = src
+output.. = target/classes
+bin.includes = META-INF/,\
+ .,\
+ fragment.xml
+
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml b/com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml
new file mode 100644
index 00000000..0606478f
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml b/com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml
new file mode 100644
index 00000000..7515e98e
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml
@@ -0,0 +1,54 @@
+
+ 4.0.0
+
+ com.microsoft.copilot.eclipse
+ github-copilot-for-eclipse
+ ${copilot-plugin-version}
+
+ com.microsoft.copilot.eclipse.ui.jobs.test
+ eclipse-test-plugin
+ ${base.name} :: UI Jobs Tests
+
+
+ true
+
+
+
+
+ skip-tests-during-ui-probe
+
+
+ probe.script
+
+
+
+
+
+ org.eclipse.tycho
+ tycho-surefire-plugin
+ ${tycho-version}
+
+ true
+
+
+
+
+
+
+
+
+
+
+ org.eclipse.tycho
+ tycho-surefire-plugin
+ ${tycho-version}
+
+ true
+ false
+
+
+
+
+
\ No newline at end of file
diff --git a/com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java b/com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java
new file mode 100644
index 00000000..1df825c9
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs.test/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImagesTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package com.microsoft.copilot.eclipse.ui.jobs;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Verifies that every public constant in {@link CopilotJobsImages} refers to a bundle resource
+ * that actually exists on the classpath.
+ *
+ *
New constants are discovered automatically via reflection; no separate list needs to be
+ * maintained.
+ */
+class CopilotJobsImagesTest {
+
+ static Stream iconPaths() {
+ return Stream.of(CopilotJobsImages.class.getDeclaredFields())
+ .filter(f -> f.getName().startsWith("IMG_"))
+ .map(f -> {
+ try {
+ return Arguments.of(f.getName(), f.get(null));
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Cannot read constant: " + f.getName(), e);
+ }
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("iconPaths")
+ void testIconFileExists(String fieldName, String path) {
+ assertNotNull(CopilotJobsImages.class.getResource("/" + path),
+ "Bundle resource not found for constant " + fieldName + ": " + path);
+ }
+}
diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java
index ec782952..899c455e 100644
--- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java
+++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobs.java
@@ -3,10 +3,12 @@
package com.microsoft.copilot.eclipse.ui.jobs;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@@ -17,11 +19,27 @@
*/
public class CopilotJobs extends AbstractUIPlugin {
+ private static volatile CopilotJobs COPILOT_JOBS_PLUGIN;
+
+ /** Returns the shared plugin instance. */
+ public static CopilotJobs getPlugin() {
+ Assert.isNotNull(COPILOT_JOBS_PLUGIN);
+ return COPILOT_JOBS_PLUGIN;
+ }
+
+ @Override
+ protected void initializeImageRegistry(ImageRegistry registry) {
+ CopilotJobsImages.initialize(registry);
+ }
+
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
+ COPILOT_JOBS_PLUGIN = this;
+ // Explicitly call method from core to ensure core is activated
CopilotCore.getPlugin();
+
Job initWaitJob = new Job("Waiting for Copilot initialization") {
@Override
protected IStatus run(IProgressMonitor monitor) {
@@ -42,5 +60,6 @@ protected IStatus run(IProgressMonitor monitor) {
@Override
public void stop(BundleContext context) throws Exception {
super.stop(context);
+ COPILOT_JOBS_PLUGIN = null;
}
}
diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java
new file mode 100644
index 00000000..0f46a682
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/CopilotJobsImages.java
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package com.microsoft.copilot.eclipse.ui.jobs;
+
+import java.net.URL;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * Centralized access to all static icons in the Copilot Jobs UI bundle.
+ *
+ * Bundle images are owned by the plugin's {@link ImageRegistry} and disposed automatically when
+ * the plugin stops. Callers must not dispose images returned by {@link #getImage(String)}.
+ */
+public final class CopilotJobsImages {
+
+ // Path prefixes
+ private static final String ICONS_ROOT = "icons/";
+ private static final String ICONS_STATUS = ICONS_ROOT + "status/";
+
+ // Icons
+ public static final String IMG_REPO = ICONS_ROOT + "repo.png";
+ public static final String IMG_INFORMATION = ICONS_ROOT + "information.png";
+ public static final String IMG_STATUS_LOADING = ICONS_STATUS + "loading.png";
+ public static final String IMG_STATUS_COMPLETE = ICONS_STATUS + "complete.png";
+
+ private CopilotJobsImages() {
+ // prevent instantiation
+ }
+
+ /**
+ * Returns the plugin's image registry.
+ */
+ static ImageRegistry getImageRegistry() {
+ return CopilotJobs.getPlugin().getImageRegistry();
+ }
+
+ /**
+ * Registers all static icon descriptors. Called once from
+ * {@link CopilotJobs#initializeImageRegistry(ImageRegistry)}.
+ */
+ static void initialize(ImageRegistry registry) {
+ register(registry, IMG_REPO);
+ register(registry, IMG_INFORMATION);
+ register(registry, IMG_STATUS_LOADING);
+ register(registry, IMG_STATUS_COMPLETE);
+ }
+
+ private static void register(ImageRegistry registry, String path) {
+ URL url = CopilotJobsImages.class.getResource("/" + path);
+ ImageDescriptor descriptor = url != null
+ ? ImageDescriptor.createFromURL(url) : ImageDescriptor.getMissingImageDescriptor();
+ registry.put(path, descriptor);
+ }
+
+ /**
+ * Returns the image for the given key. The returned image is owned by the plugin registry;
+ * callers must not dispose it.
+ *
+ * @param key one of the {@code IMG_*} constants defined in this class
+ */
+ public static Image getImage(String key) {
+ return getImageRegistry().get(key);
+ }
+
+ /**
+ * Returns the {@link ImageDescriptor} for the given key. Useful where a descriptor is required,
+ * e.g. for {@code Action.setImageDescriptor()}.
+ *
+ * @param key one of the {@code IMG_*} constants defined in this class
+ */
+ public static ImageDescriptor getImageDescriptor(String key) {
+ return getImageRegistry().getDescriptor(key);
+ }
+
+ /**
+ * Convenience access to Eclipse's workbench shared images.
+ *
+ * @param imageId a constant from {@link ISharedImages}, e.g. {@link ISharedImages#IMG_OBJS_WARN_TSK}
+ */
+ public static Image getSharedImage(String imageId) {
+ return PlatformUI.getWorkbench().getSharedImages().getImage(imageId);
+ }
+
+ /**
+ * Convenience access to Eclipse's workbench shared image descriptors.
+ *
+ * @param imageId a constant from {@link ISharedImages}
+ */
+ public static ImageDescriptor getSharedImageDescriptor(String imageId) {
+ return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(imageId);
+ }
+
+}
diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java
deleted file mode 100644
index fd2220ab..00000000
--- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/utils/UiUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-package com.microsoft.copilot.eclipse.ui.jobs.utils;
-
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.swt.graphics.Image;
-
-/**
- * Utilities for Eclipse UI in the jobs package.
- */
-public class UiUtils {
-
- private UiUtils() {
- // prevent instantiation
- }
-
- /**
- * Builds an image descriptor from a PNG file at the given path.
- *
- * @param path the path to the PNG file
- * @return the image descriptor, or null if the resource is not found
- */
- public static ImageDescriptor buildImageDescriptorFromPngPath(String path) {
- return ImageDescriptor.createFromURL(UiUtils.class.getResource(path));
- }
-
- /**
- * Builds an image from a PNG file at the given path.
- *
- * @param path the path to the PNG file
- * @return the image, or null if the resource is not found
- */
- public static Image buildImageFromPngPath(String path) {
- ImageDescriptor descriptor = buildImageDescriptorFromPngPath(path);
- return descriptor != null ? descriptor.createImage() : null;
- }
-}
diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java
index d1ceb35a..a3352d73 100644
--- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java
+++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java
@@ -42,7 +42,6 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.CopilotCore;
import com.microsoft.copilot.eclipse.core.lsp.CopilotLanguageServerConnection;
@@ -50,9 +49,9 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.SearchPrParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.SearchPrResponse;
import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils;
+import com.microsoft.copilot.eclipse.ui.jobs.CopilotJobsImages;
import com.microsoft.copilot.eclipse.ui.jobs.events.JobsViewEvents;
import com.microsoft.copilot.eclipse.ui.jobs.i18n.Messages;
-import com.microsoft.copilot.eclipse.ui.jobs.utils.UiUtils;
/**
* The view to display coding agent jobs with PRs from GitHub. Shows a 2-level
@@ -83,8 +82,8 @@ public class JobsView {
public void createPartControl(Composite parent) {
parent.setLayout(new GridLayout(1, false));
- this.directoryIcon = UiUtils.buildImageFromPngPath("/icons/repo.png");
- this.informationIcon = UiUtils.buildImageFromPngPath("/icons/information.png");
+ this.directoryIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_REPO);
+ this.informationIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_INFORMATION);
treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
treeViewer.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
@@ -108,19 +107,6 @@ public void createPartControl(Composite parent) {
}
});
- // Add dispose listener to clean up images
- treeViewer.getTree().addDisposeListener(e -> {
- if (directoryIcon != null && !directoryIcon.isDisposed()) {
- directoryIcon.dispose();
- }
- if (informationIcon != null && !informationIcon.isDisposed()) {
- informationIcon.dispose();
- }
- if (treeViewer.getLabelProvider() instanceof PullRequestLabelProvider labelProvider) {
- labelProvider.disposeImages();
- }
- });
-
treeViewer.getTree().addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
@@ -572,7 +558,7 @@ public Image getImage(Object element) {
private Image getStatusIconForLabel(String nodeText) {
if (Messages.jobsView_label_loadingAgentJobs.equals(nodeText)) {
if (this.loadingIcon == null) {
- loadingIcon = UiUtils.buildImageFromPngPath("/icons/status/loading.png");
+ loadingIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_STATUS_LOADING);
}
return this.loadingIcon;
} else if (SPECIAL_STATUS_LABELS.contains(nodeText)) {
@@ -581,40 +567,31 @@ private Image getStatusIconForLabel(String nodeText) {
return directoryIcon;
} else {
// This is an error message or unknown status, use warning icon
- return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ return CopilotJobsImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK);
}
}
private Image getStatusIconForPr(GitHubPullRequestItem pr) {
if (pr == null || pr.copilotWorkStatus() == null) {
- return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ return CopilotJobsImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK);
}
switch (pr.copilotWorkStatus()) {
case done:
if (this.completeIcon == null) {
- completeIcon = UiUtils.buildImageFromPngPath("/icons/status/complete.png");
+ completeIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_STATUS_COMPLETE);
}
return this.completeIcon;
case error:
- return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ return CopilotJobsImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK);
case in_progress:
default:
if (this.loadingIcon == null) {
- loadingIcon = UiUtils.buildImageFromPngPath("/icons/status/loading.png");
+ loadingIcon = CopilotJobsImages.getImage(CopilotJobsImages.IMG_STATUS_LOADING);
}
return this.loadingIcon;
}
}
- public void disposeImages() {
- if (loadingIcon != null && !loadingIcon.isDisposed()) {
- loadingIcon.dispose();
- }
- if (completeIcon != null && !completeIcon.isDisposed()) {
- completeIcon.dispose();
- }
- }
}
-
}
diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java
new file mode 100644
index 00000000..5fda6d5c
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotImagesTest.java
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package com.microsoft.copilot.eclipse.ui;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Verifies that every public constant in {@link CopilotImages} refers to a bundle resource that
+ * actually exists on the classpath.
+ *
+ *
This test is independent of a running Display or plugin registry — it only needs the bundle's
+ * classpath. It guards against typos in icon paths and missing files caused by renames or deletions.
+ * New constants are discovered automatically via reflection; no separate list needs to be maintained.
+ */
+class CopilotImagesTest {
+
+ static Stream iconPaths() {
+ return Stream.of(CopilotImages.class.getDeclaredFields())
+ .filter(f -> f.getName().startsWith("IMG_"))
+ .map(f -> {
+ try {
+ return Arguments.of(f.getName(), f.get(null));
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Cannot read constant: " + f.getName(), e);
+ }
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("iconPaths")
+ void testIconFileExists(String fieldName, String path) {
+ assertNotNull(CopilotImages.class.getResource("/" + path),
+ "Bundle resource not found for constant " + fieldName + ": " + path);
+ }
+}
diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java
index de9f1851..82f8891a 100644
--- a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java
+++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/CopilotUiTests.java
@@ -17,8 +17,9 @@ class CopilotUiTests {
@Test
void testCopilotCoreWakeUp() throws Exception {
- CopilotUi ui = new CopilotUi();
- ui.start(null);
+ Bundle uiBundle = Platform.getBundle("com.microsoft.copilot.eclipse.ui");
+ assertNotNull(uiBundle);
+ uiBundle.start();
Job.getJobManager().join(CopilotCore.INIT_JOB_FAMILY, null);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java
new file mode 100644
index 00000000..100f9e18
--- /dev/null
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotImages.java
@@ -0,0 +1,358 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+package com.microsoft.copilot.eclipse.ui;
+
+import java.net.URL;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+
+import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
+
+/**
+ * Centralized access to all static icons in the Copilot UI bundle and to Eclipse shared images.
+ *
+ * Bundle images are owned by the plugin's {@link ImageRegistry} and disposed automatically when
+ * the plugin stops. Callers must not dispose images returned by {@link #getImage}.
+ */
+public final class CopilotImages {
+
+ // Path prefixes
+ private static final String ICONS_ROOT = "icons/";
+ private static final String ICONS_CHAT = ICONS_ROOT + "chat/";
+ private static final String ICONS_DROPDOWN = ICONS_ROOT + "dropdown/";
+ private static final String ICONS_JOBS = ICONS_ROOT + "jobs/";
+ private static final String ICONS_MCP = ICONS_ROOT + "mcp/";
+ private static final String ICONS_QUOTA = ICONS_ROOT + "quota/";
+ private static final String ICONS_QUICKSTART = "intro/quickstart/";
+ private static final String ICONS_SPINNER = ICONS_ROOT + "spinner/";
+
+ // ---- Root icons ----------------------------------------------------------
+ public static final String IMG_BLANK = ICONS_ROOT + "blank.png";
+ public static final String IMG_CANCEL_STATUS = ICONS_ROOT + "cancel_status.png";
+ public static final String IMG_CLOSE = ICONS_ROOT + "close.png";
+ public static final String IMG_COMPLETE_STATUS = ICONS_ROOT + "complete_status.png";
+ public static final String IMG_EDIT_KEYBOARD_SHORTCUTS = ICONS_ROOT + "edit_keyboard_shortcuts.png";
+ public static final String IMG_EDIT_PREFERENCES = ICONS_ROOT + "edit_preferences.png";
+ public static final String IMG_FEEDBACK_FORUM = ICONS_ROOT + "feedback_forum.png";
+ public static final String IMG_GITHUB_COPILOT = ICONS_ROOT + "github_copilot.png";
+ public static final String IMG_GITHUB_COPILOT_ERROR = ICONS_ROOT + "github_copilot_error.png";
+ public static final String IMG_GITHUB_COPILOT_NOT_AUTHORIZED = ICONS_ROOT + "github_copilot_not_authorized.png";
+ public static final String IMG_GITHUB_COPILOT_NOT_SIGNED_IN = ICONS_ROOT + "github_copilot_not_signed_in.png";
+ public static final String IMG_GITHUB_COPILOT_SIGNED_IN = ICONS_ROOT + "github_copilot_signed_in.png";
+ public static final String IMG_INFORMATION = ICONS_ROOT + "information.png";
+ public static final String IMG_SIGNIN = ICONS_ROOT + "signin.png";
+ public static final String IMG_SIGNOUT = ICONS_ROOT + "signout.png";
+
+ // ---- Chat icons ----------------------------------------------------------
+ public static final String IMG_CHAT_ATTACH_CONTEXT = ICONS_CHAT + "attach_context.png";
+ public static final String IMG_CHAT_BACK_ARROW = ICONS_CHAT + "back_arrow.png";
+ public static final String IMG_CHAT_BACK_ARROW_GREY = ICONS_CHAT + "back_arrow_grey.png";
+ public static final String IMG_CHAT_BREAKPOINT_AUTO = ICONS_CHAT + "breakpoint_auto.png";
+ public static final String IMG_CHAT_BREAKPOINT_AUTO_DISABLED = ICONS_CHAT + "breakpoint_auto_disabled.png";
+ public static final String IMG_CHAT_HISTORY_EDIT = ICONS_CHAT + "chat_history_edit.png";
+ public static final String IMG_CHAT_COPILOT_AVATAR = ICONS_CHAT + "chat_message_copilot_avatar.png";
+ public static final String IMG_CHAT_USER_AVATAR = ICONS_CHAT + "chat_message_user_avatar.png";
+ public static final String IMG_CHATVIEW_ICON_CHAT = ICONS_CHAT + "chatview_icon_chat.png";
+ public static final String IMG_CHATVIEW_ICON_CODE = ICONS_CHAT + "chatview_icon_code.png";
+ public static final String IMG_CHATVIEW_ICON_LOADING = ICONS_CHAT + "chatview_icon_loading.png";
+ public static final String IMG_CHATVIEW_ICON_NOT_AUTHORIZED = ICONS_CHAT + "chatview_icon_not_authorized.png";
+ public static final String IMG_CHATVIEW_ICON_WELCOME = ICONS_CHAT + "chatview_icon_welcome.png";
+ public static final String IMG_CLEAR_TODO = ICONS_CHAT + "clear_todo.png";
+ public static final String IMG_CLEAR_TODO_DISABLED = ICONS_CHAT + "clear_todo_disable.png";
+ public static final String IMG_CHAT_DOWN_ARROW = ICONS_CHAT + "down_arrow.png";
+ public static final String IMG_CHAT_ENTER = ICONS_CHAT + "enter.png";
+ public static final String IMG_CHAT_EYE = ICONS_CHAT + "eye.png";
+ public static final String IMG_CHAT_EYE_DARK = ICONS_CHAT + "eye_dark.png";
+ public static final String IMG_CHAT_EYE_CLOSED = ICONS_CHAT + "eye_closed.png";
+ public static final String IMG_CHAT_EYE_CLOSED_DARK = ICONS_CHAT + "eye_closed_dark.png";
+ public static final String IMG_CHAT_GUTTER_ARROW = ICONS_CHAT + "gutter-arrow.png";
+ public static final String IMG_CHAT_KEEP = ICONS_CHAT + "keep.png";
+ public static final String IMG_CHAT_KEYBOARD_TAB = ICONS_CHAT + "keyboard-tab.png";
+ public static final String IMG_CHAT_RED_NOTICE = ICONS_CHAT + "red_notice.png";
+ public static final String IMG_CHAT_RIGHT_ARROW = ICONS_CHAT + "right_arrow.png";
+ public static final String IMG_CHAT_SEND = ICONS_CHAT + "send.png";
+ public static final String IMG_CHAT_SEND_DISABLED = ICONS_CHAT + "send_disabled.png";
+ public static final String IMG_CHAT_SEND_TO_JOB = ICONS_CHAT + "send_to_job.png";
+ public static final String IMG_CHAT_SEND_TO_JOB_DISABLED = ICONS_CHAT + "send_to_job_disabled.png";
+ public static final String IMG_CHAT_TOOLS = ICONS_CHAT + "tools.png";
+ public static final String IMG_CHAT_TOOLS_DETECTED = ICONS_CHAT + "tools_detected.png";
+ public static final String IMG_CHAT_TOOLS_DISABLED = ICONS_CHAT + "tools_disabled.png";
+
+ // ---- Todo icons ----------------------------------------------------------
+ public static final String IMG_TODOS_FINISH = ICONS_CHAT + "todos_finish.png";
+ public static final String IMG_TODOS_FINISH_DARK = ICONS_CHAT + "todos_finish_dark.png";
+ public static final String IMG_TODOS_RUNNING = ICONS_CHAT + "todos_running.png";
+ public static final String IMG_TODOS_RUNNING_DARK = ICONS_CHAT + "todos_running_dark.png";
+ public static final String IMG_TODOS_WAITING = ICONS_CHAT + "todos_waiting.png";
+ public static final String IMG_TODOS_WAITING_DARK = ICONS_CHAT + "todos_waiting_dark.png";
+
+ // ---- Dropdown icons ------------------------------------------------------
+ public static final String IMG_DROPDOWN_DOWN_ARROW = ICONS_DROPDOWN + "down_arrow.png";
+ public static final String IMG_DROPDOWN_DOWN_ARROW_DARK = ICONS_DROPDOWN + "down_arrow_dark.png";
+ public static final String IMG_DROPDOWN_COMPLETE_STATUS = ICONS_DROPDOWN + "dropdown_complete_status.png";
+ public static final String IMG_DROPDOWN_COMPLETE_STATUS_DARK = ICONS_DROPDOWN + "dropdown_complete_status_dark.png";
+ public static final String IMG_DROPDOWN_WARNING = ICONS_DROPDOWN + "dropdown_warning.png";
+ public static final String IMG_DROPDOWN_WARNING_DARK = ICONS_DROPDOWN + "dropdown_warning_dark.png";
+
+ // ---- Jobs pull-request icons (used from UI bundle Java code) -------------
+ public static final String IMG_JOBS_PULL_REQUEST_BLACK = ICONS_JOBS + "pull_request_black.png";
+ public static final String IMG_JOBS_PULL_REQUEST_WHITE = ICONS_JOBS + "pull_request_white.png";
+
+ // ---- MCP icons -----------------------------------------------------------
+ public static final String IMG_MCP_DEFAULT_ICON = ICONS_MCP + "mcp_default_icon.png";
+ public static final String IMG_MCP_MARKETPLACE_ICON = ICONS_MCP + "mcp_marketplace_icon.png";
+ public static final String IMG_MCP_REGISTRY = ICONS_MCP + "mcp_registry.png";
+ public static final String IMG_MCP_REFRESH = ICONS_MCP + "refresh.png";
+ public static final String IMG_MCP_HISTORY = ICONS_MCP + "history.png";
+ public static final String IMG_MCP_HISTORY_DARK = ICONS_MCP + "history_dark.png";
+ public static final String IMG_MCP_REPOSITORY = ICONS_MCP + "repository.png";
+ public static final String IMG_MCP_REPOSITORY_DARK = ICONS_MCP + "repository_dark.png";
+ public static final String IMG_MCP_UPDATE = ICONS_MCP + "update.png";
+ public static final String IMG_MCP_UPDATE_DARK = ICONS_MCP + "update_dark.png";
+ public static final String IMG_MCP_VERSIONS = ICONS_MCP + "versions.png";
+ public static final String IMG_MCP_VERSIONS_DARK = ICONS_MCP + "versions_dark.png";
+
+ // ---- Quota icons ---------------------------------------------------------
+ public static final String IMG_QUOTA_UPGRADE = ICONS_QUOTA + "upgrade.png";
+ public static final String IMG_QUOTA_USAGE_BLUE = ICONS_QUOTA + "usage_blue.png";
+ public static final String IMG_QUOTA_USAGE_RED = ICONS_QUOTA + "usage_red.png";
+ public static final String IMG_QUOTA_USAGE_YELLOW = ICONS_QUOTA + "usage_yellow.png";
+
+ // ---- Quick Start icons ---------------------------------------------------
+ public static final String IMG_QUICKSTART_AGENT = ICONS_QUICKSTART + "quick_start_agent.png";
+ public static final String IMG_QUICKSTART_ASK = ICONS_QUICKSTART + "quick_start_ask.png";
+ public static final String IMG_QUICKSTART_COMPLETION = ICONS_QUICKSTART + "quick_start_completion.png";
+ public static final String IMG_QUICKSTART_CLOSE_LIGHT = ICONS_QUICKSTART + "close_light.png";
+ public static final String IMG_QUICKSTART_CLOSE_DARK = ICONS_QUICKSTART + "close_dark.png";
+ public static final String IMG_QUICKSTART_CLOSE_HOVER_LIGHT = ICONS_QUICKSTART + "close_hover_light.png";
+ public static final String IMG_QUICKSTART_CLOSE_HOVER_DARK = ICONS_QUICKSTART + "close_hover_dark.png";
+
+ // ---- Spinner frames (1-based index, see getSpinnerFrame) -----------------
+ public static final String IMG_SPINNER_1 = ICONS_SPINNER + "1.png";
+ public static final String IMG_SPINNER_2 = ICONS_SPINNER + "2.png";
+ public static final String IMG_SPINNER_3 = ICONS_SPINNER + "3.png";
+ public static final String IMG_SPINNER_4 = ICONS_SPINNER + "4.png";
+ public static final String IMG_SPINNER_5 = ICONS_SPINNER + "5.png";
+ public static final String IMG_SPINNER_6 = ICONS_SPINNER + "6.png";
+ public static final String IMG_SPINNER_7 = ICONS_SPINNER + "7.png";
+ public static final String IMG_SPINNER_8 = ICONS_SPINNER + "8.png";
+
+ public static final String[] SPINNER_FRAMES = {
+ IMG_SPINNER_1, IMG_SPINNER_2, IMG_SPINNER_3, IMG_SPINNER_4,
+ IMG_SPINNER_5, IMG_SPINNER_6, IMG_SPINNER_7, IMG_SPINNER_8
+ };
+
+ private CopilotImages() {
+ // prevent instantiation
+ }
+
+ /**
+ * Returns the plugin's image registry.
+ */
+ static ImageRegistry getImageRegistry() {
+ return CopilotUi.getPlugin().getImageRegistry();
+ }
+
+ /**
+ * Registers all static icon descriptors. Called once from
+ * {@link CopilotUi#initializeImageRegistry(ImageRegistry)}.
+ */
+ static void initialize(ImageRegistry registry) {
+ register(registry, IMG_BLANK);
+ register(registry, IMG_CANCEL_STATUS);
+ register(registry, IMG_CLOSE);
+ register(registry, IMG_COMPLETE_STATUS);
+ register(registry, IMG_EDIT_KEYBOARD_SHORTCUTS);
+ register(registry, IMG_EDIT_PREFERENCES);
+ register(registry, IMG_FEEDBACK_FORUM);
+ register(registry, IMG_GITHUB_COPILOT);
+ register(registry, IMG_GITHUB_COPILOT_ERROR);
+ register(registry, IMG_GITHUB_COPILOT_NOT_AUTHORIZED);
+ register(registry, IMG_GITHUB_COPILOT_NOT_SIGNED_IN);
+ register(registry, IMG_GITHUB_COPILOT_SIGNED_IN);
+ register(registry, IMG_INFORMATION);
+ register(registry, IMG_SIGNIN);
+ register(registry, IMG_SIGNOUT);
+ register(registry, IMG_CHAT_ATTACH_CONTEXT);
+ register(registry, IMG_CHAT_BACK_ARROW);
+ register(registry, IMG_CHAT_BACK_ARROW_GREY);
+ register(registry, IMG_CHAT_BREAKPOINT_AUTO);
+ register(registry, IMG_CHAT_BREAKPOINT_AUTO_DISABLED);
+ register(registry, IMG_CHAT_HISTORY_EDIT);
+ register(registry, IMG_CHAT_COPILOT_AVATAR);
+ register(registry, IMG_CHAT_USER_AVATAR);
+ register(registry, IMG_CHATVIEW_ICON_CHAT);
+ register(registry, IMG_CHATVIEW_ICON_CODE);
+ register(registry, IMG_CHATVIEW_ICON_LOADING);
+ register(registry, IMG_CHATVIEW_ICON_NOT_AUTHORIZED);
+ register(registry, IMG_CHATVIEW_ICON_WELCOME);
+ register(registry, IMG_CLEAR_TODO);
+ register(registry, IMG_CLEAR_TODO_DISABLED);
+ register(registry, IMG_CHAT_DOWN_ARROW);
+ register(registry, IMG_CHAT_ENTER);
+ register(registry, IMG_CHAT_EYE);
+ register(registry, IMG_CHAT_EYE_DARK);
+ register(registry, IMG_CHAT_EYE_CLOSED);
+ register(registry, IMG_CHAT_EYE_CLOSED_DARK);
+ register(registry, IMG_CHAT_GUTTER_ARROW);
+ register(registry, IMG_CHAT_KEEP);
+ register(registry, IMG_CHAT_KEYBOARD_TAB);
+ register(registry, IMG_CHAT_RED_NOTICE);
+ register(registry, IMG_CHAT_RIGHT_ARROW);
+ register(registry, IMG_CHAT_SEND);
+ register(registry, IMG_CHAT_SEND_DISABLED);
+ register(registry, IMG_CHAT_SEND_TO_JOB);
+ register(registry, IMG_CHAT_SEND_TO_JOB_DISABLED);
+ register(registry, IMG_CHAT_TOOLS);
+ register(registry, IMG_CHAT_TOOLS_DETECTED);
+ register(registry, IMG_CHAT_TOOLS_DISABLED);
+ register(registry, IMG_TODOS_FINISH);
+ register(registry, IMG_TODOS_FINISH_DARK);
+ register(registry, IMG_TODOS_RUNNING);
+ register(registry, IMG_TODOS_RUNNING_DARK);
+ register(registry, IMG_TODOS_WAITING);
+ register(registry, IMG_TODOS_WAITING_DARK);
+ register(registry, IMG_DROPDOWN_DOWN_ARROW);
+ register(registry, IMG_DROPDOWN_DOWN_ARROW_DARK);
+ register(registry, IMG_DROPDOWN_COMPLETE_STATUS);
+ register(registry, IMG_DROPDOWN_COMPLETE_STATUS_DARK);
+ register(registry, IMG_DROPDOWN_WARNING);
+ register(registry, IMG_DROPDOWN_WARNING_DARK);
+ register(registry, IMG_JOBS_PULL_REQUEST_BLACK);
+ register(registry, IMG_JOBS_PULL_REQUEST_WHITE);
+ register(registry, IMG_MCP_DEFAULT_ICON);
+ register(registry, IMG_MCP_MARKETPLACE_ICON);
+ register(registry, IMG_MCP_REGISTRY);
+ register(registry, IMG_MCP_REFRESH);
+ register(registry, IMG_MCP_HISTORY);
+ register(registry, IMG_MCP_HISTORY_DARK);
+ register(registry, IMG_MCP_REPOSITORY);
+ register(registry, IMG_MCP_REPOSITORY_DARK);
+ register(registry, IMG_MCP_UPDATE);
+ register(registry, IMG_MCP_UPDATE_DARK);
+ register(registry, IMG_MCP_VERSIONS);
+ register(registry, IMG_MCP_VERSIONS_DARK);
+ register(registry, IMG_QUOTA_UPGRADE);
+ register(registry, IMG_QUOTA_USAGE_BLUE);
+ register(registry, IMG_QUOTA_USAGE_RED);
+ register(registry, IMG_QUOTA_USAGE_YELLOW);
+ register(registry, IMG_QUICKSTART_AGENT);
+ register(registry, IMG_QUICKSTART_ASK);
+ register(registry, IMG_QUICKSTART_COMPLETION);
+ register(registry, IMG_QUICKSTART_CLOSE_LIGHT);
+ register(registry, IMG_QUICKSTART_CLOSE_DARK);
+ register(registry, IMG_QUICKSTART_CLOSE_HOVER_LIGHT);
+ register(registry, IMG_QUICKSTART_CLOSE_HOVER_DARK);
+ register(registry, IMG_SPINNER_1);
+ register(registry, IMG_SPINNER_2);
+ register(registry, IMG_SPINNER_3);
+ register(registry, IMG_SPINNER_4);
+ register(registry, IMG_SPINNER_5);
+ register(registry, IMG_SPINNER_6);
+ register(registry, IMG_SPINNER_7);
+ register(registry, IMG_SPINNER_8);
+ }
+
+ private static void register(ImageRegistry registry, String path) {
+ URL url = CopilotImages.class.getResource("/" + path);
+ ImageDescriptor descriptor = url != null
+ ? ImageDescriptor.createFromURL(url) : ImageDescriptor.getMissingImageDescriptor();
+ registry.put(path, descriptor);
+ }
+
+ /**
+ * Returns the image for the given key. The returned image is owned by the plugin registry;
+ * callers must not dispose it.
+ *
+ * @param key one of the {@code IMG_*} constants defined in this class
+ */
+ public static Image getImage(String key) {
+ return getImageRegistry().get(key);
+ }
+
+ /**
+ * Returns the {@link ImageDescriptor} for the given key. Useful where a descriptor is required,
+ * e.g. for {@code Action.setImageDescriptor()}.
+ *
+ * @param key one of the {@code IMG_*} constants defined in this class
+ */
+ public static ImageDescriptor getImageDescriptor(String key) {
+ return getImageRegistry().getDescriptor(key);
+ }
+
+ /**
+ * Returns the theme-correct image for icons that come in light/dark variants.
+ * Uses {@link UiUtils#isDarkTheme()} to pick the right key.
+ *
+ *
Does not track live theme changes; callers re-render on theme-change events as needed.
+ *
+ * @param lightKey key to use in light theme
+ * @param darkKey key to use in dark theme
+ */
+ public static Image getThemedImage(String lightKey, String darkKey) {
+ return getImage(UiUtils.isDarkTheme() ? darkKey : lightKey);
+ }
+
+ /**
+ * Returns the theme-correct descriptor for icons that come in light/dark variants.
+ *
+ * @param lightKey key to use in light theme
+ * @param darkKey key to use in dark theme
+ * @see #getThemedImage(String, String)
+ */
+ public static ImageDescriptor getThemedImageDescriptor(String lightKey, String darkKey) {
+ return getImageDescriptor(UiUtils.isDarkTheme() ? darkKey : lightKey);
+ }
+
+ private static String getSpinnerFrameImageKey(int frame) {
+ Assert.isLegal(frame >= 1 && frame <= SPINNER_FRAMES.length,
+ "Spinner frame must be in [1, " + SPINNER_FRAMES.length + "], got: " + frame);
+ return SPINNER_FRAMES[frame - 1];
+ }
+
+ /**
+ * Returns the image for the given 1-based spinner frame (1–8).
+ *
+ * @param frame 1-based index of the spinner frame
+ * @throws IllegalArgumentException if {@code frame} is outside [1, 8]
+ */
+ public static Image getSpinnerFrame(int frame) {
+ return getImage(getSpinnerFrameImageKey(frame));
+ }
+
+ /**
+ * Returns the image descriptor for the given 1-based spinner frame (1–8).
+ *
+ * @param frame 1-based index of the spinner frame
+ * @return the image descriptor for the requested spinner frame
+ * @throws IllegalArgumentException if {@code frame} is outside [1, 8]
+ */
+ public static ImageDescriptor getSpinnerFrameDescriptor(int frame) {
+ return getImageDescriptor(getSpinnerFrameImageKey(frame));
+ }
+
+ /**
+ * Convenience access to Eclipse's workbench shared images.
+ *
+ * @param imageId a constant from {@link ISharedImages}, e.g. {@link ISharedImages#IMG_OBJS_ERROR_TSK}
+ */
+ public static Image getSharedImage(String imageId) {
+ return PlatformUI.getWorkbench().getSharedImages().getImage(imageId);
+ }
+
+ /**
+ * Convenience access to Eclipse's workbench shared image descriptors.
+ *
+ * @param imageId a constant from {@link ISharedImages}
+ */
+ public static ImageDescriptor getSharedImageDescriptor(String imageId) {
+ return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(imageId);
+ }
+
+}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java
index 66c472b4..869d0974 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/CopilotUi.java
@@ -7,6 +7,7 @@
import java.util.concurrent.ExecutionException;
import org.eclipse.core.net.proxy.IProxyService;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
@@ -18,6 +19,7 @@
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
@@ -42,7 +44,7 @@
*/
public class CopilotUi extends AbstractUIPlugin {
- private static CopilotUi COPILOT_UI_PLUGIN = null;
+ private static volatile CopilotUi COPILOT_UI_PLUGIN;
private CopilotStatusManager copilotStatusManager;
private EditorLifecycleListener editorLifecycleListener;
@@ -58,15 +60,24 @@ public class CopilotUi extends AbstractUIPlugin {
*/
public CopilotUi() {
super();
- COPILOT_UI_PLUGIN = this;
}
+ /** Returns the shared plugin instance. */
public static CopilotUi getPlugin() {
+ Assert.isNotNull(COPILOT_UI_PLUGIN);
return COPILOT_UI_PLUGIN;
}
+ @Override
+ protected void initializeImageRegistry(ImageRegistry registry) {
+ CopilotImages.initialize(registry);
+ }
+
@Override
public void start(BundleContext context) throws Exception {
+ super.start(context);
+ COPILOT_UI_PLUGIN = this;
+
// Explicitly call method from core to ensure core is activated
CopilotCore.getPlugin();
@@ -226,6 +237,9 @@ public void stop(BundleContext context) throws Exception {
if (this.chatServiceManager != null) {
this.chatServiceManager.dispose();
}
+
+ super.stop(context);
+ COPILOT_UI_PLUGIN = null;
}
public EditorsManager getEditorsManager() {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java
index 2c98738f..adb22ed9 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java
@@ -70,6 +70,7 @@
import com.microsoft.copilot.eclipse.core.utils.FileUtils;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.UiConstants;
import com.microsoft.copilot.eclipse.ui.chat.contextwindow.ContextSizeDonut;
@@ -336,21 +337,6 @@ private void updateTableLayout(Table table) {
// Update send to job button and send button together
updateButtonsLayout();
-
- this.addDisposeListener(e -> {
- if (mcpToolImage != null && !mcpToolImage.isDisposed()) {
- mcpToolImage.dispose();
- }
- if (mcpToolDisabledImage != null && !mcpToolDisabledImage.isDisposed()) {
- mcpToolDisabledImage.dispose();
- }
- if (mcpToolDetectedImage != null && !mcpToolDetectedImage.isDisposed()) {
- mcpToolDetectedImage.dispose();
- }
- if (redNoticeImage != null && !redNoticeImage.isDisposed()) {
- redNoticeImage.dispose();
- }
- });
}
/**
@@ -437,11 +423,11 @@ public void updateButtonsLayout() {
// Add sendToJob button - only visible if clientPreviewFeaturesEnabled
if (clientPreviewFeaturesEnabled) {
- if (sendToJobImage == null || sendToJobImage.isDisposed()) {
- sendToJobImage = UiUtils.buildImageFromPngPath("/icons/chat/send_to_job.png");
+ if (sendToJobImage == null) {
+ sendToJobImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND_TO_JOB);
}
- if (sendToJobDisabledImage == null || sendToJobDisabledImage.isDisposed()) {
- sendToJobDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/send_to_job_disabled.png");
+ if (sendToJobDisabledImage == null) {
+ sendToJobDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND_TO_JOB_DISABLED);
}
this.sendToJobButton = UiUtils.createIconButton(this.bottomRightButtonsComposite, SWT.PUSH | SWT.FLAT);
@@ -465,8 +451,8 @@ public void widgetSelected(SelectionEvent e) {
// Add toggle button for all modes if it has not been created
if (btnMsgToggle == null || btnMsgToggle.isDisposed()) {
- this.sendImage = UiUtils.buildImageFromPngPath("/icons/chat/send.png");
- this.sendDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/send_disabled.png");
+ this.sendImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND);
+ this.sendDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_SEND_DISABLED);
this.btnMsgToggle = UiUtils.createIconButton(bottomRightButtonsComposite, SWT.PUSH | SWT.FLAT);
boolean isEnabled = !StringUtils.isBlank(this.inputTextViewer.getContent());
this.btnMsgToggle.setEnabled(isEnabled);
@@ -486,14 +472,6 @@ public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
}
}
});
- this.btnMsgToggle.addDisposeListener(e -> {
- if (sendImage != null && !sendImage.isDisposed()) {
- sendImage.dispose();
- }
- if (sendDisabledImage != null && !sendDisabledImage.isDisposed()) {
- sendDisabledImage.dispose();
- }
- });
AccessibilityUtils.addAccessibilityNameForUiComponent(this.btnMsgToggle,
Messages.chat_actionBar_sendButton_Tooltip);
}
@@ -537,14 +515,14 @@ private void setUpChatModePicker(Composite parent) {
}
private void setUpMcpToolButtonInControlBar(Composite parent) {
- if (mcpToolImage == null || mcpToolImage.isDisposed()) {
- mcpToolImage = UiUtils.buildImageFromPngPath("/icons/chat/tools.png");
+ if (mcpToolImage == null) {
+ mcpToolImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS);
}
- if (mcpToolDisabledImage == null || mcpToolDisabledImage.isDisposed()) {
- mcpToolDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/tools_disabled.png");
+ if (mcpToolDisabledImage == null) {
+ mcpToolDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS_DISABLED);
}
- if (mcpToolDetectedImage == null || mcpToolDetectedImage.isDisposed()) {
- mcpToolDetectedImage = UiUtils.buildImageFromPngPath("/icons/chat/tools_detected.png");
+ if (mcpToolDetectedImage == null) {
+ mcpToolDetectedImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS_DETECTED);
}
this.mcpToolButton = UiUtils.createIconButton(parent, SWT.PUSH | SWT.FLAT);
@@ -584,11 +562,11 @@ private void setUpContextSizeDonutInControlBar(Composite parent) {
}
private void setUpAutoBreakpointButtonInControlBar(Composite parent) {
- if (autoBreakpointImage == null || autoBreakpointImage.isDisposed()) {
- autoBreakpointImage = UiUtils.buildImageFromPngPath("/icons/chat/breakpoint_auto.png");
+ if (autoBreakpointImage == null) {
+ autoBreakpointImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_BREAKPOINT_AUTO);
}
- if (autoBreakpointDisabledImage == null || autoBreakpointDisabledImage.isDisposed()) {
- autoBreakpointDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/breakpoint_auto_disabled.png");
+ if (autoBreakpointDisabledImage == null) {
+ autoBreakpointDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_BREAKPOINT_AUTO_DISABLED);
}
this.autoBreakpointButton = UiUtils.createIconButton(parent, SWT.CHECK | SWT.FLAT);
@@ -884,7 +862,7 @@ private void updateButtonState(SendOrCancelButtonStates state) {
break;
case CANCEL_ENABLED:
isSendButton = false;
- Image cancelImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ELCL_STOP);
+ Image cancelImage = CopilotImages.getSharedImage(ISharedImages.IMG_ELCL_STOP);
updateSendOrCancelMsgBtn(true, cancelImage, Messages.chat_actionBar_cancelButton_Tooltip);
updateSendToJobBtn(false);
break;
@@ -1090,8 +1068,8 @@ private void showMcpToolContextMenu() {
// First menu item
MenuItem approvalItem = new MenuItem(contextMenu, SWT.NONE);
approvalItem.setText(Messages.chat_actionBar_toolButton_detected_toolTip);
- if (redNoticeImage == null || redNoticeImage.isDisposed()) {
- redNoticeImage = UiUtils.buildImageFromPngPath("/icons/chat/red_notice.png");
+ if (redNoticeImage == null) {
+ redNoticeImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RED_NOTICE);
}
approvalItem.setImage(redNoticeImage);
approvalItem.addSelectionListener(new SelectionAdapter() {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java
index a9091824..3c023fd0 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java
@@ -27,6 +27,7 @@
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
@@ -50,7 +51,7 @@ public AddContextButton(Composite parent) {
setLayout(layout);
btnAttachIcon = UiUtils.createIconButton(this, SWT.PUSH | SWT.FLAT);
- Image attachImage = UiUtils.buildImageFromPngPath("/icons/chat/attach_context.png");
+ Image attachImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_ATTACH_CONTEXT);
btnAttachIcon.setImage(attachImage);
String attachTooltip = Messages.chat_addContext_tooltip;
btnAttachIcon.setToolTipText(attachTooltip);
@@ -61,11 +62,6 @@ public void widgetSelected(SelectionEvent e) {
openFilePickerAndAddFiles();
}
});
- btnAttachIcon.addDisposeListener(e -> {
- if (attachImage != null && !attachImage.isDisposed()) {
- attachImage.dispose();
- }
- });
}
/**
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java
index fee673d9..10df6cac 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java
@@ -17,6 +17,7 @@
import com.microsoft.copilot.eclipse.core.Constants;
import com.microsoft.copilot.eclipse.core.lsp.protocol.codingagent.CodingAgentMessageRequestParams;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
@@ -55,15 +56,10 @@ private void buildMessageLabelWithIcon(CodingAgentMessageRequestParams params) {
// Pull request icon
Label iconLabel = new Label(titleComposite, SWT.TOP);
- Image prImage = UiUtils.isDarkTheme() ? UiUtils.buildImageFromPngPath("/icons/jobs/pull_request_white.png")
- : UiUtils.buildImageFromPngPath("/icons/jobs/pull_request_black.png");
+ Image prImage = CopilotImages.getThemedImage(CopilotImages.IMG_JOBS_PULL_REQUEST_BLACK,
+ CopilotImages.IMG_JOBS_PULL_REQUEST_WHITE);
iconLabel.setImage(prImage);
iconLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
- iconLabel.addDisposeListener(e -> {
- if (prImage != null && !prImage.isDisposed()) {
- prImage.dispose();
- }
- });
// Title label
ChatMarkupViewer titleLabel = new ChatMarkupViewer(titleComposite, SWT.LEFT | SWT.WRAP);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java
index 4979bfff..a19cc3be 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java
@@ -16,10 +16,10 @@
import org.osgi.service.event.EventHandler;
import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
import com.microsoft.copilot.eclipse.ui.swt.SpinnerAnimator;
import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A label with icon that displays the running status of the agent.
@@ -49,12 +49,6 @@ public AgentStatusLabel(Composite parent, int style) {
setLayout(layout);
setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.addDisposeListener(e -> {
- if (this.completedIcon != null && !this.completedIcon.isDisposed()) {
- this.completedIcon.dispose();
- }
- if (this.cancelledIcon != null && !this.cancelledIcon.isDisposed()) {
- this.cancelledIcon.dispose();
- }
if (this.eventBroker != null) {
this.eventBroker.unsubscribe(cancelStatusHandler);
}
@@ -82,7 +76,7 @@ public void setCompletedStatus(String statusText) {
spinner.stop();
if (this.completedIcon == null) {
- this.completedIcon = UiUtils.buildImageFromPngPath("/icons/complete_status.png");
+ this.completedIcon = CopilotImages.getImage(CopilotImages.IMG_COMPLETE_STATUS);
}
iconLabel.setImage(completedIcon);
@@ -109,7 +103,7 @@ public void setErrorStatus() {
if (this.status == Status.RUNNING) {
spinner.stop();
}
- iconLabel.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK));
+ iconLabel.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_ERROR_TSK));
this.status = Status.ERROR;
}
@@ -121,7 +115,7 @@ public void setCancelledStatus() {
spinner.stop();
if (this.cancelledIcon == null) {
- this.cancelledIcon = UiUtils.buildImageFromPngPath("/icons/cancel_status.png");
+ this.cancelledIcon = CopilotImages.getImage(CopilotImages.IMG_CANCEL_STATUS);
}
iconLabel.setImage(cancelledIcon);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java
index 4f7b1e5d..b8cff619 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentToolCancelLabel.java
@@ -10,8 +10,8 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A label with icon that displays the cancel status of the agent tool.
@@ -30,7 +30,7 @@ public AgentToolCancelLabel(Composite parent, int style, String cancelMessage) {
setLayout(new GridLayout(2, false));
setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
- this.cancelIcon = UiUtils.buildImageFromPngPath("/icons/cancel_status.png");
+ this.cancelIcon = CopilotImages.getImage(CopilotImages.IMG_CANCEL_STATUS);
Label iconLabel = new Label(this, SWT.LEFT);
iconLabel.setImage(this.cancelIcon);
@@ -38,11 +38,5 @@ public AgentToolCancelLabel(Composite parent, int style, String cancelMessage) {
textLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
textLabel.setText(cancelMessage);
textLabel.setData(CssConstants.CSS_CLASS_NAME_KEY, "text-secondary");
-
- this.addDisposeListener(e -> {
- if (this.cancelIcon != null && !this.cancelIcon.isDisposed()) {
- this.cancelIcon.dispose();
- }
- });
}
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java
index 19a9dde2..b9553c45 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CurrentReferencedFile.java
@@ -9,26 +9,22 @@
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A special {@link ReferencedFile} that represents the current file being edited. This is used to represent the current
* file in the chat view, when no file is selected.
*/
public class CurrentReferencedFile extends ReferencedFile {
- private static Image visibleImage = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/eye.png").createImage();
- private static Image invisibleImage = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/eye_closed.png")
- .createImage();
private Label descriptionLabel;
private Label selectionLabel;
@@ -81,9 +77,9 @@ public CurrentReferencedFile(Composite parent) {
*/
public void updateCloseClickBtnIcon(boolean isCurrentFileVisible) {
if (isCurrentFileVisible) {
- setCloseClickBtnIcon(visibleImage);
+ setCloseClickBtnIcon(CopilotImages.getImage(CopilotImages.IMG_CHAT_EYE));
} else {
- setCloseClickBtnIcon(invisibleImage);
+ setCloseClickBtnIcon(CopilotImages.getImage(CopilotImages.IMG_CHAT_EYE_CLOSED));
}
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java
index 9debb663..26b7fb18 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ErrorWidget.java
@@ -9,8 +9,8 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.swt.WrapLabel;
/**
@@ -40,7 +40,7 @@ private void buildWarnLabelWithIcon(String message) {
composite.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
Label icon = new Label(composite, SWT.CENTER);
- icon.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK));
+ icon.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_ERROR_TSK));
WrapLabel label = new WrapLabel(composite, SWT.LEFT);
label.setText(message);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java
index 46a245ee..31ca69a1 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java
@@ -12,9 +12,9 @@
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Display;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.preferences.ByokPreferencePage;
import com.microsoft.copilot.eclipse.ui.swt.DropdownItem;
@@ -30,8 +30,6 @@
*/
public final class ModelPickerGroupsBuilder {
- private static Image warningIcon;
-
private ModelPickerGroupsBuilder() {
}
@@ -140,23 +138,8 @@ private static Image resolveModelIcon(CopilotModel model) {
if (StringUtils.isBlank(model.getDegradationReason())) {
return null;
}
- if (warningIcon == null || warningIcon.isDisposed()) {
- warningIcon = UiUtils.isDarkTheme()
- ? UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_warning_dark.png")
- : UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_warning.png");
- Display display = SwtUtils.getDisplay();
- if (display != null) {
- display.addListener(SWT.Dispose, event -> disposeWarningIcons());
- }
- }
- return warningIcon;
- }
-
- private static void disposeWarningIcons() {
- if (warningIcon != null && !warningIcon.isDisposed()) {
- warningIcon.dispose();
- warningIcon = null;
- }
+ return CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_WARNING,
+ CopilotImages.IMG_DROPDOWN_WARNING_DARK);
}
private static void openManageModelsPreferences() {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java
index e1bf9b25..050d5d48 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ReferencedFile.java
@@ -24,10 +24,10 @@
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import com.microsoft.copilot.eclipse.core.Constants;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
@@ -81,15 +81,9 @@ public void mouseDown(MouseEvent e) {
lblClose.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
setCloseClickAction();
- lblImage = UiUtils.buildImageFromPngPath("/icons/close.png");
+ lblImage = CopilotImages.getImage(CopilotImages.IMG_CLOSE);
setFile(file);
-
- this.addDisposeListener(e -> {
- if (lblImage != null && !lblImage.isDisposed()) {
- lblImage.dispose();
- }
- });
this.setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND));
// Add keyboard support for Enter activation
@@ -202,7 +196,7 @@ public void getName(AccessibleEvent event) {
*/
private void setupUnsupportedFileDisplay() {
// Set warning icon
- lblfileIcon.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK));
+ lblfileIcon.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK));
// Set tooltip with model name
String modelName = CopilotUi.getPlugin().getChatServiceManager().getModelService().getActiveModel()
.getModelName();
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java
index b1e737a8..fb2b3f1f 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java
@@ -29,13 +29,13 @@
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import com.microsoft.copilot.eclipse.core.CopilotCore;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCodeCopyParams;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.UiConstants;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager;
import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils;
@@ -171,7 +171,7 @@ private Composite createActionsComposite() {
layout.pack = true; // Pack the composite tightly
result.setLayout(layout);
- this.copyIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_COPY);
+ this.copyIcon = CopilotImages.getSharedImage(ISharedImages.IMG_TOOL_COPY);
Button copyButton = createActionButton(result, SWT.PUSH | SWT.FLAT, copyIcon, "Copy", "Copy to clipboard");
copyButton.addListener(SWT.Selection, e -> {
String content = this.sourceViewer.getDocument().get();
@@ -184,7 +184,7 @@ private Composite createActionsComposite() {
ImageDescriptor insertDesc = AbstractUIPlugin.imageDescriptorFromPlugin(UiConstants.WORKBENCH_TEXTEDITOR,
UiConstants.INSERT_ICON);
this.insertIcon = Optional.ofNullable(insertDesc).map(desc -> desc.createImage()).orElse(null);
- Image pasteIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_PASTE);
+ Image pasteIcon = CopilotImages.getSharedImage(ISharedImages.IMG_TOOL_PASTE);
Image insertButtonIcon = this.insertIcon != null ? this.insertIcon : pasteIcon;
Button insertButton = createActionButton(result, SWT.PUSH | SWT.FLAT, insertButtonIcon, "Insert",
"Insert into editor");
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java
index ec340b7e..26a257af 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/StaticBanner.java
@@ -18,8 +18,8 @@
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
@@ -64,7 +64,7 @@ public StaticBanner(Composite parent, int style, String message, List handleDispose());
-
setTitle(Messages.thinking_title);
spinner = new SpinnerAnimator(iconLabel);
spinner.start();
@@ -135,7 +134,7 @@ public boolean showCancelled() {
}
stopSpinner();
if (cancelledIcon == null || cancelledIcon.isDisposed()) {
- cancelledIcon = UiUtils.buildImageFromPngPath("/icons/cancel_status.png");
+ cancelledIcon = CopilotImages.getImage(CopilotImages.IMG_CANCEL_STATUS);
}
if (!iconLabel.isDisposed()) {
iconLabel.setImage(cancelledIcon);
@@ -474,14 +473,14 @@ private void updateChevron() {
Image image;
String tooltip;
if (expanded) {
- if (downArrowImage == null || downArrowImage.isDisposed()) {
- downArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/down_arrow.png");
+ if (downArrowImage == null) {
+ downArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_DOWN_ARROW);
}
image = downArrowImage;
tooltip = Messages.thinking_collapseTooltip;
} else {
- if (rightArrowImage == null || rightArrowImage.isDisposed()) {
- rightArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/right_arrow.png");
+ if (rightArrowImage == null) {
+ rightArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RIGHT_ARROW);
}
image = rightArrowImage;
tooltip = Messages.thinking_expandTooltip;
@@ -505,19 +504,4 @@ private void refreshEnclosingScroller() {
}
}
- private void handleDispose() {
- if (cancelledIcon != null && !cancelledIcon.isDisposed()) {
- cancelledIcon.dispose();
- cancelledIcon = null;
- }
- if (downArrowImage != null && !downArrowImage.isDisposed()) {
- downArrowImage.dispose();
- downArrowImage = null;
- }
- if (rightArrowImage != null && !rightArrowImage.isDisposed()) {
- rightArrowImage.dispose();
- rightArrowImage = null;
- }
- }
-
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java
index abaae519..cf6bcd2c 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/TodoListBar.java
@@ -23,6 +23,7 @@
import org.eclipse.swt.widgets.Label;
import com.microsoft.copilot.eclipse.core.lsp.protocol.TodoItem;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService;
import com.microsoft.copilot.eclipse.ui.chat.services.TodoListService;
@@ -59,32 +60,15 @@ public TodoListBar(Composite parent, int style) {
this.todoListService = CopilotUi.getPlugin().getChatServiceManager().getTodoListService();
this.chatFontService = CopilotUi.getPlugin().getChatServiceManager().getChatFontService();
loadStatusImages();
- this.addDisposeListener(e -> disposeStatusImages());
- }
-
- private void disposeStatusImages() {
- if (completedImage != null && !completedImage.isDisposed()) {
- completedImage.dispose();
- }
- if (inProgressImage != null && !inProgressImage.isDisposed()) {
- inProgressImage.dispose();
- }
- if (notStartedImage != null && !notStartedImage.isDisposed()) {
- notStartedImage.dispose();
- }
}
private void loadStatusImages() {
- boolean isDarkTheme = UiUtils.isDarkTheme();
- if (isDarkTheme) {
- completedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_finish_dark.png");
- inProgressImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_running_dark.png");
- notStartedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_waiting_dark.png");
- } else {
- completedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_finish.png");
- inProgressImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_running.png");
- notStartedImage = UiUtils.buildImageFromPngPath("/icons/chat/todos_waiting.png");
- }
+ completedImage = CopilotImages.getThemedImage(CopilotImages.IMG_TODOS_FINISH,
+ CopilotImages.IMG_TODOS_FINISH_DARK);
+ inProgressImage = CopilotImages.getThemedImage(CopilotImages.IMG_TODOS_RUNNING,
+ CopilotImages.IMG_TODOS_RUNNING_DARK);
+ notStartedImage = CopilotImages.getThemedImage(CopilotImages.IMG_TODOS_WAITING,
+ CopilotImages.IMG_TODOS_WAITING_DARK);
}
Image getStatusImage(String status) {
@@ -228,8 +212,6 @@ public void mouseUp(MouseEvent e) {
addMouseListener(clickListener);
updateDisplay();
-
- addDisposeListener(e -> disposeImages());
}
private void createExpandIcon() {
@@ -280,8 +262,8 @@ public void widgetSelected(SelectionEvent e) {
}
private void loadClearButtonImages() {
- clearEnabledImage = UiUtils.buildImageFromPngPath("/icons/chat/clear_todo.png");
- clearDisabledImage = UiUtils.buildImageFromPngPath("/icons/chat/clear_todo_disable.png");
+ clearEnabledImage = CopilotImages.getImage(CopilotImages.IMG_CLEAR_TODO);
+ clearDisabledImage = CopilotImages.getImage(CopilotImages.IMG_CLEAR_TODO_DISABLED);
}
public void updateDisplay() {
@@ -322,13 +304,13 @@ private void updateExpandIcon() {
if (isExpanded) {
if (downArrowImage == null) {
- downArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/down_arrow.png");
+ downArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_DOWN_ARROW);
}
expandIcon.setImage(downArrowImage);
setToolTipText(Messages.todoList_collapseTooltip);
} else {
if (rightArrowImage == null) {
- rightArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/right_arrow.png");
+ rightArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RIGHT_ARROW);
}
expandIcon.setImage(rightArrowImage);
setToolTipText(Messages.todoList_expandTooltip);
@@ -391,24 +373,6 @@ public void updateClearButtonState() {
}
}
- private void disposeImages() {
- if (downArrowImage != null && !downArrowImage.isDisposed()) {
- downArrowImage.dispose();
- downArrowImage = null;
- }
- if (rightArrowImage != null && !rightArrowImage.isDisposed()) {
- rightArrowImage.dispose();
- rightArrowImage = null;
- }
- if (clearEnabledImage != null && !clearEnabledImage.isDisposed()) {
- clearEnabledImage.dispose();
- clearEnabledImage = null;
- }
- if (clearDisabledImage != null && !clearDisabledImage.isDisposed()) {
- clearDisabledImage.dispose();
- clearDisabledImage = null;
- }
- }
}
class TodoListContent extends Composite {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java
index 2582320c..8f4f61c3 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java
@@ -16,9 +16,9 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.chat.QuotaActions.QuotaAction;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
@@ -111,7 +111,7 @@ private void buildWarnLabelWithIcon(String message) {
composite.setLayoutData(new GridData(SWT.LEFT, SWT.NONE, true, false));
Label iconLabel = new Label(composite, SWT.TOP);
- Image warnImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ Image warnImage = CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK);
iconLabel.setImage(warnImage);
GridData iconGd = new GridData(SWT.LEFT, SWT.TOP, false, false);
iconGd.verticalIndent = 4;
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java
index 1c0780a6..b86131aa 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBar.java
@@ -28,6 +28,7 @@
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.WorkbenchLabelProvider;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService;
import com.microsoft.copilot.eclipse.ui.chat.tools.ChangedFile;
@@ -210,15 +211,6 @@ public void mouseUp(MouseEvent e) {
addMouseListener(clickListener);
updateTitleBarButtons();
-
- this.addDisposeListener(e -> {
- if (downArrowImage != null && !downArrowImage.isDisposed()) {
- downArrowImage.dispose();
- }
- if (rightArrowImage != null && !rightArrowImage.isDisposed()) {
- rightArrowImage.dispose();
- }
- });
}
/**
@@ -234,14 +226,14 @@ private void updateExpandCollapseState() {
if (isExpanded) {
// Expanded state: show down arrow and collapse tooltip
if (downArrowImage == null) {
- downArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/down_arrow.png");
+ downArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_DOWN_ARROW);
}
expandIcon.setImage(downArrowImage);
tooltipMessage = NLS.bind(Messages.fileChangeSummary_collapseTooltip, changeFilesTitle);
} else {
// Collapsed state: show right arrow and expand tooltip
if (rightArrowImage == null) {
- rightArrowImage = UiUtils.buildImageFromPngPath("/icons/chat/right_arrow.png");
+ rightArrowImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_RIGHT_ARROW);
}
expandIcon.setImage(rightArrowImage);
tooltipMessage = NLS.bind(Messages.fileChangeSummary_expandTooltip, changeFilesTitle);
@@ -354,7 +346,7 @@ public ChangedFiles(Composite parent, int style, Map {
- if (keepImg != null && !keepImg.isDisposed()) {
- keepImg.dispose();
- }
- });
+ Image keepImg = CopilotImages.getImage(CopilotImages.IMG_CHAT_KEEP);
keepButton.setImage(keepImg);
keepButton.setToolTipText(Messages.fileChangeSummary_keepButton);
GridData keepGridData = new GridData(SWT.END, SWT.CENTER, false, false);
@@ -506,7 +493,7 @@ public void mouseUp(MouseEvent e) {
});
undoButton = new Label(actionsArea, SWT.NONE);
- Image undoImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_UNDO);
+ Image undoImage = CopilotImages.getSharedImage(ISharedImages.IMG_TOOL_UNDO);
undoButton.setImage(undoImage);
undoButton.setToolTipText(Messages.fileChangeSummary_undoButton);
GridData undoGridData = new GridData(SWT.END, SWT.CENTER, false, false);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java
index 77edec36..abfd23ca 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/dialogs/DynamicOauthDialog.java
@@ -24,7 +24,7 @@
import com.microsoft.copilot.eclipse.core.lsp.mcp.McpOauthRequest;
import com.microsoft.copilot.eclipse.core.lsp.mcp.McpOauthRequest.DynamicOauthInput;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
/**
* Dialog for handling Dynamic OAuth requests from MCP servers.
@@ -57,7 +57,7 @@ protected void configureShell(Shell newShell) {
newShell.setText(request.getTitle());
}
- icon = UiUtils.buildImageFromPngPath("/icons/github_copilot.png");
+ icon = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT);
if (icon != null) {
newShell.setImage(icon);
}
@@ -213,10 +213,6 @@ private void disposeResources() {
headerFont.dispose();
headerFont = null;
}
- if (icon != null && !icon.isDisposed()) {
- icon.dispose();
- icon = null;
- }
}
@Override
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java
index 656a6f62..25edb66b 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/AvatarService.java
@@ -23,6 +23,7 @@
import com.microsoft.copilot.eclipse.core.CopilotCore;
import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
@@ -31,9 +32,6 @@
*/
public class AvatarService {
private static final String AVATAR_URL = "https://avatars.githubusercontent.com/%s?s=24&v=4";
- private static final String DEFAULT_COPILOT_AVATAR_NAME = "/icons/chat/chat_message_copilot_avatar.png";
- private static final String DEFAULT_USER_AVATAR_NAME = "/icons/chat/chat_message_user_avatar.png";
-
private Map avatarCache = new ConcurrentHashMap<>();
private Map jobs = new ConcurrentHashMap<>();
@@ -48,8 +46,8 @@ public class AvatarService {
*/
public AvatarService(AuthStatusManager authStatusManager) {
this.authStatusManager = authStatusManager;
- this.defaultGithubAvatar = UiUtils.buildImageFromPngPath(DEFAULT_COPILOT_AVATAR_NAME);
- this.defaultUserAvatar = UiUtils.buildImageFromPngPath(DEFAULT_USER_AVATAR_NAME);
+ this.defaultGithubAvatar = CopilotImages.getImage(CopilotImages.IMG_CHAT_COPILOT_AVATAR);
+ this.defaultUserAvatar = CopilotImages.getImage(CopilotImages.IMG_CHAT_USER_AVATAR);
this.authStatusChangedEventHandler = event -> {
Object property = event.getProperty(IEventBroker.DATA);
if (property instanceof CopilotStatusResult statusResult && statusResult.isSignedIn()) {
@@ -137,8 +135,6 @@ protected IStatus run(IProgressMonitor monitor) {
* Disposes the resources.
*/
public void dispose() {
- defaultGithubAvatar.dispose();
- defaultUserAvatar.dispose();
avatarCache.values().forEach(Image::dispose);
jobs.values().forEach(Job::cancel);
if (this.eventBroker != null) {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java
index 2bfbdc11..67e1b1ca 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/RunInTerminalToolAdapter.java
@@ -25,12 +25,12 @@
import com.microsoft.copilot.eclipse.terminal.api.IRunInTerminalTool;
import com.microsoft.copilot.eclipse.terminal.api.TerminalCommandProcessor;
import com.microsoft.copilot.eclipse.terminal.api.TerminalServiceManager;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.ChatView;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager;
import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService;
import com.microsoft.copilot.eclipse.ui.utils.ResourceUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* Adapter that bridges the UI tool interface with SPI-based terminal implementations.
@@ -199,7 +199,7 @@ public CompletableFuture invoke(Map i
isBackground = Boolean.parseBoolean((String) isBackgroundObj);
}
- impl.setTerminalIconDescriptor(UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png"));
+ impl.setTerminalIconDescriptor(CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT));
String workingDirectory = resolveWorkingDirectory();
return impl.executeCommand(command, isBackground, workingDirectory)
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java
index bb9ff033..e7d5241b 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AfterLoginWelcomeViewer.java
@@ -14,9 +14,9 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.WrapLabel;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A widget that displays a initial chat introduction.
@@ -82,13 +82,8 @@ private void buildMainIconAndLabel() {
iconLabelComposite.setLayout(iconLabelGridlayout);
iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- Image mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_welcome.png");
+ Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_WELCOME);
this.copilotIconLabel = new Label(iconLabelComposite, SWT.NONE);
- this.copilotIconLabel.addDisposeListener(e -> {
- if (mainIcon != null && !mainIcon.isDisposed()) {
- mainIcon.dispose();
- }
- });
this.copilotIconLabel.setImage(mainIcon);
this.copilotIconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
@@ -137,12 +132,7 @@ private void buildInstructions() {
gridData.minimumHeight = 70;
instructionComposite.setLayoutData(gridData);
- Image attachContextIcon = UiUtils.buildImageFromPngPath("/icons/chat/attach_context.png");
- instructionComposite.addDisposeListener(e -> {
- if (attachContextIcon != null && !attachContextIcon.isDisposed()) {
- attachContextIcon.dispose();
- }
- });
+ Image attachContextIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_ATTACH_CONTEXT);
buildLabelWithIcon(instructionComposite, attachContextIcon, Messages.chat_initialChatView_attachContextSuffix);
WrapLabel subLabel = new WrapLabel(instructionComposite, SWT.CENTER);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java
index 0d2d0bac..b58728f2 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/AgentModeViewer.java
@@ -14,9 +14,9 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.WrapLabel;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A widget that displays a initial chat introduction.
@@ -82,13 +82,8 @@ private void buildMainIconAndLabel() {
iconLabelComposite.setLayout(iconLabelGridlayout);
iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- Image mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_welcome.png");
+ Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_WELCOME);
this.copilotIconLabel = new Label(iconLabelComposite, SWT.CENTER);
- this.copilotIconLabel.addDisposeListener(e -> {
- if (mainIcon != null && !mainIcon.isDisposed()) {
- mainIcon.dispose();
- }
- });
this.copilotIconLabel.setImage(mainIcon);
this.copilotIconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
@@ -142,21 +137,11 @@ private void buildInstructions() {
instructionComposite.setLayoutData(gridData);
// configure MCP icon with label
- Image configureMcpIcon = UiUtils.buildImageFromPngPath("/icons/chat/tools.png");
- instructionComposite.addDisposeListener(e -> {
- if (configureMcpIcon != null && !configureMcpIcon.isDisposed()) {
- configureMcpIcon.dispose();
- }
- });
+ Image configureMcpIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS);
buildLabelWithIcon(instructionComposite, configureMcpIcon, Messages.chat_agentModeView_configureMcpSuffix);
// attach context icon with label
- Image attachContextIcon = UiUtils.buildImageFromPngPath("/icons/chat/attach_context.png");
- instructionComposite.addDisposeListener(e -> {
- if (attachContextIcon != null && !attachContextIcon.isDisposed()) {
- attachContextIcon.dispose();
- }
- });
+ Image attachContextIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_ATTACH_CONTEXT);
buildLabelWithIcon(instructionComposite, attachContextIcon, Messages.chat_agentModeView_attachContextSuffix);
}
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java
index 26050db8..8f528ff2 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/BeforeLoginWelcomeViewer.java
@@ -19,6 +19,7 @@
import org.eclipse.ui.handlers.IHandlerService;
import com.microsoft.copilot.eclipse.core.CopilotCore;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
import com.microsoft.copilot.eclipse.ui.swt.WrapLabel;
@@ -113,7 +114,7 @@ private void buildMainIconAndLabel(Composite parent) {
iconLabelComposite.setLayout(iconLabelGridlayout);
iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- this.mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_welcome.png");
+ this.mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_WELCOME);
this.copilotIconLabel = new Label(iconLabelComposite, SWT.CENTER);
this.copilotIconLabel.setImage(mainIcon);
this.copilotIconLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, true));
@@ -139,28 +140,16 @@ private void buildSubComposite(Composite parent) {
subComposite.setLayoutData(gridData);
subComposite.setLayout(new GridLayout(1, true));
- if (this.agentIcon != null && !this.agentIcon.isDisposed()) {
- this.agentIcon.dispose();
- }
- this.agentIcon = UiUtils.buildImageFromPngPath("/icons/github_copilot.png");
+ this.agentIcon = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT);
buildLabelWithIcon(subComposite, agentIcon, Messages.chat_welcomeView_agentSuffix);
- if (this.mcpIcon != null && !this.mcpIcon.isDisposed()) {
- this.mcpIcon.dispose();
- }
- this.mcpIcon = UiUtils.buildImageFromPngPath("/icons/chat/tools.png");
+ this.mcpIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_TOOLS);
buildLabelWithIcon(subComposite, mcpIcon, Messages.chat_welcomeView_mcpSuffix);
- if (this.chatIcon != null && !this.chatIcon.isDisposed()) {
- this.chatIcon.dispose();
- }
- this.chatIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_chat.png");
+ this.chatIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CHAT);
buildLabelWithIcon(subComposite, chatIcon, Messages.chat_welcomeView_chatSuffix);
- if (this.codeIcon != null && !this.codeIcon.isDisposed()) {
- this.codeIcon.dispose();
- }
- this.codeIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_code.png");
+ this.codeIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CODE);
buildLabelWithIcon(subComposite, codeIcon, Messages.chat_welcomeView_completionSuffix);
}
@@ -242,24 +231,9 @@ private void buildFooterLinks() {
@Override
public void dispose() {
- if (this.mainIcon != null) {
- this.mainIcon.dispose();
- }
if (this.mainLabelFont != null) {
this.mainLabelFont.dispose();
}
- if (this.codeIcon != null) {
- this.codeIcon.dispose();
- }
- if (this.chatIcon != null) {
- this.chatIcon.dispose();
- }
- if (this.agentIcon != null) {
- this.agentIcon.dispose();
- }
- if (this.mcpIcon != null) {
- this.mcpIcon.dispose();
- }
super.dispose();
}
}
\ No newline at end of file
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java
index 09c8f0f4..bca8f9ec 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/ChatHistoryViewer.java
@@ -36,6 +36,7 @@
import com.microsoft.copilot.eclipse.core.persistence.ConversationPersistenceManager;
import com.microsoft.copilot.eclipse.core.persistence.ConversationXmlData;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.ConversationUtils;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService;
@@ -76,10 +77,10 @@ public ChatHistoryViewer(Composite parent, int style, List
this.stylingEngine = PlatformUI.getWorkbench().getService(IStylingEngine.class);
this.chatFontService = CopilotUi.getPlugin().getChatServiceManager().getChatFontService();
this.currentConversationId = currentConversationId;
- this.backImage = UiUtils.buildImageFromPngPath(
- UiUtils.isDarkTheme() ? "/icons/chat/back_arrow_grey.png" : "/icons/chat/back_arrow.png");
- this.enterImage = UiUtils.buildImageFromPngPath("/icons/chat/enter.png");
- this.editImage = UiUtils.buildImageFromPngPath("/icons/chat/chat_history_edit.png");
+ this.backImage = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_BACK_ARROW,
+ CopilotImages.IMG_CHAT_BACK_ARROW_GREY);
+ this.enterImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_ENTER);
+ this.editImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_HISTORY_EDIT);
// Assign CSS id for styling
this.setData(CssConstants.CSS_ID_KEY, "chat-history-viewer");
@@ -91,17 +92,6 @@ public ChatHistoryViewer(Composite parent, int style, List
setLayout(layout);
createChatHistoryComposite(conversations, currentConversationId);
- this.addDisposeListener(e -> {
- if (backImage != null && !backImage.isDisposed()) {
- backImage.dispose();
- }
- if (enterImage != null && !enterImage.isDisposed()) {
- enterImage.dispose();
- }
- if (editImage != null && !editImage.isDisposed()) {
- editImage.dispose();
- }
- });
}
/**
@@ -484,7 +474,7 @@ private Composite createActionsComposite(Composite parent, ConversationXmlData c
if (!isCurrentConversation) {
deleteIcon = new Label(actionsComposite, SWT.NONE);
- deleteImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ELCL_REMOVE);
+ deleteImage = CopilotImages.getSharedImage(ISharedImages.IMG_ELCL_REMOVE);
deleteIcon.setImage(deleteImage);
deleteIcon.setToolTipText(Messages.chat_historyView_deleteIcon_tooltip);
deleteIcon.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND));
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java
index b5a6ae25..cf75454f 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/LoadingViewer.java
@@ -13,9 +13,9 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.WrapLabel;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A widget that displays a loading view.
@@ -44,7 +44,7 @@ private void buildMainIconAndLabel() {
iconLabelComposite.setLayout(iconLabelGridlayout);
iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- Image mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_loading.png");
+ Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_LOADING);
Label icon = new Label(iconLabelComposite, SWT.CENTER);
icon.setImage(mainIcon);
icon.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
@@ -62,9 +62,6 @@ private void buildMainIconAndLabel() {
subLabel.setForeground(this.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
this.addDisposeListener(e -> {
- if (mainIcon != null && !mainIcon.isDisposed()) {
- mainIcon.dispose();
- }
if (mainLabelFont != null && !mainLabelFont.isDisposed()) {
mainLabelFont.dispose();
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java
index 01b724d8..332fe7c2 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/viewers/NoSubscriptionViewer.java
@@ -15,6 +15,7 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.swt.WrapLabel;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
@@ -23,7 +24,6 @@
* A widget that displays a no Copilot subscription info.
*/
public class NoSubscriptionViewer extends BaseViewer {
- private Image mainIcon;
private Font mainLabelFont;
/**
@@ -49,7 +49,7 @@ private void buildMainIconAndLabel() {
iconLabelComposite.setLayout(iconLabelGridlayout);
iconLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- this.mainIcon = UiUtils.buildImageFromPngPath("/icons/chat/chatview_icon_not_authorized.png");
+ Image mainIcon = CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_NOT_AUTHORIZED);
Label icon = new Label(iconLabelComposite, SWT.NONE);
icon.setImage(mainIcon);
icon.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
@@ -88,9 +88,6 @@ public void widgetSelected(org.eclipse.swt.events.SelectionEvent event) {
@Override
public void dispose() {
- if (this.mainIcon != null) {
- this.mainIcon.dispose();
- }
if (this.mainLabelFont != null) {
this.mainLabelFont.dispose();
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java
index 7c237936..1e87cdfc 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/MissingTerminalDependenciesDialog.java
@@ -29,10 +29,10 @@
import com.microsoft.copilot.eclipse.core.Constants;
import com.microsoft.copilot.eclipse.core.CopilotCore;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.UiConstants;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* Dialog to inform users about missing terminal dependencies. Provides information about what needs to be installed and
@@ -66,14 +66,9 @@ protected void configureShell(Shell shell) {
}
private void loadIcon(Shell shell) {
- Image icon = UiUtils.buildImageFromPngPath("/icons/github_copilot.png");
+ Image icon = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT);
if (icon != null) {
shell.setImage(icon);
- shell.addDisposeListener(e -> {
- if (!icon.isDisposed()) {
- icon.dispose();
- }
- });
}
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java
index bff5569c..1f0cba10 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/jobs/BaseCopilotDialog.java
@@ -17,6 +17,7 @@
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
@@ -46,15 +47,10 @@ protected void configureShell(Shell newShell) {
* @param shell the shell to set the icon on
*/
protected void loadCopilotIcon(Shell shell) {
- Image dialogTitleImage = UiUtils.buildImageFromPngPath("/icons/github_copilot.png");
+ Image dialogTitleImage = CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT);
if (dialogTitleImage != null) {
shell.setImage(dialogTitleImage);
}
- shell.addDisposeListener(e -> {
- if (dialogTitleImage != null && !dialogTitleImage.isDisposed()) {
- dialogTitleImage.dispose();
- }
- });
}
/**
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java
index 97a1a025..3c435738 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpApprovalDialog.java
@@ -40,10 +40,10 @@
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.chat.services.McpExtensionPointManager.McpRegistrationInfo;
import com.microsoft.copilot.eclipse.ui.preferences.WrappableNoteLabel;
import com.microsoft.copilot.eclipse.ui.utils.TextMateUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* Dialog for approving third-party MCP providers.
@@ -85,13 +85,8 @@ protected void configureShell(Shell newShell) {
}
private void loadImages(Shell shell) {
- approvedImage = UiUtils.buildImageFromPngPath("/icons/chat/keep.png");
- deniedImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE);
- shell.addDisposeListener(e -> {
- if (approvedImage != null && !approvedImage.isDisposed()) {
- approvedImage.dispose();
- }
- });
+ approvedImage = CopilotImages.getImage(CopilotImages.IMG_CHAT_KEEP);
+ deniedImage = CopilotImages.getSharedImage(ISharedImages.IMG_TOOL_DELETE);
}
@Override
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java
index d7d3124b..f315d215 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpRegistryDialog.java
@@ -39,6 +39,7 @@
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerList;
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerResponse;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.preferences.McpPreferencePage;
import com.microsoft.copilot.eclipse.ui.swt.CssConstants;
@@ -91,15 +92,10 @@ protected void configureShell(Shell newShell) {
newShell.setText(Messages.mcpRegistryDialog_mcpRegistry);
newShell.setMinimumSize(MIN_WIDTH, SWT.DEFAULT);
- Image dialogIcon = UiUtils.buildImageFromPngPath("/icons/mcp/mcp_registry.png");
+ Image dialogIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_REGISTRY);
if (dialogIcon != null) {
newShell.setImage(dialogIcon);
}
- newShell.addDisposeListener(e -> {
- if (dialogIcon != null && !dialogIcon.isDisposed()) {
- dialogIcon.dispose();
- }
- });
}
@Override
@@ -443,22 +439,17 @@ private void createHeaderButtons(Composite parent) {
private void createRefreshButton(Composite parent) {
Button refreshButton = new Button(parent, SWT.PUSH);
- Image refreshIcon = UiUtils.buildImageFromPngPath("/icons/mcp/refresh.png");
+ Image refreshIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_REFRESH);
refreshButton.setImage(refreshIcon);
refreshButton.setText(Messages.mcpRegistryDialog_button_refresh);
refreshButton.setToolTipText(Messages.mcpRegistryDialog_button_refresh_tooltip);
refreshButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
refreshButton.addListener(SWT.Selection, e -> refreshServerList());
- refreshButton.addDisposeListener(e -> {
- if (refreshIcon != null && !refreshIcon.isDisposed()) {
- refreshIcon.dispose();
- }
- });
}
private void createChangeUrlButton(Composite parent) {
Button editButton = new Button(parent, SWT.PUSH);
- Image editIcon = UiUtils.buildImageFromPngPath("/icons/edit_preferences.png");
+ Image editIcon = CopilotImages.getImage(CopilotImages.IMG_EDIT_PREFERENCES);
editButton.setImage(editIcon);
editButton.setText(Messages.mcpRegistryDialog_button_changeUrl);
editButton.setToolTipText(Messages.mcpRegistryDialog_button_changeUrl_tooltip);
@@ -467,11 +458,6 @@ private void createChangeUrlButton(Composite parent) {
e -> PreferencesUtil
.createPreferenceDialogOn(getShell(), McpPreferencePage.ID, PreferencesUtils.getAllPreferenceIds(), null)
.open());
- editButton.addDisposeListener(e -> {
- if (editIcon != null && !editIcon.isDisposed()) {
- editIcon.dispose();
- }
- });
}
/**
@@ -531,14 +517,9 @@ private void createBanner(Composite parent) {
// Create icon label on the right
Label iconLabel = new Label(banner, SWT.NONE);
- Image bannerIcon = UiUtils.buildImageFromPngPath("/icons/mcp/mcp_marketplace_icon.png");
+ Image bannerIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_MARKETPLACE_ICON);
if (bannerIcon != null) {
iconLabel.setImage(bannerIcon);
- iconLabel.addDisposeListener(e -> {
- if (!bannerIcon.isDisposed()) {
- bannerIcon.dispose();
- }
- });
}
iconLabel.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java
index 6d36fef4..ffcd08f7 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerDetailDialog.java
@@ -48,6 +48,7 @@
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.Remote;
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerDetail;
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerResponse;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ButtonState;
import com.microsoft.copilot.eclipse.ui.utils.McpUtils;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
@@ -179,15 +180,10 @@ protected void configureShell(Shell newShell) {
: Messages.mcpServerDetailDialog_title;
newShell.setText(title);
- Image dialogIcon = UiUtils.buildImageFromPngPath("/icons/mcp/mcp_registry.png");
+ Image dialogIcon = CopilotImages.getImage(CopilotImages.IMG_MCP_REGISTRY);
if (dialogIcon != null) {
newShell.setImage(dialogIcon);
}
- newShell.addDisposeListener(e -> {
- if (dialogIcon != null && !dialogIcon.isDisposed()) {
- dialogIcon.dispose();
- }
- });
}
@Override
@@ -395,7 +391,8 @@ private void createDescriptionContent(Composite parent) {
private void createVersionContent(Composite parent) {
ServerDetail serverDetail = getServerDetail();
String version = serverDetail == null ? Messages.mcpServer_unknown : serverDetail.version();
- createIconTextRow(parent, UiUtils.isDarkTheme() ? "/icons/mcp/versions_dark.png" : "/icons/mcp/versions.png",
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_VERSIONS, CopilotImages.IMG_MCP_VERSIONS_DARK);
+ createIconTextRow(parent, icon,
Messages.mcpServerDetailDialog_version + " " + version, Messages.mcpServerDetailDialog_version + " " + version);
}
@@ -405,8 +402,8 @@ private void createPublishedContent(Composite parent) {
String detailedDate = getDetailedFormattedDate(publishedAt);
String text = relativeTime != null ? Messages.mcpServerDetailDialog_published + " " + relativeTime
: Messages.mcpServerDetailDialog_noPublishedDate;
- createIconTextRow(parent, UiUtils.isDarkTheme() ? "/icons/mcp/history_dark.png" : "/icons/mcp/history.png", text,
- detailedDate);
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_HISTORY, CopilotImages.IMG_MCP_HISTORY_DARK);
+ createIconTextRow(parent, icon, text, detailedDate);
}
private void createUpdatedContent(Composite parent) {
@@ -415,13 +412,13 @@ private void createUpdatedContent(Composite parent) {
String detailedDate = getDetailedFormattedDate(updatedAt);
String text = relativeTime != null ? Messages.mcpServerDetailDialog_updated + " " + relativeTime
: Messages.mcpServerDetailDialog_noUpdatedDate;
- createIconTextRow(parent, UiUtils.isDarkTheme() ? "/icons/mcp/update_dark.png" : "/icons/mcp/update.png", text,
- detailedDate);
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_UPDATE, CopilotImages.IMG_MCP_UPDATE_DARK);
+ createIconTextRow(parent, icon, text, detailedDate);
}
private void createRepositoryLink(Composite parent) {
- Composite row = createRowWithIcon(parent,
- UiUtils.isDarkTheme() ? "/icons/mcp/repository_dark.png" : "/icons/mcp/repository.png");
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_REPOSITORY, CopilotImages.IMG_MCP_REPOSITORY_DARK);
+ Composite row = createRowWithIcon(parent, icon);
Link repoLink = new Link(row, SWT.NONE);
repoLink.setText("" + Messages.mcpServerDetailDialog_repository + "");
@@ -438,11 +435,10 @@ private void createRepositoryLink(Composite parent) {
}
/**
- * Builds a two-column row with an icon on the left and a text label on the right. Disposes the image when the icon
- * label is disposed.
+ * Builds a two-column row with an icon on the left and a text label on the right.
*/
- private Composite createIconTextRow(Composite parent, String imagePath, String text, String tooltip) {
- Composite row = createRowWithIcon(parent, imagePath);
+ private Composite createIconTextRow(Composite parent, Image icon, String text, String tooltip) {
+ Composite row = createRowWithIcon(parent, icon);
Label textLabel = new Label(row, SWT.NONE);
textLabel.setText(text != null ? text : "");
@@ -454,7 +450,7 @@ private Composite createIconTextRow(Composite parent, String imagePath, String t
return row;
}
- private Composite createRowWithIcon(Composite parent, String imagePath) {
+ private Composite createRowWithIcon(Composite parent, Image icon) {
GridLayout rowLayout = new GridLayout(2, false);
rowLayout.marginWidth = 0;
rowLayout.marginHeight = 0;
@@ -462,14 +458,8 @@ private Composite createRowWithIcon(Composite parent, String imagePath) {
row.setLayout(rowLayout);
Label iconLabel = new Label(row, SWT.NONE);
- final Image icon = UiUtils.buildImageFromPngPath(imagePath);
iconLabel.setImage(icon);
iconLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
- iconLabel.addDisposeListener(e -> {
- if (icon != null && !icon.isDisposed()) {
- icon.dispose();
- }
- });
return row;
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java
index 2c15179e..923a09e9 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/dialogs/mcp/McpServerItem.java
@@ -41,6 +41,7 @@
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.Remote;
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerDetail;
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.ServerResponse;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ActionResult;
import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ActionType;
import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpServerInstallManager.ButtonState;
@@ -191,32 +192,30 @@ private void createMetaInfoRow(Composite parent, ServerDetail detail) {
rowLayout.marginBottom = 0;
metaRow.setLayout(rowLayout);
- boolean isDark = UiUtils.isDarkTheme();
-
// Version
if (StringUtils.isNotBlank(version)) {
- String iconPath = isDark ? "/icons/mcp/versions_dark.png" : "/icons/mcp/versions.png";
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_VERSIONS, CopilotImages.IMG_MCP_VERSIONS_DARK);
String text = Messages.mcpServerDetailDialog_version + " " + version;
- createIconTextLabel(metaRow, iconPath, text, NLS.bind(Messages.mcpServerItem_versionTooltip, version));
+ createIconTextLabel(metaRow, icon, text, NLS.bind(Messages.mcpServerItem_versionTooltip, version));
}
// Published date
if (StringUtils.isNotBlank(publishedAt)) {
- String iconPath = isDark ? "/icons/mcp/history_dark.png" : "/icons/mcp/history.png";
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_HISTORY, CopilotImages.IMG_MCP_HISTORY_DARK);
String relativeTime = formatRelativeTime(publishedAt);
String text = relativeTime != null ? Messages.mcpServerDetailDialog_published + " " + relativeTime
: Messages.mcpServerDetailDialog_noPublishedDate;
- createIconTextLabel(metaRow, iconPath, text,
+ createIconTextLabel(metaRow, icon, text,
NLS.bind(Messages.mcpServerItem_publishedTooltip, formatDetailedDate(publishedAt)));
}
// Updated date
if (StringUtils.isNotBlank(updatedAt)) {
- String iconPath = isDark ? "/icons/mcp/update_dark.png" : "/icons/mcp/update.png";
+ Image icon = CopilotImages.getThemedImage(CopilotImages.IMG_MCP_UPDATE, CopilotImages.IMG_MCP_UPDATE_DARK);
String relativeTime = formatRelativeTime(updatedAt);
String text = relativeTime != null ? Messages.mcpServerDetailDialog_updated + " " + relativeTime
: Messages.mcpServerDetailDialog_noUpdatedDate;
- createIconTextLabel(metaRow, iconPath, text,
+ createIconTextLabel(metaRow, icon, text,
NLS.bind(Messages.mcpServerItem_updatedTooltip, formatDetailedDate(updatedAt)));
}
}
@@ -233,24 +232,16 @@ private OfficialMeta getMetaOfficial() {
return serverResponse.meta().official();
}
- private void createIconTextLabel(Composite parent, String iconPath, String text, String tooltip) {
+ private void createIconTextLabel(Composite parent, Image icon, String text, String tooltip) {
Composite row = new Composite(parent, SWT.NONE);
GridLayout rowLayout = new GridLayout(2, false);
rowLayout.marginWidth = 0;
rowLayout.marginHeight = 0;
row.setLayout(rowLayout);
- // Icon
Label iconLabel = new Label(row, SWT.NONE);
- Image icon = UiUtils.buildImageFromPngPath(iconPath);
iconLabel.setImage(icon);
- iconLabel.addDisposeListener(e -> {
- if (icon != null && !icon.isDisposed()) {
- icon.dispose();
- }
- });
- // Text
Label textLabel = new Label(row, SWT.NONE);
textLabel.setText(text != null ? text : "");
if (tooltip != null) {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java
index 7ff74eec..1ebdb04d 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowContextMenuHandler.java
@@ -18,9 +18,8 @@
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.menus.CommandContributionItemParameter;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.ResourceUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
-
/**
* Handler to show context menu items for adding files or folders to references in package explorer/project explorer.
@@ -32,7 +31,6 @@ protected IContributionItem[] getContributionItems() {
IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
var sel = win != null ? win.getSelectionService().getSelection() : null;
-
if (!(sel instanceof IStructuredSelection s) || s.isEmpty()) {
return new IContributionItem[0];
}
@@ -49,7 +47,7 @@ protected IContributionItem[] getContributionItems() {
items.add(new Separator("com.microsoft.copilot.eclipse.ui.contextMenu.start"));
- ImageDescriptor menuIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png");
+ ImageDescriptor menuIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT);
MenuManager submenu = new MenuManager("Copilot", menuIcon, "com.microsoft.copilot.eclipse.ui.contextMenu");
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java
index 5ea52002..829385dc 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowMenuBarMenuHandler.java
@@ -32,13 +32,13 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan;
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.Quota;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.UiConstants;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.preferences.LanguageServerSettingManager;
import com.microsoft.copilot.eclipse.ui.utils.MenuUtils;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* Handler for showing GitHub Copilot menu bar menu.
@@ -87,7 +87,7 @@ protected IContributionItem[] getContributionItems() {
// menu: username/Sign In
if (CopilotStatusResult.NOT_SIGNED_IN.equals(status)) {
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.signIn", Messages.menu_signToGitHub,
- UiUtils.buildImageDescriptorFromPngPath("/icons/signin.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNIN)));
} else if (CopilotStatusResult.OK.equals(status)) {
String userName = authStatusManager.getUserName();
String planLabel = MenuUtils.getPlanLabel(authStatusManager.getQuotaStatus().copilotPlan());
@@ -102,7 +102,7 @@ protected IContributionItem[] getContributionItems() {
// menu: openChatView
items.add(new Separator());
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.openChatView", Messages.menu_openChatView,
- UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT)));
// menu:(label options) Turn off Completions or Turn on Completions
LanguageServerSettingManager languageServerSettingManager = CopilotUi.getPlugin().getLanguageServerSettingManager();
@@ -111,29 +111,29 @@ protected IContributionItem[] getContributionItems() {
String label = languageServerSettingManager.isAutoShowCompletionEnabled() ? Messages.menu_turnOffCompletions
: Messages.menu_turnOnCompletions;
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.autoShowCompletions", label,
- UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK)));
}
// menu: editKeyboardShortcuts
items.add(new Separator());
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.openEditKeyboardShortcuts",
Messages.menu_editKeyboardShortcuts,
- UiUtils.buildImageDescriptorFromPngPath("/icons/edit_keyboard_shortcuts.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_KEYBOARD_SHORTCUTS)));
// menu: editPreferences
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.openPreferences", Messages.menu_editPreferences,
- UiUtils.buildImageDescriptorFromPngPath("/icons/edit_preferences.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_PREFERENCES)));
// menu: giveFeedback
items.add(new Separator());
Map parameters = Map.of(UiConstants.OPEN_URL_PARAMETER_NAME,
UiConstants.COPILOT_FEEDBACK_FORUM_URL);
items.add(createCommandItem(UiConstants.OPEN_URL_COMMAND_ID, Messages.menu_giveFeedback, parameters,
- UiUtils.buildImageDescriptorFromPngPath("/icons/feedback_forum.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_FEEDBACK_FORUM)));
// menu: whatIsNew
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.showWhatIsNew", Messages.menu_whatIsNew,
- UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK)));
// menu: Copilot settings and Sign Out
addAuthenticationActions(items, status);
@@ -222,7 +222,7 @@ private void addCopilotUsageItemsTbb(List items, CheckQuotaRe
}
// Upsell actions based on the user's plan
- ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png");
+ ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE);
// For non-free users (excluding org-unlimited business/enterprise):
// show "Enable Additional Usage" or "Increase Budget" depending on overage state.
@@ -319,7 +319,7 @@ private void addCopilotUsageItemsLegacy(List items, CheckQuot
}
// Upsell actions based on the user's plan (legacy wording).
- ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png");
+ ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE);
if (plan == CopilotPlan.free) {
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.upgradeCopilotPlan",
Messages.menu_quota_updateCopilotToPro, Messages.menu_quota_updateCopilotToProPlus, upgradeIcon));
@@ -339,7 +339,7 @@ private void addAuthenticationActions(List items, String stat
Messages.menu_configureGitHubCopilotSettings, null));
}
items.add(createCommandItem("com.microsoft.copilot.eclipse.commands.signOut", Messages.menu_signOutOfGitHub,
- UiUtils.buildImageDescriptorFromPngPath("/icons/signout.png")));
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNOUT)));
}
/**
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java
index 178e23e3..e2db3c69 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ShowStatusBarMenuHandler.java
@@ -41,13 +41,13 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan;
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.Quota;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.UiConstants;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.preferences.LanguageServerSettingManager;
import com.microsoft.copilot.eclipse.ui.utils.MenuUtils;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* Handler for showing GitHub Copilot status bar menu.
@@ -198,36 +198,33 @@ public void updateElement(UIElement element, Map parameters) {
return;
} else {
String copilotStatus = authStatusManager.getCopilotStatus();
- String iconPath = null;
+ String iconKey = null;
switch (copilotStatus) {
case CopilotStatusResult.OK:
- iconPath = "/icons/github_copilot_signed_in.png";
+ iconKey = CopilotImages.IMG_GITHUB_COPILOT_SIGNED_IN;
break;
case CopilotStatusResult.LOADING:
scheduleSpinnerJob(element);
return;
case CopilotStatusResult.ERROR, CopilotStatusResult.WARNING:
- iconPath = "/icons/github_copilot_error.png";
+ iconKey = CopilotImages.IMG_GITHUB_COPILOT_ERROR;
break;
case CopilotStatusResult.NOT_AUTHORIZED:
- iconPath = "/icons/github_copilot_not_authorized.png";
+ iconKey = CopilotImages.IMG_GITHUB_COPILOT_NOT_AUTHORIZED;
break;
case CopilotStatusResult.NOT_SIGNED_IN:
default:
- iconPath = "/icons/github_copilot_not_signed_in.png";
+ iconKey = CopilotImages.IMG_GITHUB_COPILOT_NOT_SIGNED_IN;
}
- setIconOnDisplayThread(element, iconPath);
+ setIconOnDisplayThread(element, CopilotImages.getImageDescriptor(iconKey));
}
}
}
- private void setIconOnDisplayThread(UIElement element, String iconPath) {
- if (iconPath != null) {
- SwtUtils.invokeOnDisplayThread(() -> {
- ImageDescriptor newIcon = UiUtils.buildImageDescriptorFromPngPath(iconPath);
- element.setIcon(newIcon);
- });
+ private void setIconOnDisplayThread(UIElement element, ImageDescriptor icon) {
+ if (icon != null) {
+ SwtUtils.invokeOnDisplayThread(() -> element.setIcon(icon));
}
}
@@ -236,7 +233,7 @@ private void addSignInOrUsernameAction(MenuManager menuManager) {
if (CopilotStatusResult.NOT_SIGNED_IN.equals(status)) {
MenuActionFactory.createMenuAction(menuManager, Messages.menu_signToGitHub,
- UiUtils.buildImageDescriptorFromPngPath("/icons/signin.png"), handlerService,
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNIN), handlerService,
"com.microsoft.copilot.eclipse.commands.signIn", true);
} else if (CopilotStatusResult.OK.equals(status)) {
String userName = authStatusManager.getUserName();
@@ -326,7 +323,7 @@ private void addCopilotUsageActionTbb(MenuManager menuManager, CheckQuotaResult
}
// Upsell actions based on the user's plan
- ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png");
+ ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE);
// For non-free users (excluding org-unlimited business/enterprise):
// show "Enable Additional Usage" or "Increase Budget" depending on overage state.
@@ -423,7 +420,7 @@ private void addCopilotUsageActionLegacy(MenuManager menuManager, CheckQuotaResu
}
// Upsell actions based on the user's plan (legacy wording).
- ImageDescriptor upgradeIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/quota/upgrade.png");
+ ImageDescriptor upgradeIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_UPGRADE);
if (plan == CopilotPlan.free) {
MenuActionFactory.createMenuAction(menuManager, Messages.menu_quota_updateCopilotToPro, upgradeIcon,
handlerService, "com.microsoft.copilot.eclipse.commands.upgradeCopilotPlan", true);
@@ -435,7 +432,7 @@ private void addCopilotUsageActionLegacy(MenuManager menuManager, CheckQuotaResu
}
private void addOpenChatViewAction(MenuManager menuManager) {
- ImageDescriptor icon = UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png");
+ ImageDescriptor icon = CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT);
MenuActionFactory.createMenuAction(menuManager, Messages.menu_openChatView, icon, handlerService,
"com.microsoft.copilot.eclipse.commands.openChatView", true);
}
@@ -443,26 +440,26 @@ private void addOpenChatViewAction(MenuManager menuManager) {
private void addLinkToFeedbackForumAction(MenuManager menuManager) {
Map parameters = Map.of(UiConstants.OPEN_URL_PARAMETER_NAME,
UiConstants.COPILOT_FEEDBACK_FORUM_URL);
- ImageDescriptor feedbackIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/feedback_forum.png");
+ ImageDescriptor feedbackIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_FEEDBACK_FORUM);
MenuActionFactory.createMenuAction(menuManager, Messages.menu_giveFeedback, feedbackIcon, handlerService,
UiConstants.OPEN_URL_COMMAND_ID, parameters, true);
}
private void addPreferencesAction(MenuManager menuManager) {
- ImageDescriptor editPreferencesIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/edit_preferences.png");
+ ImageDescriptor editPreferencesIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_PREFERENCES);
MenuActionFactory.createMenuAction(menuManager, Messages.menu_editPreferences, editPreferencesIcon, handlerService,
"com.microsoft.copilot.eclipse.commands.openPreferences", true);
}
private void addEditKeyboardShortcutsAction(MenuManager menuManager) {
- ImageDescriptor editKeyboardShortcutsIcon = UiUtils
- .buildImageDescriptorFromPngPath("/icons/edit_keyboard_shortcuts.png");
+ ImageDescriptor editKeyboardShortcutsIcon =
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_EDIT_KEYBOARD_SHORTCUTS);
MenuActionFactory.createMenuAction(menuManager, Messages.menu_editKeyboardShortcuts, editKeyboardShortcutsIcon,
handlerService, "com.microsoft.copilot.eclipse.commands.openEditKeyboardShortcuts", true);
}
private void addCompletionSettingsAction(MenuManager menuManager) {
- ImageDescriptor placeHolder = UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png");
+ ImageDescriptor placeHolder = CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK);
if (languageServerSettingManager.isAutoShowCompletionEnabled()) {
MenuActionFactory.createMenuAction(menuManager, Messages.menu_turnOffCompletions, placeHolder, handlerService,
"com.microsoft.copilot.eclipse.commands.autoShowCompletions", true);
@@ -473,7 +470,7 @@ private void addCompletionSettingsAction(MenuManager menuManager) {
}
private void addShowWhatIsNewAction(MenuManager menuManager) {
- ImageDescriptor placeHolder = UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png");
+ ImageDescriptor placeHolder = CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK);
MenuActionFactory.createMenuAction(menuManager, Messages.menu_whatIsNew, placeHolder, handlerService,
"com.microsoft.copilot.eclipse.commands.showWhatIsNew", true);
}
@@ -499,7 +496,7 @@ private void addAuthenticationActions(MenuManager menuManager) {
handlerService, "com.microsoft.copilot.eclipse.commands.configureCopilotSettings", true);
}
// Only show sign out action when the user is in OK, NOT_AUTHORIZED, WARNING, or ERROR state.
- ImageDescriptor signOutIcon = UiUtils.buildImageDescriptorFromPngPath("/icons/signout.png");
+ ImageDescriptor signOutIcon = CopilotImages.getImageDescriptor(CopilotImages.IMG_SIGNOUT);
MenuActionFactory.createMenuAction(menuManager, Messages.menu_signOutOfGitHub, signOutIcon, handlerService,
"com.microsoft.copilot.eclipse.commands.signOut", true);
}
@@ -607,7 +604,7 @@ protected IStatus run(IProgressMonitor monitor) {
if (this.uiElement == null) {
throw new IllegalStateException("UI element is not set. Spinner cannot be set.");
}
- setIconOnDisplayThread(this.uiElement, String.format("/icons/spinner/%d.png", currentIconIndex));
+ setIconOnDisplayThread(this.uiElement, CopilotImages.getSpinnerFrameDescriptor(currentIconIndex));
currentIconIndex = (currentIconIndex % TOTAL_SPINNER_ICONS) + 1;
if (CopilotCore.getPlugin().getAuthStatusManager() != null
&& CopilotCore.getPlugin().getAuthStatusManager().isLoading()) {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java
index 89635a03..1ef1ff15 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ToolConfigurationContextMenuHandler.java
@@ -19,6 +19,7 @@
import org.eclipse.ui.texteditor.ITextEditor;
import com.microsoft.copilot.eclipse.core.CopilotCore;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
@@ -41,7 +42,7 @@ protected IContributionItem[] getContributionItems() {
// Create submenu for Copilot tool configuration
MenuManager submenu = new MenuManager("Copilot",
- UiUtils.buildImageDescriptorFromPngPath("/icons/github_copilot.png"),
+ CopilotImages.getImageDescriptor(CopilotImages.IMG_GITHUB_COPILOT),
"com.microsoft.copilot.eclipse.ui.toolConfigMenu");
// Add "Configure Tools..." command
@@ -49,7 +50,7 @@ protected IContributionItem[] getContributionItems() {
CommandContributionItemParameter param = new CommandContributionItemParameter(window, null,
"com.microsoft.copilot.eclipse.commands.configureTools", CommandContributionItem.STYLE_PUSH);
param.label = "Configure Tools...";
- param.icon = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/tools.png");
+ param.icon = CopilotImages.getImageDescriptor(CopilotImages.IMG_CHAT_TOOLS);
submenu.add(new CommandContributionItem(param));
items.add(submenu);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java
index 557503c0..ddaea8f2 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/ActionMenu.java
@@ -15,7 +15,7 @@
import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
/**
* Action menu for accepting/rejecting suggestions.
@@ -68,8 +68,8 @@ public void show(int x, int y) {
String acceptLabel = buildAlignedMenuLabel(Messages.actionMenu_accept, "Tab");
String rejectLabel = buildAlignedMenuLabel(Messages.actionMenu_reject, "Esc");
- ImageDescriptor acceptImage = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/keyboard-tab.png");
- ImageDescriptor cancelImage = UiUtils.buildImageDescriptorFromPngPath("/icons/close.png");
+ ImageDescriptor acceptImage = CopilotImages.getImageDescriptor(CopilotImages.IMG_CHAT_KEYBOARD_TAB);
+ ImageDescriptor cancelImage = CopilotImages.getImageDescriptor(CopilotImages.IMG_CLOSE);
menuManager.add(new Action(acceptLabel, acceptImage) {
@Override
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java
index bc48eb03..00da9d29 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/nes/RulerColumn.java
@@ -32,6 +32,7 @@
import org.eclipse.ui.texteditor.rulers.IContributedRulerColumn;
import org.eclipse.ui.texteditor.rulers.RulerColumnDescriptor;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.completion.EditorsManager;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
@@ -263,7 +264,7 @@ private Rectangle getSuggestionIconBounds(int expectedModelLine, int expectedWid
private void ensureIcon() {
if (icon == null || icon.isDisposed()) {
- ImageDescriptor desc = UiUtils.buildImageDescriptorFromPngPath("/icons/chat/gutter-arrow.png");
+ ImageDescriptor desc = CopilotImages.getImageDescriptor(CopilotImages.IMG_CHAT_GUTTER_ARROW);
if (desc != null) {
icon = desc.createImage(true);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java
index aa6f8eaa..6b4b9ff9 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddApiKeyDialog.java
@@ -22,6 +22,7 @@
import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelProvider;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
@@ -93,21 +94,12 @@ protected Control createDialogArea(Composite parent) {
apiKeyText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
apiKeyText.addModifyListener(this::onFieldChanged);
- eyeOpenImg = UiUtils
- .buildImageFromPngPath(UiUtils.isDarkTheme() ? "/icons/chat/eye_dark.png" : "/icons/chat/eye.png");
- eyeClosedImg = UiUtils.buildImageFromPngPath(
- UiUtils.isDarkTheme() ? "/icons/chat/eye_closed_dark.png" : "/icons/chat/eye_closed.png");
+ eyeOpenImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE, CopilotImages.IMG_CHAT_EYE_DARK);
+ eyeClosedImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE_CLOSED,
+ CopilotImages.IMG_CHAT_EYE_CLOSED_DARK);
toggleEyeBtn = new Button(apiKeyRow, SWT.PUSH);
toggleEyeBtn.setImage(eyeClosedImg);
toggleEyeBtn.addListener(SWT.Selection, e -> togglePasswordVisibility());
- toggleEyeBtn.addDisposeListener(e -> {
- if (eyeOpenImg != null && !eyeOpenImg.isDisposed()) {
- eyeOpenImg.dispose();
- }
- if (eyeClosedImg != null && !eyeClosedImg.isDisposed()) {
- eyeClosedImg.dispose();
- }
- });
return container;
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java
index 4d20ddf6..adba9628 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java
@@ -24,6 +24,7 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModel;
import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelCapabilities;
import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelProvider;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
@@ -135,23 +136,14 @@ private void createAzureSpecificFields(Composite container) {
apiKeyText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
apiKeyText.addModifyListener(this::onFieldChanged);
- eyeOpenImg = UiUtils
- .buildImageFromPngPath(UiUtils.isDarkTheme() ? "/icons/chat/eye_dark.png" : "/icons/chat/eye.png");
- eyeClosedImg = UiUtils.buildImageFromPngPath(
- UiUtils.isDarkTheme() ? "/icons/chat/eye_closed_dark.png" : "/icons/chat/eye_closed.png");
+ eyeOpenImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE, CopilotImages.IMG_CHAT_EYE_DARK);
+ eyeClosedImg = CopilotImages.getThemedImage(CopilotImages.IMG_CHAT_EYE_CLOSED,
+ CopilotImages.IMG_CHAT_EYE_CLOSED_DARK);
toggleEyeBtn = new Button(apiKeyRow, SWT.FLAT);
toggleEyeBtn.setImage(eyeClosedImg);
toggleEyeBtn.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
toggleEyeBtn.addListener(SWT.Selection, e -> togglePasswordVisibility());
- toggleEyeBtn.addDisposeListener(e -> {
- if (eyeOpenImg != null && !eyeOpenImg.isDisposed()) {
- eyeOpenImg.dispose();
- }
- if (eyeClosedImg != null && !eyeClosedImg.isDisposed()) {
- eyeClosedImg.dispose();
- }
- });
}
/**
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java
index fb0be2c0..2495a5bd 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java
@@ -15,10 +15,10 @@
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
-import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.CopilotCore;
import com.microsoft.copilot.eclipse.core.FeatureFlags;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager;
import com.microsoft.copilot.eclipse.ui.chat.services.McpConfigService;
@@ -47,9 +47,8 @@ public void init(IWorkbench workbench) {
protected Control createContents(Composite parent) {
FeatureFlags flags = CopilotCore.getPlugin().getFeatureFlags();
if (flags != null && !flags.isAutoApprovalEnabled()) {
- return WrappableIconLink.createWithSharedImage(parent,
- PlatformUI.getWorkbench().getSharedImages()
- .getImage(ISharedImages.IMG_OBJS_INFO_TSK),
+ return WrappableIconLink.create(parent,
+ CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_INFO_TSK),
Messages.preferences_page_auto_approve_disabled_by_organization);
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java
index 1392a529..feb7e234 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java
@@ -49,10 +49,10 @@
import com.microsoft.copilot.eclipse.core.FeatureFlags;
import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModel;
import com.microsoft.copilot.eclipse.core.lsp.protocol.byok.ByokModelProvider;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ByokService;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* BYOK (Bring Your Own Key) preference page for configuring AI model providers. This page allows users to add, remove,
@@ -177,7 +177,7 @@ protected Control createContents(Composite parent) {
disabledCompositeLayout.marginWidth = 0;
disabledCompositeLayout.marginHeight = 0;
disabledComposite.setLayout(disabledCompositeLayout);
- WrappableIconLink.createWithCustomizedImage(disabledComposite, "/icons/information.png",
+ WrappableIconLink.create(disabledComposite, CopilotImages.getImage(CopilotImages.IMG_INFORMATION),
Messages.preferences_page_byok_disabled_tip);
contentComposite = createByokView(pageStateStack);
updatePageState();
@@ -269,15 +269,8 @@ private Composite createContentArea(Composite parent) {
viewerStack.setLayout(stackLayout);
GridData stackData = new GridData(SWT.FILL, SWT.FILL, true, true);
viewerStack.setLayoutData(stackData);
- enabledIcon = UiUtils.buildImageFromPngPath("/icons/chat/keep.png");
- disabledIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ETOOL_DELETE);
-
- viewerStack.addDisposeListener(e -> {
- if (enabledIcon != null && !enabledIcon.isDisposed()) {
- enabledIcon.dispose();
- enabledIcon = null;
- }
- });
+ enabledIcon = CopilotImages.getImage(CopilotImages.IMG_CHAT_KEEP);
+ disabledIcon = CopilotImages.getSharedImage(ISharedImages.IMG_ETOOL_DELETE);
// Create tree viewer composite
treeComposite = createTreeViewer(viewerStack);
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java
index 49657b8e..b6a98e20 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CustomModesPreferencePage.java
@@ -55,6 +55,7 @@
import com.microsoft.copilot.eclipse.core.chat.CustomChatModeManager;
import com.microsoft.copilot.eclipse.core.chat.service.ICustomModeService;
import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
/**
@@ -90,7 +91,7 @@ protected Control createContents(Composite parent) {
disabledCompositeLayout.marginHeight = 0;
disabledComposite.setLayout(disabledCompositeLayout);
disabledComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
- WrappableIconLink.createWithCustomizedImage(disabledComposite, "/icons/information.png",
+ WrappableIconLink.create(disabledComposite, CopilotImages.getImage(CopilotImages.IMG_INFORMATION),
Messages.setting_disabled_by_organization);
return container;
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GlobalAutoApproveSection.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GlobalAutoApproveSection.java
index 3aaa8c7a..2fa73dc4 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GlobalAutoApproveSection.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GlobalAutoApproveSection.java
@@ -15,9 +15,9 @@
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.Constants;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
/**
* Global auto-approve preference section with a YOLO mode checkbox
@@ -80,8 +80,7 @@ public void widgetSelected(SelectionEvent e) {
});
Label warningIcon = new Label(yoloRow, SWT.NONE);
- warningIcon.setImage(PlatformUI.getWorkbench().getSharedImages()
- .getImage(ISharedImages.IMG_OBJS_WARN_TSK));
+ warningIcon.setImage(CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_WARN_TSK));
warningIcon.setToolTipText(wrapTooltip(
Messages.preferences_page_global_auto_approve_confirm_message));
warningIcon.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java
index c41975d0..c4424b7b 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java
@@ -54,9 +54,9 @@
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
-import org.eclipse.ui.PlatformUI;
import org.osgi.service.prefs.BackingStoreException;
import com.microsoft.copilot.eclipse.core.Constants;
@@ -70,6 +70,7 @@
import com.microsoft.copilot.eclipse.core.lsp.mcp.RegistryAccess;
import com.microsoft.copilot.eclipse.core.lsp.protocol.LanguageModelToolInformation;
import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.McpExtensionPointManager;
import com.microsoft.copilot.eclipse.ui.dialogs.mcp.McpRegistryDialog;
@@ -167,8 +168,8 @@ protected Control createContents(Composite parent) {
// Create a simple note for the feature disabled case
FeatureFlags flags = CopilotCore.getPlugin().getFeatureFlags();
if (flags != null && !flags.isMcpEnabled()) {
- return WrappableIconLink.createWithSharedImage(parent,
- PlatformUI.getWorkbench().getSharedImages().getImage(org.eclipse.ui.ISharedImages.IMG_OBJS_INFO_TSK),
+ return WrappableIconLink.create(parent,
+ CopilotImages.getSharedImage(ISharedImages.IMG_OBJS_INFO_TSK),
Messages.preferences_page_mcp_disabled_tip);
}
@@ -377,8 +378,8 @@ private void showRegistryInfoMessage(String message) {
}
// Create info message composite using WrappableIconLink
- registryInfoMessageComposite = WrappableIconLink.createWithCustomizedImage(mcpRegistryGroup,
- "/icons/information.png", message);
+ registryInfoMessageComposite = WrappableIconLink.create(mcpRegistryGroup,
+ CopilotImages.getImage(CopilotImages.IMG_INFORMATION), message);
// Trigger layout update on the entire hierarchy to ensure proper sizing
mcpRegistryGroup.requestLayout();
@@ -403,20 +404,14 @@ private void createExtMcpRegistrationArea(Composite parent) {
var service = CopilotUi.getPlugin().getChatServiceManager().getMcpConfigService();
boolean newExtMcpRegFound = service.isNewExtMcpRegFound();
if (newExtMcpRegFound) {
- if (redNotice == null || redNotice.isDisposed()) {
- redNotice = UiUtils.buildImageFromPngPath("/icons/chat/red_notice.png");
+ if (redNotice == null) {
+ redNotice = CopilotImages.getImage(CopilotImages.IMG_CHAT_RED_NOTICE);
}
redNoticeLabel = new Label(extMcpTitleComposite, SWT.NONE);
redNoticeLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
redNoticeLabel.setImage(redNotice);
redNoticeLabel
.setToolTipText(com.microsoft.copilot.eclipse.ui.i18n.Messages.chat_actionBar_toolButton_detected_toolTip);
- redNoticeLabel.addDisposeListener(e -> {
- if (redNotice != null && !redNotice.isDisposed()) {
- redNotice.dispose();
- redNotice = null;
- }
- });
}
// Title label
@@ -450,11 +445,6 @@ public void disposeNoticeIcon() {
redNoticeLabel = null;
}
- if (redNotice != null && !redNotice.isDisposed()) {
- redNotice.dispose();
- redNotice = null;
- }
-
// Refresh layout to reflect changes immediately
extMcpTitleComposite.requestLayout();
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java
index 591f0617..8466405d 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/WrappableIconLink.java
@@ -5,7 +5,6 @@
import java.net.URL;
-import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionAdapter;
@@ -19,11 +18,10 @@
import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.CopilotCore;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A composite that displays an icon and a wrappable link. The component automatically resizes when the parent composite
- * is resized and handles proper image disposal.
+ * is resized. The icon image is not owned by this widget and is therefore not disposed by it.
*/
public class WrappableIconLink extends Composite {
@@ -35,61 +33,43 @@ public class WrappableIconLink extends Composite {
// Icon
private Label iconLabel;
- private String iconPath; // only used for customized image case
- private Image icon; // holds the created (non-shared) image if any
- private boolean isSharedImage; // true when using shared workbench image
+ private Image icon;
// Link
private Link linkControl;
private String linkText;
/**
- * Creates a new WrappableIconLink with default layout settings and no icon.
+ * Creates a new WrappableIconLink.
*
* @param parent the parent composite
+ * @param icon the icon image (not owned by this widget)
* @param linkText the text for the link (may contain HTML link tags)
+ * @param widthMargin the horizontal margin to subtract when computing the link width
*/
- private WrappableIconLink(Composite parent, String iconPath, Image sharedImage, String linkText, int widthMargin) {
+ private WrappableIconLink(Composite parent, Image icon, String linkText, int widthMargin) {
super(parent, SWT.NONE);
this.widthMargin = widthMargin;
this.parent = parent;
this.linkText = linkText;
- this.iconPath = iconPath;
- this.isSharedImage = sharedImage != null;
- this.icon = sharedImage;
+ this.icon = icon;
createControls();
setupResizeListener();
}
// ------------- Factory methods -------------
/**
- * Creates a WrappableIconLink with a shared workbench image.
+ * Creates a WrappableIconLink with the given icon image.
*/
- public static WrappableIconLink createWithSharedImage(Composite parent, Image sharedImage, String linkText) {
- return new WrappableIconLink(parent, null, sharedImage, linkText, DEFAULT_MARGIN);
+ public static WrappableIconLink create(Composite parent, Image icon, String linkText) {
+ return new WrappableIconLink(parent, icon, linkText, DEFAULT_MARGIN);
}
/**
- * Creates a WrappableIconLink with a shared workbench image and custom width margin.
+ * Creates a WrappableIconLink with the given icon image and custom width margin.
*/
- public static WrappableIconLink createWithSharedImage(Composite parent, Image sharedImage, String linkText,
- int widthMargin) {
- return new WrappableIconLink(parent, null, sharedImage, linkText, widthMargin);
- }
-
- /**
- * Creates a WrappableIconLink with a customized image from the given path.
- */
- public static WrappableIconLink createWithCustomizedImage(Composite parent, String iconPath, String linkText) {
- return new WrappableIconLink(parent, iconPath, null, linkText, DEFAULT_MARGIN);
- }
-
- /**
- * Creates a WrappableIconLink with a customized image from the given path and custom width margin.
- */
- public static WrappableIconLink createWithCustomizedImage(Composite parent, String iconPath, String linkText,
- int widthMargin) {
- return new WrappableIconLink(parent, iconPath, null, linkText, widthMargin);
+ public static WrappableIconLink create(Composite parent, Image icon, String linkText, int widthMargin) {
+ return new WrappableIconLink(parent, icon, linkText, widthMargin);
}
/**
@@ -103,14 +83,8 @@ private void createControls() {
// Create icon label
iconLabel = new Label(this, SWT.NONE);
iconLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
- if (isSharedImage && icon != null) {
- iconLabel.setImage(icon); // shared image
- } else if (iconPath != null) {
- ImageDescriptor imageDescriptor = UiUtils.buildImageDescriptorFromPngPath(iconPath);
- if (imageDescriptor != null) {
- icon = imageDescriptor.createImage();
- iconLabel.setImage(icon);
- }
+ if (icon != null) {
+ iconLabel.setImage(icon);
}
// Create link control
@@ -130,13 +104,6 @@ public void widgetSelected(SelectionEvent e) {
}
}
});
-
- // Dispose image when composite is disposed
- addDisposeListener(e -> {
- if (!isSharedImage && icon != null && !icon.isDisposed()) {
- icon.dispose();
- }
- });
}
/**
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java
index 283b736f..98d0556e 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/quickstart/FeaturePage.java
@@ -28,6 +28,7 @@
import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
@@ -47,7 +48,6 @@ public enum Feature {
private Composite selectedCard;
private Color normalBackgroundColor;
private Color selectedBackgroundColor;
- private Image currentContentImage;
private Display display;
private boolean isDarkTheme;
@@ -179,12 +179,12 @@ private void createLeftPanel(Composite parent) {
leftPanel.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-container");
// Create clickable feature cards (Agent selected by default)
- selectedCard = createClickableFeatureCard(leftPanel, "/icons/github_copilot.png", Messages.quickStart_agent_title,
- Messages.quickStart_agent_description, Feature.AGENT);
- createClickableFeatureCard(leftPanel, "/icons/chat/chatview_icon_chat.png", Messages.quickStart_ask_title,
- Messages.quickStart_ask_description, Feature.ASK);
- createClickableFeatureCard(leftPanel, "/icons/chat/chatview_icon_code.png", Messages.quickStart_completion_title,
- Messages.quickStart_completion_description, Feature.COMPLETION);
+ selectedCard = createClickableFeatureCard(leftPanel, CopilotImages.getImage(CopilotImages.IMG_GITHUB_COPILOT),
+ Messages.quickStart_agent_title, Messages.quickStart_agent_description, Feature.AGENT);
+ createClickableFeatureCard(leftPanel, CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CHAT),
+ Messages.quickStart_ask_title, Messages.quickStart_ask_description, Feature.ASK);
+ createClickableFeatureCard(leftPanel, CopilotImages.getImage(CopilotImages.IMG_CHATVIEW_ICON_CODE),
+ Messages.quickStart_completion_title, Messages.quickStart_completion_description, Feature.COMPLETION);
}
private void createRightPanel(Composite parent) {
@@ -198,11 +198,6 @@ private void createRightPanel(Composite parent) {
// Content area that will change based on selection
rightPanelContent = new Label(rightPanel, SWT.WRAP | SWT.CENTER);
rightPanelContent.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-feature-card");
- rightPanelContent.addDisposeListener(e -> {
- if (currentContentImage != null && !currentContentImage.isDisposed()) {
- currentContentImage.dispose();
- }
- });
// Add resize listener to properly size the content
rightPanel.addListener(SWT.Resize, e -> {
@@ -213,7 +208,7 @@ private void createRightPanel(Composite parent) {
updateRightPanelContent(Feature.AGENT);
}
- private Composite createClickableFeatureCard(Composite parent, String imagePath, String title, String description,
+ private Composite createClickableFeatureCard(Composite parent, Image iconImage, String title, String description,
Feature feature) {
// Create a canvas for custom rounded painting
Canvas cardCanvas = new Canvas(parent, SWT.NONE);
@@ -284,8 +279,7 @@ public void mouseDown(MouseEvent e) {
iconTitleComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
iconTitleComposite.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-feature-card");
- // Icon - Load image from path using UiUtils
- Image iconImage = UiUtils.buildImageFromPngPath(imagePath);
+ // Icon
Label iconLabel = new Label(iconTitleComposite, SWT.NONE);
iconLabel.setImage(iconImage);
iconLabel.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-feature-card");
@@ -294,13 +288,6 @@ public void mouseDown(MouseEvent e) {
iconData.heightHint = 20;
iconLabel.setLayoutData(iconData);
- // Dispose image when label is disposed
- iconLabel.addDisposeListener(e -> {
- if (iconImage != null && !iconImage.isDisposed()) {
- iconImage.dispose();
- }
- });
-
// Make icon clickable too
addClickableToControls(clickHandler, iconLabel);
@@ -434,28 +421,20 @@ private void updateChildrenControlBackground(Control control, boolean selected)
}
private void updateRightPanelContent(Feature feature) {
- // Dispose previous image if it exists
- if (currentContentImage != null && !currentContentImage.isDisposed()) {
- currentContentImage.dispose();
- currentContentImage = null;
- }
-
- String imagePath;
+ String key;
switch (feature) {
case ASK:
- imagePath = "/intro/quickstart/quick_start_ask.png";
+ key = CopilotImages.IMG_QUICKSTART_ASK;
break;
case COMPLETION:
- imagePath = "/intro/quickstart/quick_start_completion.png";
+ key = CopilotImages.IMG_QUICKSTART_COMPLETION;
break;
case AGENT:
default:
- imagePath = "/intro/quickstart/quick_start_agent.png";
+ key = CopilotImages.IMG_QUICKSTART_AGENT;
}
- // Load and set the new image
- currentContentImage = UiUtils.buildImageFromPngPath(imagePath);
- rightPanelContent.setImage(currentContentImage);
+ rightPanelContent.setImage(CopilotImages.getImage(key));
rightPanelContent.requestLayout();
}
@@ -483,8 +462,8 @@ private void disposeColors() {
* Creates a close button with hover effects.
*/
private Label createCloseButton(Composite parent) {
- Image normalImage = UiUtils
- .buildImageFromPngPath(isDarkTheme ? "/intro/quickstart/close_dark.png" : "/intro/quickstart/close_light.png");
+ Image normalImage = CopilotImages.getThemedImage(
+ CopilotImages.IMG_QUICKSTART_CLOSE_LIGHT, CopilotImages.IMG_QUICKSTART_CLOSE_DARK);
Label closeButton = new Label(parent, SWT.NONE);
closeButton.setImage(normalImage);
closeButton.setData(CSSSWTConstants.CSS_ID_KEY, "quick-start-container");
@@ -501,17 +480,8 @@ public void mouseDown(MouseEvent e) {
}
});
- Image hoverImage = UiUtils.buildImageFromPngPath(
- isDarkTheme ? "/intro/quickstart/close_hover_dark.png" : "/intro/quickstart/close_hover_light.png");
- // Add dispose listener for close button images
- closeButton.addDisposeListener(e -> {
- if (normalImage != null && !normalImage.isDisposed()) {
- normalImage.dispose();
- }
- if (hoverImage != null && !hoverImage.isDisposed()) {
- hoverImage.dispose();
- }
- });
+ Image hoverImage = CopilotImages.getThemedImage(
+ CopilotImages.IMG_QUICKSTART_CLOSE_HOVER_LIGHT, CopilotImages.IMG_QUICKSTART_CLOSE_HOVER_DARK);
// Add hover effect for close button
closeButton.addMouseTrackListener(new MouseTrackAdapter() {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java
index 6799c4a0..6b9d4790 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownButton.java
@@ -19,9 +19,9 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.UiConstants;
import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A custom-painted dropdown button composite.
@@ -44,8 +44,6 @@ public class DropdownButton extends Composite {
private static final int H_PADDING = 4;
private static final int ARROW_AREA_WIDTH = 16;
- private static Image arrowIcon;
-
private final DropdownPopup popup;
private List itemGroups;
private String selectedItemId;
@@ -61,13 +59,6 @@ public DropdownButton(Composite parent, int style) {
super(parent, style | SWT.NONE);
popup = new DropdownPopup(getShell(), this);
- if (arrowIcon == null || arrowIcon.isDisposed()) {
- arrowIcon = UiUtils.isDarkTheme()
- ? UiUtils.buildImageFromPngPath("/icons/dropdown/down_arrow_dark.png")
- : UiUtils.buildImageFromPngPath("/icons/dropdown/down_arrow.png");
- getDisplay().addListener(SWT.Dispose, e -> disposeStaticIcons());
- }
-
addPaintListener(e -> paintControl(e.gc));
addMouseListener(new MouseAdapter() {
@@ -158,13 +149,6 @@ public void setAccessibilityName(String name) {
AccessibilityUtils.addAccessibilityNameForUiComponent(this, name);
}
- private static void disposeStaticIcons() {
- if (arrowIcon != null && !arrowIcon.isDisposed()) {
- arrowIcon.dispose();
- arrowIcon = null;
- }
- }
-
private void togglePopup() {
if (popup.isOpen()) {
popup.close();
@@ -185,6 +169,8 @@ private void openPopup() {
private void paintControl(GC gc) {
Rectangle bounds = getClientArea();
Display display = getDisplay();
+ Image arrowIcon = CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_DOWN_ARROW,
+ CopilotImages.IMG_DROPDOWN_DOWN_ARROW_DARK);
DropdownItem selected = findItemById(selectedItemId);
Image selectedIcon = getSelectedItemIcon(selected);
Color bg = mouseHover ? CssConstants.getButtonFocusBgColor(display) : getBackground();
@@ -249,9 +235,10 @@ public Point computeSize(int widthHint, int heightHint, boolean changed) {
if (selectedIcon != null) {
iconWidth = selectedIcon.getBounds().width + ICON_TEXT_GAP;
}
- Image arrow = arrowIcon;
- int arrowWidth = arrowIcon != null && !arrowIcon.isDisposed()
- ? arrowIcon.getBounds().width : ARROW_AREA_WIDTH;
+ Image arrow = CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_DOWN_ARROW,
+ CopilotImages.IMG_DROPDOWN_DOWN_ARROW_DARK);
+ int arrowWidth = arrow != null && !arrow.isDisposed()
+ ? arrow.getBounds().width : ARROW_AREA_WIDTH;
int width = H_PADDING + iconWidth + textExtent.x + arrowWidth;
int height = getContentHeight(textExtent, selectedIcon, arrow) + 2 * UiConstants.BTN_PADDING;
if (widthHint != SWT.DEFAULT) {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java
index 344eb79c..7f564f53 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/DropdownPopup.java
@@ -29,8 +29,8 @@
import org.eclipse.ui.PlatformUI;
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* A generic popup shell for {@link DropdownButton}.
@@ -51,8 +51,6 @@ class DropdownPopup {
private static final int MAX_VISIBLE_ITEMS = 15;
private static final int SHORT_POPUP_WIDTH = 250;
- private static Image checkIcon;
-
private Shell shell;
private final Shell parentShell;
private Consumer selectionListener;
@@ -81,26 +79,12 @@ private record ItemEntry(DropdownItem item, Composite composite, ItemController
this.parentShell = parentShell;
this.anchorControl = anchorControl;
this.stylingEngine = PlatformUI.getWorkbench().getService(IStylingEngine.class);
-
- if (checkIcon == null || checkIcon.isDisposed()) {
- checkIcon = UiUtils.isDarkTheme()
- ? UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status_dark.png")
- : UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status.png");
- parentShell.getDisplay().addListener(SWT.Dispose, e -> disposeStaticIcons());
- }
}
void setSelectionListener(Consumer listener) {
this.selectionListener = listener;
}
- private static void disposeStaticIcons() {
- if (checkIcon != null && !checkIcon.isDisposed()) {
- checkIcon.dispose();
- checkIcon = null;
- }
- }
-
/**
* Opens the popup at the given screen location with the provided item groups.
*
@@ -528,8 +512,9 @@ public void mouseDown(MouseEvent e) {
}
private Image resolvePopupLeadingIcon(DropdownItem item, boolean selected) {
- if (selected && checkIcon != null && !checkIcon.isDisposed()) {
- return checkIcon;
+ if (selected) {
+ return CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS,
+ CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS_DARK);
}
Image icon = item.getIcon();
return icon != null && !icon.isDisposed() ? icon : null;
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java
index 425a6b54..011db3ea 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java
@@ -28,11 +28,11 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel.CopilotModelCapabilitiesSupports;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel.CopilotModelCustomModel;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ModelService;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
import com.microsoft.copilot.eclipse.ui.utils.ModelUtils;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
/**
* Renders the full hover UI for model items in the model picker dropdown. The layout consists of the bold title header,
@@ -49,8 +49,6 @@ public class ModelHoverContentProvider implements IDropdownItemHoverProvider {
/** Vertical padding inside a thinking effort row, so the hover background has breathing room. */
private static final int THINKING_EFFORT_ROW_V_PADDING = 2;
- private static Image effortCheckIcon;
-
private final CopilotModel model;
private final IStylingEngine stylingEngine;
@@ -213,7 +211,7 @@ private void addThinkingEffortOption(Composite parent, ModelService modelService
row.setLayout(rowLayout);
row.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
- Image checkIcon = getCheckIcon(parent);
+ Image checkIcon = getCheckIcon();
Label iconLabel = new Label(row, SWT.NONE);
GridData iconGd = new GridData(SWT.LEFT, SWT.CENTER, false, false);
if (checkIcon != null) {
@@ -317,28 +315,14 @@ private Composite createKeyValueRow(Composite parent) {
return row;
}
- private static void disposeStaticIcons() {
- if (effortCheckIcon != null && !effortCheckIcon.isDisposed()) {
- effortCheckIcon.dispose();
- effortCheckIcon = null;
- }
- }
-
/**
* Returns the cached check-mark image used to indicate the selected thinking effort row, lazily loaded on first
* access. The icon shares the asset used by the dropdown popup so the leading column lines up visually with the
* checkmarks shown next to selected model items.
*/
- private static Image getCheckIcon(Composite parent) {
- if (effortCheckIcon == null || effortCheckIcon.isDisposed()) {
- effortCheckIcon = UiUtils.isDarkTheme()
- ? UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status_dark.png")
- : UiUtils.buildImageFromPngPath("/icons/dropdown/dropdown_complete_status.png");
- if (parent != null && !parent.isDisposed()) {
- parent.getDisplay().addListener(SWT.Dispose, e -> disposeStaticIcons());
- }
- }
- return effortCheckIcon;
+ private static Image getCheckIcon() {
+ return CopilotImages.getThemedImage(CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS,
+ CopilotImages.IMG_DROPDOWN_COMPLETE_STATUS_DARK);
}
private static boolean isPositive(Integer value) {
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java
index 7ebafdb5..4fb7b9a2 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/SpinnerAnimator.java
@@ -7,7 +7,7 @@
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
-import com.microsoft.copilot.eclipse.ui.utils.UiUtils;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
/**
* Drives a rotating spinner animation on a target {@link Label}.
@@ -21,8 +21,8 @@
* running frame is freed automatically when the label goes away.
*/
public final class SpinnerAnimator {
- /** Total number of frames in the spinner animation under {@code /icons/spinner/}. */
- private static final int TOTAL_FRAMES = 8;
+ /** Total number of frames. */
+ private static final int TOTAL_FRAMES = CopilotImages.SPINNER_FRAMES.length;
/** Per-frame interval in milliseconds. */
private static final int FRAME_INTERVAL_MS = 100;
@@ -58,10 +58,6 @@ public void run() {
if (target.isDisposed()) {
return;
}
- // Dispose the previous frame before loading the next one.
- if (currentFrameImage != null && !currentFrameImage.isDisposed()) {
- currentFrameImage.dispose();
- }
currentFrameImage = buildFrame(currentFrame);
target.setImage(currentFrameImage);
// Request layout so the icon scale stays correct as frames change.
@@ -88,13 +84,10 @@ public void stop() {
if (!target.isDisposed() && target.getImage() == currentFrameImage) {
target.setImage(null);
}
- if (currentFrameImage != null && !currentFrameImage.isDisposed()) {
- currentFrameImage.dispose();
- }
currentFrameImage = null;
}
private static Image buildFrame(int frame) {
- return UiUtils.buildImageFromPngPath(String.format("/icons/spinner/%d.png", frame));
+ return CopilotImages.getSpinnerFrame(frame);
}
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java
index debbcbef..ee4ff4df 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/McpUtils.java
@@ -18,6 +18,7 @@
import com.microsoft.copilot.eclipse.core.lsp.mcp.RegistryAccess;
import com.microsoft.copilot.eclipse.core.lsp.mcp.registry.Icon;
import com.microsoft.copilot.eclipse.core.lsp.protocol.NullParams;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.dialogs.mcp.Messages;
import com.microsoft.copilot.eclipse.ui.preferences.CopilotPreferenceInitializer;
@@ -157,11 +158,11 @@ public static CompletableFuture loadServerIcon(String iconUrl, int width,
}
/**
- * Builds the default MCP server icon image.
+ * Loads the shared default MCP server icon.
*
- * @return the default MCP icon image, or {@code null} if it cannot be loaded
+ * @return the default MCP icon image; callers must not dispose it
*/
public static Image loadDefaultServerIcon() {
- return UiUtils.buildImageFromPngPath("/icons/mcp/mcp_default_icon.png");
+ return CopilotImages.getImage(CopilotImages.IMG_MCP_DEFAULT_ICON);
}
}
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java
index 7af8ba99..cf24b437 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/MenuUtils.java
@@ -19,6 +19,7 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CheckQuotaResult;
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CopilotPlan;
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.Quota;
+import com.microsoft.copilot.eclipse.ui.CopilotImages;
import com.microsoft.copilot.eclipse.ui.i18n.Messages;
/**
@@ -82,19 +83,19 @@ public static double calculatePercentRemaining(CheckQuotaResult quotaStatus) {
*/
public static ImageDescriptor getUsageIcon(double percentRemaining) {
if (percentRemaining <= 10) {
- return UiUtils.buildImageDescriptorFromPngPath("/icons/quota/usage_red.png");
+ return CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_USAGE_RED);
}
if (percentRemaining <= 25) {
- return UiUtils.buildImageDescriptorFromPngPath("/icons/quota/usage_yellow.png");
+ return CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_USAGE_YELLOW);
}
- return UiUtils.buildImageDescriptorFromPngPath("/icons/quota/usage_blue.png");
+ return CopilotImages.getImageDescriptor(CopilotImages.IMG_QUOTA_USAGE_BLUE);
}
/**
* Returns the shared blank icon descriptor used for indented usage rows.
*/
public static ImageDescriptor getBlankIcon() {
- return UiUtils.buildImageDescriptorFromPngPath("/icons/blank.png");
+ return CopilotImages.getImageDescriptor(CopilotImages.IMG_BLANK);
}
/**
diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java
index 5d1ab2a9..a7a765fd 100644
--- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java
+++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/UiUtils.java
@@ -406,20 +406,6 @@ public static int modelLine2WidgetLine(ITextViewer viewer, int modelLine) {
return modelLine;
}
- /**
- * Builds an image descriptor from a PNG file at the given path.
- */
- public static ImageDescriptor buildImageDescriptorFromPngPath(String path) {
- return ImageDescriptor.createFromURL(UiUtils.class.getResource(path));
- }
-
- /**
- * Builds an image from a PNG file at the given path.
- */
- public static Image buildImageFromPngPath(String path) {
- return buildImageDescriptorFromPngPath(path).createImage();
- }
-
/**
* Refreshes the elements of the command with the given ID.
*/
diff --git a/pom.xml b/pom.xml
index 2a9b33d6..ae073c4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,7 @@
com.microsoft.copilot.eclipse.core.test
com.microsoft.copilot.eclipse.ui.test
+ com.microsoft.copilot.eclipse.ui.jobs.test
com.microsoft.copilot.eclipse.swtbot.test