Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4081440
feat: Add image registries (#364)
travkin79 Jul 20, 2026
cd4a030
Migrate image loading and disposal to using image registries (#364)
travkin79 Jul 21, 2026
2e88e7e
Use image registries to access shared images from other plug-ins (#364)
travkin79 Jul 21, 2026
45c02b3
Simplify WrappableIconLink to take only an Image parameter (#364)
travkin79 Jul 21, 2026
9427256
Remove unused buildImage(Descriptor)FromPngPath helpers (#364)
travkin79 Jul 21, 2026
d7d4e2b
Document image registry usage and disposal rules in copilot instructi…
travkin79 Jul 21, 2026
0d6ad79
Improve javadoc: clarify image disposal
travkin79 Jul 29, 2026
0a2880a
Fix mixed image ownership in McpUtils.loadServerIcon
travkin79 Jul 29, 2026
dc5f822
Fix stale SpinnerAnimator Javadoc on frame ownership
travkin79 Jul 29, 2026
d580fea
fix: Remove unused dependencies
travkin79 Jul 29, 2026
980d83f
Remove unused image registry methods returning ImageDescriptor
travkin79 Jul 29, 2026
e820a33
Complete Javadoc for public image registry APIs
travkin79 Jul 29, 2026
49bee87
Hide spinner frame constants
travkin79 Jul 29, 2026
4cd7c6c
Use shared registry image for NES ruler column icon
travkin79 Jul 29, 2026
4d0b6dc
Add insert icon to copilot's image registry
travkin79 Jul 29, 2026
c333bd7
Remove unused method
travkin79 Jul 30, 2026
31dfca0
Use the image registry in McpRuntimeLogger
travkin79 Jul 30, 2026
0f0f128
Use the image registry for accessing the terminal icon
travkin79 Jul 30, 2026
9766881
Provide a populated image registry to turn widget tests
travkin79 Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,33 @@ 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

**When** child has a parent → resources with a defined parent (e.g., Label with parent Composite) do not need explicit disposal

**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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;


/**
Expand Down Expand Up @@ -50,7 +51,14 @@ public Map<String, Object> prepareTerminalProperties(boolean runInBackground, St
public void cancelCurrentCommand();

/**
* Sets the terminal icon descriptor for the tool.
* Sets the supplier that provides the terminal icon for the tool.
*
* <p>The supplier must return a shared {@link Image} owned by a plug-in's image registry.
* The image must never be disposed (this is done by the registry).
*
* <p>The run-in-terminal implementation needs to invoke the supplier on the UI thread.
*
* @param terminalIconSupplier supplier of a shared, non-disposable terminal icon
*/
public void setTerminalIconDescriptor(ImageDescriptor terminalIconDescriptor);
public void setTerminalIconSupplier(Supplier<Image> terminalIconSupplier);
}
11 changes: 11 additions & 0 deletions com.microsoft.copilot.eclipse.ui.jobs.test/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
34 changes: 34 additions & 0 deletions com.microsoft.copilot.eclipse.ui.jobs.test/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.microsoft.copilot.eclipse.ui.jobs.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
11 changes: 11 additions & 0 deletions com.microsoft.copilot.eclipse.ui.jobs.test/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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
Require-Bundle: junit-jupiter-api;bundle-version="5.10.1",
junit-jupiter-params;bundle-version="5.10.1"
6 changes: 6 additions & 0 deletions com.microsoft.copilot.eclipse.ui.jobs.test/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source.. = src
output.. = target/classes
bin.includes = META-INF/,\
.,\
fragment.xml

5 changes: 5 additions & 0 deletions com.microsoft.copilot.eclipse.ui.jobs.test/fragment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>

</plugin>
54 changes: 54 additions & 0 deletions com.microsoft.copilot.eclipse.ui.jobs.test/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.microsoft.copilot.eclipse</groupId>
<artifactId>github-copilot-for-eclipse</artifactId>
<version>${copilot-plugin-version}</version>
</parent>
<artifactId>com.microsoft.copilot.eclipse.ui.jobs.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
<name>${base.name} :: UI Jobs Tests</name>

<properties>
<checkstyle.skip>true</checkstyle.skip>
</properties>

<profiles>
<profile>
<id>skip-tests-during-ui-probe</id>
<activation>
<property>
<name>probe.script</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-surefire-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-surefire-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
<useUIHarness>true</useUIHarness>
<useUIThread>false</useUIThread>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>New constants are discovered automatically via reflection; no separate list needs to be
* maintained.
*/
class CopilotJobsImagesTest {

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

Expand All @@ -17,11 +19,31 @@
*/
public class CopilotJobs extends AbstractUIPlugin {

private static volatile CopilotJobs COPILOT_JOBS_PLUGIN;

/**
* Returns the shared plugin instance.
*
* @return the shared plugin instance
*/
public static CopilotJobs getPlugin() {
Comment thread
jdneo marked this conversation as resolved.
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) {
Expand All @@ -42,5 +64,6 @@ protected IStatus run(IProgressMonitor monitor) {
@Override
public void stop(BundleContext context) throws Exception {
super.stop(context);
COPILOT_JOBS_PLUGIN = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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.
*
* <p>Bundle images are owned by the plugin's {@link ImageRegistry} and disposed automatically when
* the plugin stops. Callers must <em>not</em> 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 <em>not</em> dispose it.
*
* @param key one of the {@code IMG_*} constants defined in this class
* @return the registry-owned image for the given key
*/
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
* @return the image descriptor for the given key
*/
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}
* @return the shared workbench image for the given id
*/
public static Image getSharedImage(String imageId) {
return PlatformUI.getWorkbench().getSharedImages().getImage(imageId);
}

}
Loading
Loading