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