From 96980eec17b2baf9447dac1ac5e3ad1893105283 Mon Sep 17 00:00:00 2001 From: Daniel Belina Date: Fri, 28 Aug 2026 13:12:28 -0400 Subject: [PATCH 1/2] Refresh conversation templates dynamically on workspace project changes --- .../chat/services/ChatCompletionService.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java index d1500745..db5a33e7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java @@ -10,12 +10,16 @@ import java.util.Set; import java.util.concurrent.ExecutionException; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.ResourcesPlugin; 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.e4.core.services.events.IEventBroker; -import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4j.WorkspaceFolder; import org.eclipse.ui.PlatformUI; import org.osgi.service.event.EventHandler; @@ -31,6 +35,7 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotScope; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult; import com.microsoft.copilot.eclipse.core.lsp.protocol.TemplateSource; +import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils; import com.microsoft.copilot.eclipse.ui.utils.PreferencesUtils; /** @@ -49,6 +54,7 @@ public class ChatCompletionService implements CopilotAuthStatusListener { private AuthStatusManager authStatusManager; private IEventBroker eventBroker; private EventHandler customPromptsChangedHandler; + private IResourceChangeListener resourceChangeListener; /** * Constructor for the SlashCommandService. @@ -69,9 +75,33 @@ public ChatCompletionService(CopilotLanguageServerConnection lsConnection, AuthS this.eventBroker.subscribe(CopilotEventConstants.TOPIC_CHAT_DID_CHANGE_CUSTOMIZATION_FILES, customPromptsChangedHandler); } + this.resourceChangeListener = event -> { + if (event.getType() == IResourceChangeEvent.POST_CHANGE && hasProjectChanges(event.getDelta())) { + fetchAsync(); + } + }; + try { + ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener, + IResourceChangeEvent.POST_CHANGE); + } catch (IllegalStateException e) { + // Workspace may not be available in standalone headless unit test environments + } syncCommands(this.authStatusManager.getCopilotStatus()); } + private boolean hasProjectChanges(IResourceDelta delta) { + if (delta == null) { + return false; + } + // If the delta is at the root level, check each child + for (IResourceDelta child : delta.getAffectedChildren()) { + if (child.getResource() instanceof IProject) { + return true; + } + } + return delta.getResource() instanceof IProject; + } + private void fetchAsync() { Job.getJobManager().cancel(REFRESH_JOB_FAMILY); @@ -103,7 +133,7 @@ private void initConversationTemplates(IProgressMonitor monitor) { // Pass workspace folders so the language server returns workspace-specific // prompt files (.prompt.md) and skills (SKILL.md) alongside built-in templates. try { - List workspaceFolders = LSPEclipseUtils.getWorkspaceFolders(); + List workspaceFolders = WorkspaceUtils.listWorkspaceFolders(); ConversationTemplate[] rawTemplates = this.lsConnection.listConversationTemplates(workspaceFolders).get(); if (monitor.isCanceled()) { return; @@ -216,5 +246,12 @@ public void dispose() { if (this.eventBroker != null && this.customPromptsChangedHandler != null) { this.eventBroker.unsubscribe(this.customPromptsChangedHandler); } + if (this.resourceChangeListener != null) { + try { + ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceChangeListener); + } catch (IllegalStateException e) { + // Workspace may not be available during shutdown + } + } } } From d35212a905e71598b988483d25c5e45a26f5185c Mon Sep 17 00:00:00 2001 From: Daniel Belina Date: Mon, 31 Aug 2026 09:03:19 -0400 Subject: [PATCH 2/2] Address review comments --- .../chat/services/ChatCompletionService.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java index db5a33e7..4bc45ddc 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java @@ -84,7 +84,7 @@ public ChatCompletionService(CopilotLanguageServerConnection lsConnection, AuthS ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener, IResourceChangeEvent.POST_CHANGE); } catch (IllegalStateException e) { - // Workspace may not be available in standalone headless unit test environments + CopilotCore.LOGGER.info("Workspace not available; skipping resource change listener registration."); } syncCommands(this.authStatusManager.getCopilotStatus()); } @@ -93,13 +93,24 @@ private boolean hasProjectChanges(IResourceDelta delta) { if (delta == null) { return false; } - // If the delta is at the root level, check each child + if (delta.getResource() instanceof IProject) { + int kind = delta.getKind(); + int flags = delta.getFlags(); + if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) { + return true; + } + return (flags & (IResourceDelta.OPEN + | IResourceDelta.DESCRIPTION + | IResourceDelta.MOVED_FROM + | IResourceDelta.MOVED_TO + | IResourceDelta.REPLACED)) != 0; + } for (IResourceDelta child : delta.getAffectedChildren()) { - if (child.getResource() instanceof IProject) { + if (hasProjectChanges(child)) { return true; } } - return delta.getResource() instanceof IProject; + return false; } private void fetchAsync() { @@ -250,7 +261,7 @@ public void dispose() { try { ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceChangeListener); } catch (IllegalStateException e) { - // Workspace may not be available during shutdown + CopilotCore.LOGGER.info("Workspace not available; skipping resource change listener unregistration."); } } }