diff --git a/CONTEXT.md b/CONTEXT.md index 78e6cee3..bb6b45cb 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -27,3 +27,12 @@ connection to the language server. A capability is an *offer of support*, not a A gate that exposes in-development features to a subset of users. It is distinct from the sub-agent policy: after this change, the availability of sub-agents no longer depends on the client preview feature. + +### Workspace folder +A project root associated with a chat so project-scoped prompts, skills, agents, +and instructions can be discovered. A workspace folder does not imply that the +project's files are semantically indexed. + +### Workspace instructions +Custom instructions associated with workspace folders and loaded into chat. +They are distinct from semantic search over project files. diff --git a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProviderTests.java b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProviderTests.java index b75d8f43..9aca6a8f 100644 --- a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProviderTests.java +++ b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProviderTests.java @@ -4,15 +4,21 @@ package com.microsoft.copilot.eclipse.core.lsp; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.IOException; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; import org.junit.jupiter.api.Test; import com.microsoft.copilot.eclipse.core.lsp.protocol.InitializationOptions; class LsStreamConnectionProviderTests { + private static final String LEGACY_WORKSPACE_CONTEXT_PREFERENCE = "workspaceContextEnabled"; + private static final String UI_PREFERENCE_NODE = "com.microsoft.copilot.eclipse.ui"; + @Test void testInitializationOptions() { LsStreamConnectionProvider provider = new LsStreamConnectionProvider(); @@ -23,6 +29,27 @@ void testInitializationOptions() { assertEquals(LsStreamConnectionProvider.EDITOR_PLUGIN_NAME, options.getEditorPluginInfo().getName()); } + @Test + void testInitializationIgnoresLegacyWorkspaceContextPreference() { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(UI_PREFERENCE_NODE); + String previousValue = preferences.get(LEGACY_WORKSPACE_CONTEXT_PREFERENCE, null); + preferences.putBoolean(LEGACY_WORKSPACE_CONTEXT_PREFERENCE, true); + + try { + LsStreamConnectionProvider provider = new LsStreamConnectionProvider(); + + InitializationOptions options = (InitializationOptions) provider.getInitializationOptions(null); + + assertFalse(options.getCopilotCapabilities().isWatchedFiles()); + } finally { + if (previousValue == null) { + preferences.remove(LEGACY_WORKSPACE_CONTEXT_PREFERENCE); + } else { + preferences.put(LEGACY_WORKSPACE_CONTEXT_PREFERENCE, previousValue); + } + } + } + @Test void testStartLanguageServer() throws IOException { LsStreamConnectionProvider provider = new LsStreamConnectionProvider(); diff --git a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/WatchedFileManagerTests.java b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/WatchedFileManagerTests.java deleted file mode 100644 index 5d0723f3..00000000 --- a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/WatchedFileManagerTests.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.core.lsp; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - -import java.util.List; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.microsoft.copilot.eclipse.core.CopilotCore; -import com.microsoft.copilot.eclipse.core.lsp.protocol.GetWatchedFilesRequest; -import com.microsoft.copilot.eclipse.core.utils.FileUtils; - -@ExtendWith(MockitoExtension.class) -class WatchedFileManagerTests { - - private WatchedFileManager watchedFileManager; - - @Mock - private ResourcesPlugin mockResourcesPlugin; - @Mock - private IWorkspace mockWorkspace; - @Mock - private IWorkspaceRoot mockRoot; - @Mock - private IProject mockProject; - @Mock - private IFile mockFile; - @Mock - private IPath mockLocation; - @Mock - private CopilotCore mockCopilotPlugin; - - @BeforeEach - void setUp() { - watchedFileManager = new WatchedFileManager(); - } - - @Test - void emptyWorkspaceReturnsEmptyFileList() throws Exception { - try (MockedStatic mockedPlugin = mockStatic(ResourcesPlugin.class)) { - mockedPlugin.when(ResourcesPlugin::getPlugin).thenReturn(mockResourcesPlugin); - when(mockResourcesPlugin.getWorkspace()).thenReturn(mockWorkspace); - when(mockWorkspace.getRoot()).thenReturn(mockRoot); - when(mockRoot.getProjects()).thenReturn(new IProject[0]); - - GetWatchedFilesRequest request = new GetWatchedFilesRequest(); - request.setExcludeGitignoredFiles(false); - - List results = watchedFileManager.getWatchedFilesWithProgress(request).get().getFiles(); - - assertEquals(0, results.size()); - } - } - - @Test - void collectsFilesFromProject() throws Exception { - try (MockedStatic mockedPlugin = mockStatic(ResourcesPlugin.class); - MockedStatic mockedUtil = mockStatic(FileUtils.class)) { - IProject[] projects = new IProject[] { mockProject }; - IResource[] resources = new IResource[] { mockFile }; - - mockedPlugin.when(ResourcesPlugin::getPlugin).thenReturn(mockResourcesPlugin); - when(mockResourcesPlugin.getWorkspace()).thenReturn(mockWorkspace); - when(mockWorkspace.getRoot()).thenReturn(mockRoot); - when(mockRoot.getProjects()).thenReturn(projects); - - when(mockProject.exists()).thenReturn(true); - when(mockProject.isAccessible()).thenReturn(true); - when(mockProject.members()).thenReturn(resources); - - when(mockFile.exists()).thenReturn(true); - when(mockFile.getLocation()).thenReturn(mockLocation); - mockedUtil.when(() -> FileUtils.getResourceUri((IResource) any())).thenReturn("file:///test/file.txt"); - - GetWatchedFilesRequest request = new GetWatchedFilesRequest(); - request.setExcludeGitignoredFiles(false); - - List results = watchedFileManager.getWatchedFilesWithProgress(request).get().getFiles(); - - assertEquals(1, results.size()); - assertEquals("file:///test/file.txt", results.get(0)); - } - } - -} diff --git a/com.microsoft.copilot.eclipse.core/META-INF/MANIFEST.MF b/com.microsoft.copilot.eclipse.core/META-INF/MANIFEST.MF index 9b5eb2d0..bfa15256 100644 --- a/com.microsoft.copilot.eclipse.core/META-INF/MANIFEST.MF +++ b/com.microsoft.copilot.eclipse.core/META-INF/MANIFEST.MF @@ -47,7 +47,6 @@ Require-Bundle: org.eclipse.lsp4e;bundle-version="0.18.1", org.eclipse.core.runtime;bundle-version="[3.30.0,4.0.0)", org.apache.httpcomponents.client5.httpclient5;bundle-version="5.2.1", org.apache.httpcomponents.core5.httpcore5;bundle-version="5.2.3", - org.eclipse.jgit;bundle-version="6.8.0", org.osgi.service.event;bundle-version="1.4.1", org.eclipse.e4.core.services;bundle-version="2.4.200", org.eclipse.e4.core.contexts;bundle-version="1.12.400" diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java index 101fa319..73817770 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java @@ -25,7 +25,6 @@ private Constants() { public static final String ENABLE_STRICT_SSL = "enableStrictSsl"; public static final String PROXY_KERBEROS_SP = "proxyKerberosSp"; public static final String GITHUB_ENTERPRISE = "githubEnterprise"; - public static final String WORKSPACE_CONTEXT_ENABLED = "workspaceContextEnabled"; public static final String AGENT_MAX_REQUESTS = "agentMaxRequests"; public static final String ENABLE_SKILLS = "enableSkills"; public static final String TRANSCRIPT_SUBDIR = ".copilot/eclipse"; diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/FeatureFlags.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/FeatureFlags.java index 6f4f3a7e..c96b14a6 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/FeatureFlags.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/FeatureFlags.java @@ -3,9 +3,6 @@ package com.microsoft.copilot.eclipse.core; -import org.eclipse.core.runtime.preferences.IEclipsePreferences; -import org.eclipse.core.runtime.preferences.InstanceScope; - /** * Class to manage feature flags for the Copilot plugin. This class allows enabling or disabling features. */ @@ -108,23 +105,6 @@ public void setClientPreviewFeatureEnabled(boolean clientPreviewFeatureEnabled) this.clientPreviewFeatureEnabled = clientPreviewFeatureEnabled; } - /** - * Checks if the workspace context is enabled. - * - * @return true if the workspace context is enabled, false otherwise. - */ - public static boolean isWorkspaceContextEnabled() { - // Directly access the instance scope of Eclipse preferences, which are preferences that are specific to the - // current workspace. So the code won't need to involve any component from the UI plugin. - // The file name for the preferences is "com.microsoft.copilot.eclipse.ui.prefs" - IEclipsePreferences uiPrefs = InstanceScope.INSTANCE.getNode("com.microsoft.copilot.eclipse.ui"); - if (uiPrefs != null) { - return uiPrefs.getBoolean(Constants.WORKSPACE_CONTEXT_ENABLED, false); - } - - return false; - } - /** * Checks if the custom agent is enabled. * Custom agent is enabled only if the organization policy allows it. diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClient.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClient.java index 079c2ec1..07afcc18 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClient.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClient.java @@ -23,7 +23,6 @@ import org.eclipse.lsp4j.ProgressParams; import org.eclipse.lsp4j.ShowDocumentParams; import org.eclipse.lsp4j.ShowDocumentResult; -import org.eclipse.lsp4j.WorkspaceFolder; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; @@ -54,8 +53,6 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.FindFilesResult; import com.microsoft.copilot.eclipse.core.lsp.protocol.FindTextInFilesParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.FindTextInFilesResult; -import com.microsoft.copilot.eclipse.core.lsp.protocol.GetWatchedFilesRequest; -import com.microsoft.copilot.eclipse.core.lsp.protocol.GetWatchedFilesResponse; import com.microsoft.copilot.eclipse.core.lsp.protocol.InvokeClientToolConfirmationParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.InvokeClientToolParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.LanguageModelToolResult; @@ -80,8 +77,6 @@ public class CopilotLanguageClient extends LanguageClientImpl { private static final String HTTP = "http"; //$NON-NLS-1$ private static final String COPILOT_FILE_ENCODING_SECTION = "copilot.file.encoding"; //$NON-NLS-1$ - private WatchedFileManager watchedFileManager; - private IEventBroker eventBroker; private static final String SIGNUP_URL = "https://github.com/github-copilot/signup"; @@ -186,20 +181,6 @@ public CompletableFuture confirmClientTool(InvokeClientToolConfirmatio }); } - // TODO: Should remove workspace-root folder as the projects are not directly under it in Eclipse, and can cause - // confusion in CLS. - @Override - public CompletableFuture> workspaceFolders() { - // Ideally, we should return each IProject as a workspace folder, but given that when - // creating a new conversation or new conversation turn, the uri of the workspace folder - // is required to use the @project (or @workspace) agent. There is no easy way to guess which - // IProject should be used. So we are returning the workspace root as a single workspace folder. - final WorkspaceFolder folder = new WorkspaceFolder(); - folder.setUri(PlatformUtils.getWorkspaceRootUri()); - folder.setName("workspace-root"); // $NON-NLS-1$ - return CompletableFuture.completedFuture(List.of(folder)); - } - @Override public CompletableFuture> configuration(ConfigurationParams params) { return CompletableFuture.supplyAsync(() -> { @@ -223,17 +204,6 @@ public CompletableFuture> configuration(ConfigurationParams params) }); } - /** - * Get the conversation context for the given request. - */ - @JsonRequest("copilot/watchedFiles") - public CompletableFuture getWatchedFiles(GetWatchedFilesRequest params) { - if (watchedFileManager == null) { - watchedFileManager = new WatchedFileManager(); - } - return watchedFileManager.getWatchedFilesWithProgress(params); - } - /** * Notify when mcp server/tool change. */ diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServer.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServer.java index f238d933..18a5be6a 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServer.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServer.java @@ -22,7 +22,6 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.CheckStatusParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.CompletionParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.CompletionResult; -import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCodeCopyParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCreateParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationDestroyParams; @@ -223,12 +222,6 @@ public interface CopilotLanguageServer extends LanguageServer { @JsonRequest("copilot/models") CompletableFuture listModels(NullParams param); - /** - * Get the conversation agents. - */ - @JsonRequest("conversation/agents") - CompletableFuture listAgents(NullParams params); - /** * Notify the code acceptance. */ diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServerConnection.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServerConnection.java index 5844c4f7..e0f533be 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServerConnection.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageServerConnection.java @@ -16,11 +16,9 @@ import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.ExecuteCommandParams; -import org.eclipse.lsp4j.ProgressParams; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceFolder; -import org.eclipse.lsp4j.jsonrpc.Endpoint; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.LanguageServer; @@ -39,7 +37,6 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.CheckStatusParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.CompletionParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.CompletionResult; -import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCodeCopyParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCreateParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationDestroyParams; @@ -50,7 +47,6 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult; import com.microsoft.copilot.eclipse.core.lsp.protocol.CustomizationFileInfo; -import com.microsoft.copilot.eclipse.core.lsp.protocol.DidChangeCopilotWatchedFilesParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.DidShowInlineEditParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.GenerateThinkingTitleParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.GenerateThinkingTitleResponse; @@ -86,7 +82,6 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.CheckQuotaResult; import com.microsoft.copilot.eclipse.core.utils.ChatMessageUtils; import com.microsoft.copilot.eclipse.core.utils.FileUtils; -import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; /** * Language Server for Copilot agent. @@ -293,7 +288,6 @@ public CompletableFuture createConversation(String workDoneTok } if (StringUtils.isBlank(agentSlug)) { - param.setWorkspaceFolder(PlatformUtils.getWorkspaceRootUri()); param.setWorkspaceFolders(workspaceFolders == null ? List.of() : workspaceFolders); param.setTodoList(todos); } else { @@ -346,7 +340,6 @@ public CompletableFuture addConversationTurn(String workDoneToke param.setCustomChatModeId(customChatModeId); if (StringUtils.isBlank(agentSlug)) { - param.setWorkspaceFolder(PlatformUtils.getWorkspaceRootUri()); param.setWorkspaceFolders(workspaceFolders == null ? List.of() : workspaceFolders); param.setTodoList(todoList); } else { @@ -430,24 +423,6 @@ public CompletableFuture listConversationModes(ConversationM return this.languageServerWrapper.execute(fn); } - /** - * List the conversation agents. - */ - public CompletableFuture listConversationAgents() { - Function> fn = server -> { - // return ((CopilotLanguageServer) server).listAgents(new NullParams()); - // Hard code the only supported @project agent. Should revert this when @github agent is supported. - ConversationAgent project = new ConversationAgent(); - project.setSlug("project"); - project.setName("Project"); - project.setDescription("Ask about your project"); - project.setAvatarUrl(null); - - return CompletableFuture.completedFuture(new ConversationAgent[] { project }); - }; - return this.languageServerWrapper.execute(fn); - } - /** * Used to track telemetry from users copying code from chat. */ @@ -537,27 +512,6 @@ public CompletableFuture updateConversationToolsStatus(UpdateConversatio }); } - /** - * Notify the language server that watched files have changed. - */ - public void didChangeWatchedFiles(DidChangeCopilotWatchedFilesParams params) { - this.languageServerWrapper.sendNotification(server -> server.getWorkspaceService().didChangeWatchedFiles(params)); - } - - /** - * Send $/progress notification to the language server. Used for reporting partial results during long-running - * operations like file indexing. - */ - public CompletableFuture sendProgressNotification(ProgressParams progressParams) { - Function> fn = server -> { - if (server instanceof Endpoint endpoint) { - endpoint.notify("$/progress", progressParams); - } - return CompletableFuture.completedFuture(null); - }; - return this.languageServerWrapper.execute(fn); - } - /** * Notify the language server about code acceptance. */ diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java index 4c775f84..925dcc08 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java @@ -34,7 +34,6 @@ import org.osgi.framework.Bundle; import com.microsoft.copilot.eclipse.core.CopilotCore; -import com.microsoft.copilot.eclipse.core.FeatureFlags; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotCapabilities; import com.microsoft.copilot.eclipse.core.lsp.protocol.InitializationOptions; import com.microsoft.copilot.eclipse.core.lsp.protocol.NameAndVersion; @@ -54,8 +53,7 @@ public Object getInitializationOptions(@Nullable URI rootUri) { String bundleVersion = PlatformUtils.getBundleVersion(); NameAndVersion editorPluginInfo = new NameAndVersion(EDITOR_PLUGIN_NAME, bundleVersion); List supportedUriSchemes = PlatformUtils.getSupportedUriSchemes(); - CopilotCapabilities capabilities = new CopilotCapabilities(false, FeatureFlags.isWorkspaceContextEnabled(), - true /*isSubAgentEnabled*/, supportedUriSchemes); + CopilotCapabilities capabilities = new CopilotCapabilities(false, true /*isSubAgentEnabled*/, supportedUriSchemes); return new InitializationOptions(editorInfo, editorPluginInfo, capabilities); } diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/WatchedFileManager.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/WatchedFileManager.java deleted file mode 100644 index 8f8120a9..00000000 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/WatchedFileManager.java +++ /dev/null @@ -1,462 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.core.lsp; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IResourceProxy; -import org.eclipse.core.resources.IResourceProxyVisitor; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jgit.ignore.FastIgnoreRule; -import org.eclipse.jgit.ignore.IgnoreNode; -import org.eclipse.jgit.util.StringUtils; -import org.eclipse.lsp4j.DidChangeConfigurationParams; -import org.eclipse.lsp4j.FileChangeType; -import org.eclipse.lsp4j.FileEvent; -import org.eclipse.lsp4j.ProgressParams; -import org.eclipse.lsp4j.jsonrpc.messages.Either; - -import com.microsoft.copilot.eclipse.core.Constants; -import com.microsoft.copilot.eclipse.core.CopilotCore; -import com.microsoft.copilot.eclipse.core.lsp.protocol.DidChangeCopilotWatchedFilesParams; -import com.microsoft.copilot.eclipse.core.lsp.protocol.GetWatchedFilesRequest; -import com.microsoft.copilot.eclipse.core.lsp.protocol.GetWatchedFilesResponse; -import com.microsoft.copilot.eclipse.core.utils.FileUtils; -import com.microsoft.copilot.eclipse.core.utils.PlatformUtils; - -/** - * Listener for watched files. - */ -class WatchedFileManager { - - public static final String GITIGNORE = ".gitignore"; - - private static final String GIT = ".git"; - - /** - * Currently the CLS only accept at-most 10000 files to index. - */ - private static final int MAX_WATCHED_FILE_NUM = 10000; - - /** - * Batch size for reporting progress during file collection. - */ - private static final int PROGRESS_BATCH_SIZE = 500; - - /** - * the map of all the .gitignore, key is the folder path containing the .gitignore. - */ - private Map gitignoreNodeMap; - - /** - * For some unknown reason, 'copilot/watchedFiles' will be called multiple times. So we cached the file list to save - * the calculation time. - */ - private Set files; - - /** - * Constructor. - */ - public WatchedFileManager() { - gitignoreNodeMap = new LinkedHashMap<>(); - addWatchedFileChangeListener(); - } - - /** - * Get the watched files with support for progress reporting. - * If partialResultToken is provided, files will be sent in batches via $/progress notifications. - * - * @param params the request parameters - * @return CompletableFuture with the final response - */ - public synchronized CompletableFuture getWatchedFilesWithProgress( - GetWatchedFilesRequest params) { - if (files == null) { - files = new LinkedHashSet<>(); - scanWorkspace(params); - } - - final List fileSnapshot = new ArrayList<>(files); - - Either partialToken = params.getPartialResultToken(); - - // If no partial result token, return all files synchronously - if (partialToken == null) { - return CompletableFuture.completedFuture(new GetWatchedFilesResponse(fileSnapshot)); - } - - // Send files in batches via progress notifications - return CompletableFuture.supplyAsync(() -> { - CopilotLanguageServerConnection connection = CopilotCore.getPlugin().getCopilotLanguageServer(); - if (connection == null) { - return new GetWatchedFilesResponse(fileSnapshot); - } - - List batch = new ArrayList<>(); - for (String uri : fileSnapshot) { - batch.add(uri); - if (batch.size() >= PROGRESS_BATCH_SIZE) { - emitProgressBatch(connection, partialToken, batch); - batch.clear(); - } - } - if (!batch.isEmpty()) { - emitProgressBatch(connection, partialToken, batch); - batch.clear(); - } - return new GetWatchedFilesResponse(Collections.emptyList()); - }).exceptionally(ex -> { - CopilotCore.LOGGER.error("Error during watched files collection, returning empty response", ex); - return new GetWatchedFilesResponse(Collections.emptyList()); - }); - } - - private void scanWorkspace(GetWatchedFilesRequest params) { - IProject[] projects = ResourcesPlugin.getPlugin().getWorkspace().getRoot().getProjects(); - - // Load ignore nodes - if (params.isExcludeGitignoredFiles()) { - List gitignoreFiles = new ArrayList<>(); - for (IProject project : projects) { - if (!project.isAccessible()) { - continue; - } - gitignoreFiles.addAll(findGitignoreFiles(project)); - } - - // Sort gitignore files by their path to ensure the closest .gitignore is used first. - gitignoreFiles.sort((f1, f2) -> { - return f2.getLocation().segmentCount() - f1.getLocation().segmentCount(); - }); - - loadGitignoreNodeMap(gitignoreFiles); - } - - // collect watched files - for (IProject project : projects) { - if (!project.isAccessible()) { - continue; - } - - try { - collectFiles(project); - } catch (CoreException e) { - CopilotCore.LOGGER.error("Error when collect files", e); - } - } - } - - private void collectFiles(IContainer container) throws CoreException { - if (files.size() >= MAX_WATCHED_FILE_NUM) { - return; - } - - if (isInvalidToScan(container)) { - return; - } - - for (IResource member : container.members()) { - String uri = FileUtils.getResourceUri(member); - if (uri == null) { - continue; - } - boolean isDirectory = member instanceof IContainer; - if (isDirectory) { - collectFiles((IContainer) member); - } else { - if (shouldCollect(member, false)) { - files.add(uri); - } - } - } - } - - private void emitProgressBatch(CopilotLanguageServerConnection connection, Either token, - List batch) { - if (batch == null || batch.isEmpty()) { - return; - } - try { - ProgressParams progressParams = new ProgressParams(); - progressParams.setToken(token); - // Use a copy of the batch to avoid concurrency issues as the batch list is cleared by the caller - progressParams.setValue(Either.forRight(new GetWatchedFilesResponse(new ArrayList<>(batch)))); - - connection.sendProgressNotification(progressParams).get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - CopilotCore.LOGGER.error("Interrupted while sending progress", e); - } catch (Exception e) { - CopilotCore.LOGGER.error("Failed to send progress notification (may be shutting down)", e); - } - } - - private List findGitignoreFiles(IProject project) { - final List gitignoreFiles = new ArrayList<>(); - - try { - project.accept(new IResourceProxyVisitor() { - @Override - public boolean visit(IResourceProxy proxy) throws CoreException { - if (proxy.getType() == IResource.FILE && proxy.getName().equals(GITIGNORE)) { - gitignoreFiles.add((IFile) proxy.requestResource()); - } - return true; // Continue visiting children - } - }, IResource.NONE); - } catch (CoreException e) { - CopilotCore.LOGGER.error("Error when finding gitignore files.", e); - } - - return gitignoreFiles; - } - - private void loadGitignoreNodeMap(List gitignoreFiles) { - for (IFile gitignoreFile : gitignoreFiles) { - try { - IgnoreNode ignoreNode = new IgnoreNode() { - // This is to fix the issue that when a directory is ignored, the files under that directory are not ignored. - @Override - public @Nullable Boolean checkIgnored(String entryPath, boolean isDirectory) { - for (int i = this.getRules().size() - 1; i > -1; i--) { - FastIgnoreRule rule = this.getRules().get(i); - // Enable relative path match when pathMatch is false. - if (rule.isMatch(entryPath, isDirectory, false)) { - return Boolean.valueOf(rule.getResult()); - } - } - return null; - } - }; - - if (gitignoreFile.getParent() != null && gitignoreFile.getParent().getLocation() != null) { - ignoreNode.parse(gitignoreFile.getContents()); - gitignoreNodeMap.put(gitignoreFile.getParent().getLocation(), ignoreNode); - } - } catch (IOException | CoreException e) { - CopilotCore.LOGGER.error("Error when parse git ignore file: ", e); - } - } - } - - private boolean isInvalidToScan(IContainer container) { - if (container == null || !container.exists()) { - return true; - } - - if (container.isDerived() || container.isTeamPrivateMember()) { - return true; - } - - // Do not include .git content, this block list may need to expand per requirement. - if (GIT.equals(container.getName())) { - return true; - } - - return false; - } - - private void addWatchedFileChangeListener() { - WatchedFilesListener watchedFilesListener = new WatchedFilesListener(); - ResourcesPlugin.getWorkspace().addResourceChangeListener(watchedFilesListener, - IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE); - } - - private boolean shouldCollect(IResource resource, boolean isDirectory) { - if (resource == null || !resource.exists()) { - return false; - } - - // Check if resource location is available - IPath resourceLocation = resource.getLocation(); - if (resourceLocation == null) { - return false; - } - - if (resource.isDerived() || resource.isTeamPrivateMember()) { - return false; - } - - String extension = resource.getFileExtension(); - if (!StringUtils.isEmptyOrNull(extension) && Constants.EXCLUDED_CURRENT_FILE_TYPE.contains(extension)) { - return false; - } - - for (Map.Entry entry : gitignoreNodeMap.entrySet()) { - IPath directoryPath = entry.getKey(); - if (!directoryPath.isPrefixOf(resourceLocation)) { - continue; - } - - IgnoreNode ignoreNode = entry.getValue(); - IPath relativePath = resourceLocation.makeRelativeTo(directoryPath); - IgnoreNode.MatchResult matchResult = ignoreNode.isIgnored(relativePath.toString(), isDirectory); - - switch (matchResult) { - case IGNORED: - return false; - case NOT_IGNORED: - return true; - case CHECK_PARENT: - default: - } - } - - return true; - } - - private final class WatchedFilesListener implements IResourceChangeListener { - @Override - public void resourceChanged(IResourceChangeEvent event) { - DidChangeCopilotWatchedFilesParams params = toDidChangeCopilotWatchedFilesParams(event); - if (params == null || (params.getChanges() != null && params.getChanges().isEmpty())) { - return; - } - // If shutting down, language server will be set to null, so ignore the event - final CopilotLanguageServerConnection connection = CopilotCore.getPlugin().getCopilotLanguageServer(); - if (connection != null) { - connection.didChangeWatchedFiles(params); - } - } - - private @Nullable DidChangeCopilotWatchedFilesParams toDidChangeCopilotWatchedFilesParams(IResourceChangeEvent e) { - if (!isPostChangeEvent(e) && !isPreDeleteEvent(e)) { - return null; - } - - List fileChanges = new ArrayList<>(); - - if (isPostChangeEvent(e) && e.getDelta() != null) { - collectFileChanges(e.getDelta(), fileChanges); - } else if (isPreDeleteEvent(e) && e.getResource() != null) { - IResource resource = e.getResource(); - if (resource.exists()) { - addResourceDeletion(resource, fileChanges); - } - } - - fileChanges.removeIf(fileEvent -> fileEvent.getUri() == null); - if (fileChanges.isEmpty()) { - return null; - } - - return new DidChangeCopilotWatchedFilesParams(PlatformUtils.getWorkspaceRootUri(), fileChanges); - } - - private void collectFileChanges(IResourceDelta delta, List changes) { - // Process this delta node - IResource resource = delta.getResource(); - if (resource == null || !resource.exists() && !isRemoveEvent(delta)) { - return; - } - - // For files, add the change if it's not ignored - if (resource.getType() == IResource.FILE) { - String uri = FileUtils.getResourceUri(resource); - if (shouldCollect(resource, false)) { - if (isAddEvent(delta)) { - changes.add(createFileEvent(uri, FileChangeType.Created)); - } else if (isRemoveEvent(delta)) { - changes.add(createFileEvent(uri, FileChangeType.Deleted)); - } else if ((delta.getFlags() & IResourceDelta.CONTENT) != 0) { - changes.add(createFileEvent(uri, FileChangeType.Changed)); - } else if ((delta.getFlags() & IResourceDelta.ENCODING) != 0) { - // File encoding changed - notify CLS to invalidate cache - notifyEncodingChange(uri); - } - } - } - - // Recursively process child deltas - for (IResourceDelta childDelta : delta.getAffectedChildren()) { - collectFileChanges(childDelta, changes); - } - } - - private void addResourceDeletion(IResource resource, List changes) { - // If it's a file, add it directly - if (resource.getType() == IResource.FILE) { - String uri = FileUtils.getResourceUri(resource); - if (shouldCollect(resource, false)) { - changes.add(createFileEvent(uri, FileChangeType.Deleted)); - } - return; - } - - // For containers, recursively process all children - if (resource instanceof IContainer container) { - try { - for (IResource child : container.members()) { - addResourceDeletion(child, changes); - } - } catch (CoreException ex) { - CopilotCore.LOGGER.error("Error processing resource deletion", ex); - } - } - } - - private boolean isPostChangeEvent(IResourceChangeEvent e) { - return e.getType() == IResourceChangeEvent.POST_CHANGE; - } - - private boolean isPreDeleteEvent(IResourceChangeEvent e) { - return e.getType() == IResourceChangeEvent.PRE_DELETE; - } - - private boolean isAddEvent(IResourceDelta delta) { - return delta.getKind() == IResourceDelta.ADDED; - } - - private boolean isRemoveEvent(IResourceDelta delta) { - return delta.getKind() == IResourceDelta.REMOVED; - } - - @Nullable - private FileEvent createFileEvent(String uri, FileChangeType type) { - if (uri == null) { - return null; - } - FileEvent event = new FileEvent(); - event.setUri(uri); - event.setType(type); - return event; - } - - /** - * Notify the language server about encoding changes for specific files. - * This triggers cache invalidation in CLS for the affected files. - * - * @param fileUri the URI of the file whose encoding changed - */ - private void notifyEncodingChange(String fileUri) { - CopilotLanguageServerConnection connection = CopilotCore.getPlugin().getCopilotLanguageServer(); - if (connection != null) { - Map copilotSettings = Map.of( - "encodingChanges", List.of(fileUri) - ); - DidChangeConfigurationParams params = new DidChangeConfigurationParams(); - params.setSettings(Map.of("copilot", copilotSettings)); - connection.updateConfig(params); - } - } - } -} diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/ConversationAgent.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/ConversationAgent.java deleted file mode 100644 index ab2f53c5..00000000 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/ConversationAgent.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.core.lsp.protocol; - -import java.util.Objects; - -import org.apache.commons.lang3.builder.ToStringBuilder; - -/** - * Agents are used to provide additional functionality to the conversation, e.g. resolving the whole project context. - * Agents can be used by sending a message starting with @name - */ -public class ConversationAgent { - - private String slug; - - private String name; - - private String description; - - private String avatarUrl; - - public String getSlug() { - return slug; - } - - public void setSlug(String slug) { - this.slug = slug; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getAvatarUrl() { - return avatarUrl; - } - - public void setAvatarUrl(String avatarUrl) { - this.avatarUrl = avatarUrl; - } - - @Override - public int hashCode() { - return Objects.hash(avatarUrl, description, name, slug); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - ConversationAgent other = (ConversationAgent) obj; - return Objects.equals(avatarUrl, other.avatarUrl) && Objects.equals(description, other.description) - && Objects.equals(name, other.name) && Objects.equals(slug, other.slug); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - builder.append("slug", slug); - builder.append("name", name); - builder.append("description", description); - builder.append("avatarUrl", avatarUrl); - return builder.toString(); - } -} diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/CopilotCapabilities.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/CopilotCapabilities.java index a9f5cb00..14b740c4 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/CopilotCapabilities.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/CopilotCapabilities.java @@ -17,7 +17,8 @@ public class CopilotCapabilities { private boolean fetch; - private boolean watchedFiles; + // CLS still expects this capability in the initialization payload, but Eclipse no longer supports the integration. + private final boolean watchedFiles = false; private boolean didChangeFeatureFlags; @@ -35,15 +36,18 @@ public class CopilotCapabilities { /** * Creates a new CopilotCapabilities. + * + * @param fetch whether the client supports fetch requests + * @param subAgent whether the client supports sub-agents + * @param contentProvider supported content-provider URI schemes */ - public CopilotCapabilities(boolean fetch, boolean watchedFiles, boolean subAgent, List contentProvider) { + public CopilotCapabilities(boolean fetch, boolean subAgent, List contentProvider) { this.didChangeFeatureFlags = true; this.stateDatabase = true; this.cveRemediatorAgent = true; this.debuggerAgent = JdtUtils.isJdtDebugAvailable() && PlatformUtils.isNightly(); this.manageTodoListTool = true; this.fetch = fetch; - this.watchedFiles = watchedFiles; this.subAgent = subAgent; this.contentProvider = contentProvider; } @@ -60,10 +64,6 @@ public boolean isWatchedFiles() { return watchedFiles; } - public void setWatchedFiles(boolean watchedFiles) { - this.watchedFiles = watchedFiles; - } - public void setStateDatabase(boolean stateDatabase) { this.stateDatabase = stateDatabase; } diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/DidChangeCopilotWatchedFilesParams.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/DidChangeCopilotWatchedFilesParams.java deleted file mode 100644 index ccd0e198..00000000 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/DidChangeCopilotWatchedFilesParams.java +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.core.lsp.protocol; - -import java.util.List; -import java.util.Objects; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.eclipse.lsp4j.DidChangeWatchedFilesParams; -import org.eclipse.lsp4j.FileEvent; -import org.eclipse.lsp4j.jsonrpc.validation.NonNull; - -/** - * See https://github.com/microsoft/copilot-client/blob/main/agent/API_INTERNAL.md#workspacedidchangewatchedfiles. - */ -public class DidChangeCopilotWatchedFilesParams extends DidChangeWatchedFilesParams { - private String workspaceUri; - - /** - * Constructor. - */ - public DidChangeCopilotWatchedFilesParams(@NonNull final String workspaceUri, - @NonNull final List changes) { - super(changes); - this.workspaceUri = workspaceUri; - } - - public String getWorkkpaceUri() { - return workspaceUri; - } - - public void setWorkkpaceUri(String workspaceUri) { - this.workspaceUri = workspaceUri; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + Objects.hash(workspaceUri); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - DidChangeCopilotWatchedFilesParams other = (DidChangeCopilotWatchedFilesParams) obj; - return Objects.equals(workspaceUri, other.workspaceUri); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - builder.append("workspaceUri", workspaceUri); - builder.append("changes", getChanges()); - return builder.toString(); - } - -} diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/GetWatchedFilesRequest.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/GetWatchedFilesRequest.java deleted file mode 100644 index d01c4297..00000000 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/GetWatchedFilesRequest.java +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.core.lsp.protocol; - -import java.util.Objects; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.eclipse.lsp4j.jsonrpc.messages.Either; - -/** - * Request to get the list of watched files. See: - * https://github.com/microsoft/copilot-client/blob/main/agent/API_INTERNAL.md#copilotwatchedfiles - */ -public class GetWatchedFilesRequest { - - private String workspaceUri; - - private boolean excludeGitignoredFiles; - - private boolean excludeIdeIgnoredFiles; - - /** - * An optional token for reporting partial results via $/progress notifications. - */ - private Either partialResultToken; - - public String getWorkspaceUri() { - return workspaceUri; - } - - public void setWorkspaceUri(String uri) { - this.workspaceUri = uri; - } - - public boolean isExcludeGitignoredFiles() { - return excludeGitignoredFiles; - } - - public void setExcludeGitignoredFiles(boolean excludeGitignoredFiles) { - this.excludeGitignoredFiles = excludeGitignoredFiles; - } - - public boolean isExcludeIdeIgnoredFiles() { - return excludeIdeIgnoredFiles; - } - - public void setExcludeIdeIgnoredFiles(boolean excludeIdeIgnoredFiles) { - this.excludeIdeIgnoredFiles = excludeIdeIgnoredFiles; - } - - public Either getPartialResultToken() { - return partialResultToken; - } - - public void setPartialResultToken(Either partialResultToken) { - this.partialResultToken = partialResultToken; - } - - @Override - public int hashCode() { - return Objects.hash(excludeGitignoredFiles, excludeIdeIgnoredFiles, workspaceUri, partialResultToken); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - GetWatchedFilesRequest other = (GetWatchedFilesRequest) obj; - return excludeGitignoredFiles == other.excludeGitignoredFiles - && excludeIdeIgnoredFiles == other.excludeIdeIgnoredFiles - && Objects.equals(workspaceUri, other.workspaceUri) - && Objects.equals(partialResultToken, other.partialResultToken); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - builder.append("uri", workspaceUri); - builder.append("excludeGitignoredFiles", excludeGitignoredFiles); - builder.append("excludeIdeIgnoredFiles", excludeIdeIgnoredFiles); - builder.append("partialResultToken", partialResultToken); - return builder.toString(); - } - -} diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/GetWatchedFilesResponse.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/GetWatchedFilesResponse.java deleted file mode 100644 index c7067570..00000000 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/GetWatchedFilesResponse.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package com.microsoft.copilot.eclipse.core.lsp.protocol; - -import java.util.List; -import java.util.Objects; - -import org.apache.commons.lang3.builder.ToStringBuilder; - -/** - * Response to get the list of watched files. See: - * https://github.com/microsoft/copilot-client/blob/main/agent/API_INTERNAL.md#copilotwatchedfiles - */ -public class GetWatchedFilesResponse { - - private List files; - - /** - * Gets the list of files. - * - * @param files the URI string list of files. - */ - public GetWatchedFilesResponse(List files) { - this.files = files; - } - - public List getFiles() { - return files; - } - - public void setFiles(List files) { - this.files = files; - } - - @Override - public int hashCode() { - return Objects.hash(files); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - GetWatchedFilesResponse other = (GetWatchedFilesResponse) obj; - return Objects.equals(files, other.files); - } - - @Override - public String toString() { - ToStringBuilder builder = new ToStringBuilder(this); - builder.append("files", files); - return builder.toString(); - } - -} diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/PlatformUtils.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/PlatformUtils.java index 277ed92c..e619baf1 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/PlatformUtils.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/PlatformUtils.java @@ -6,7 +6,6 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -16,13 +15,10 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.lsp4e.LSPEclipseUtils; import org.osgi.framework.Bundle; import org.osgi.framework.Version; @@ -186,15 +182,6 @@ public static Object getPropertyWithReflection(Object object, String propertyNam return null; } - /** - * Return the workspace root URI string. - */ - public static String getWorkspaceRootUri() { - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - URI uri = LSPEclipseUtils.toUri((IResource) workspaceRoot); - return uri != null ? uri.toASCIIString() : ""; - } - /** * Get the charset name from an IFile, defaulting to UTF-8 if empty or on error. * diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessorTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessorTest.java new file mode 100644 index 00000000..7c0e6880 --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessorTest.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui.chat; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.lang.reflect.Constructor; + +import org.eclipse.jface.text.TextViewer; +import org.eclipse.jface.text.contentassist.IContentAssistProcessor; +import org.junit.jupiter.api.Test; + +import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; + +class ChatAssistProcessorTest { + + @Test + void testContentAssistAutoActivationUsesSlashOnly() throws ReflectiveOperationException { + // The UI test bundle uses a separate OSGi classloader, so reflection is required for this package-private class. + Class processorClass = Class.forName("com.microsoft.copilot.eclipse.ui.chat.ChatAssistProcessor"); + Constructor constructor = processorClass.getDeclaredConstructor(TextViewer.class, ChatServiceManager.class); + constructor.setAccessible(true); + IContentAssistProcessor processor = (IContentAssistProcessor) constructor.newInstance(null, null); + + assertArrayEquals(new char[] { '/' }, processor.getCompletionProposalAutoActivationCharacters()); + assertArrayEquals(new char[] { '/' }, processor.getContextInformationAutoActivationCharacters()); + } +} diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionServiceTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionServiceTest.java index 79f9c3df..c2d3aa42 100644 --- a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionServiceTest.java +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionServiceTest.java @@ -28,7 +28,6 @@ import com.microsoft.copilot.eclipse.core.Constants; import com.microsoft.copilot.eclipse.core.lsp.CopilotLanguageServerConnection; import com.microsoft.copilot.eclipse.core.lsp.protocol.ChatMode; -import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotScope; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult; @@ -71,8 +70,6 @@ static void setUp() { List.of(CopilotScope.CHAT_PANEL), null); ConversationTemplate[] templates = new ConversationTemplate[] { template }; when(mockLsConnection.listConversationTemplates(any())).thenReturn(CompletableFuture.completedFuture(templates)); - when(mockLsConnection.listConversationAgents()) - .thenReturn(CompletableFuture.completedFuture(new ConversationAgent[0])); when(mockAuthStatusManager.getCopilotStatus()).thenReturn(CopilotStatusResult.OK); chatCompletionService = new ChatCompletionService(mockLsConnection, mockAuthStatusManager); try { @@ -116,6 +113,8 @@ void testIsBrokenCommand() { void testIsCommand() { assertTrue(chatCompletionService.isCommand("/test")); assertFalse(chatCompletionService.isCommand("/invalid")); + assertFalse(chatCompletionService.isCommand("@workspace")); + assertFalse(chatCompletionService.isCommand("@project")); } @Test diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessor.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessor.java index 0b961438..623261f5 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessor.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatAssistProcessor.java @@ -4,12 +4,9 @@ package com.microsoft.copilot.eclipse.ui.chat; import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.List; import java.util.Map.Entry; -import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.eclipse.jface.text.BadLocationException; @@ -30,7 +27,6 @@ import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.core.lsp.protocol.ChatMode; -import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate; import com.microsoft.copilot.eclipse.core.lsp.protocol.TemplateSource; import com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService; @@ -167,26 +163,6 @@ private int getMatchPriority(ConversationTemplate template, String lowerPrefix) return -1; } - public ICompletionProposal[] createCopilotCompletionAgentProposals(String prefix) { - List proposals = new ArrayList<>(); - ChatCompletionService commandService = chatServiceManager.getChatCompletionService(); - if (!commandService.isAgentsReady()) { - return new ICompletionProposal[0]; - } - // So far no template supports agent mode. - if (Objects.equals(chatServiceManager.getUserPreferenceService().getActiveChatMode(), ChatMode.Agent)) { - return new ICompletionProposal[0]; - } - ConversationAgent[] agents = commandService.getAgents(); - for (ConversationAgent agent : agents) { - if (prefix.isEmpty() || agent.getSlug().startsWith(prefix)) { - proposals - .add(new ChatCompletionProposal(ChatCompletionService.AGENT_MARK, agent.getSlug(), agent.getDescription())); - } - } - return proposals.toArray(new ICompletionProposal[proposals.size()]); - } - @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { // Provide your completion proposals here @@ -201,10 +177,6 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int return createCopilotCompletionTemplateProposals(lineText.substring(1)); } - // Check if the "@" are at the beginning of the line - if (lineText.startsWith(ChatCompletionService.AGENT_MARK)) { - return createCopilotCompletionAgentProposals(lineText.substring(1)); - } } catch (BadLocationException e) { CopilotCore.LOGGER.error(e); } @@ -218,12 +190,12 @@ public IContextInformation[] computeContextInformation(ITextViewer viewer, int o @Override public char[] getCompletionProposalAutoActivationCharacters() { - return new char[] { '/', '@' }; + return new char[] { '/' }; } @Override public char[] getContextInformationAutoActivationCharacters() { - return new char[] { '/', '@' }; + return new char[] { '/' }; } @Override diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatView.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatView.java index c5aa62c6..30ceac7e 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatView.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatView.java @@ -88,7 +88,6 @@ import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.UiConstants; import com.microsoft.copilot.eclipse.ui.chat.services.AgentToolService; -import com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService; import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; import com.microsoft.copilot.eclipse.ui.chat.services.DebugEventAutoResponseHandler; import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService; @@ -1030,10 +1029,8 @@ public void setFocus() { private void onSendInternal(String workDoneToken, String message, String agentSlug, String agentJobWorkspaceFolder, boolean createNewTurn) { - String processedMessage = replaceWorkspaceCommand(message); - // Persist the user input to history - chatServiceManager.getUserPreferenceService().addInputToHistory(processedMessage); + chatServiceManager.getUserPreferenceService().addInputToHistory(message); final ChatMode activeChatMode = chatServiceManager.getUserPreferenceService().getActiveChatMode(); @@ -1096,7 +1093,7 @@ private void onSendInternal(String workDoneToken, String message, String agentSl flushPendingAttachedFiles(this.conversationId); // Continue existing conversation - persist user message and send to existing conversation if (persistenceManager != null) { - this.persistUserTurnFuture = persistenceManager.persistUserTurnInfo(conversationId, null, processedMessage, + this.persistUserTurnFuture = persistenceManager.persistUserTurnInfo(conversationId, null, message, activeModel, chatModeName, customChatModeId, currentFile, references); } @@ -1110,7 +1107,7 @@ private void onSendInternal(String workDoneToken, String message, String agentSl String turnReasoningEffort = chatServiceManager.getModelService().resolveEffectiveReasoningEffort(activeModel); CompletableFuture addConversationFuture = ls.addConversationTurn(workDoneToken, conversationId, - processedMessage, references, currentFile, currentSelection, activeModel, turnReasoningEffort, chatModeName, + message, references, currentFile, currentSelection, activeModel, turnReasoningEffort, chatModeName, customChatModeId, currentTodos, agentSlug, agentJobWorkspaceFolder, deriveWorkspaceFolders(currentFile, references)); conversationFutures.add(addConversationFuture); @@ -1146,7 +1143,7 @@ private void onSendInternal(String workDoneToken, String message, String agentSl // Load turns from the history conversation and persist user turn with current conversation ID turns = persistenceManager.loadConversationTurns(this.conversationId); this.persistUserTurnFuture = persistenceManager.persistUserTurnInfo(this.conversationId, null, - processedMessage, activeModel, chatModeName, customChatModeId, currentFile, references); + message, activeModel, chatModeName, customChatModeId, currentFile, references); // Set conversationId and last completed turnId for CLS server-side session restoration. restoredConversationId = this.conversationId; @@ -1169,20 +1166,20 @@ private void onSendInternal(String workDoneToken, String message, String agentSl // Generate a temporary ID for brand new conversation and persist user turn this.conversationId = UUID.randomUUID().toString(); this.persistUserTurnFuture = persistenceManager.persistUserTurnInfo(this.conversationId, null, - processedMessage, activeModel, chatModeName, customChatModeId, currentFile, references); + message, activeModel, chatModeName, customChatModeId, currentFile, references); } List workspaceFolders = deriveWorkspaceFolders(currentFile, references); String reasoningEffort = chatServiceManager.getModelService().resolveEffectiveReasoningEffort(activeModel); CompletableFuture createConversationFuture = null; if (StringUtils.isBlank(agentSlug)) { - createConversationFuture = ls.createConversation(workDoneToken, processedMessage, references, currentFile, + createConversationFuture = ls.createConversation(workDoneToken, message, references, currentFile, currentSelection, turns, activeModel, reasoningEffort, chatModeName, customChatModeId, todosToRestore, null, null, restoredConversationId, restoreToTurnId, workspaceFolders); } else { // For conversations sending to agents, include agentSlug and specify the target agentJobWorkspaceFolder // Don't send todo list for agent jobs - agents manage their own todo state independently - createConversationFuture = ls.createConversation(workDoneToken, processedMessage, references, currentFile, + createConversationFuture = ls.createConversation(workDoneToken, message, references, currentFile, currentSelection, turns, activeModel, reasoningEffort, chatModeName, customChatModeId, null, agentSlug, agentJobWorkspaceFolder, restoredConversationId, restoreToTurnId, workspaceFolders); } @@ -1271,24 +1268,6 @@ private boolean isCompressionForActiveConversation(String compressionConversatio || StringUtils.equals(compressionConversationId, this.subagentConversationId); } - /** - * Align with @Workspace of vscode, because we are actually indexing the whole workspace, not a single project. - * (@Project is only for IntelliJ.) - * - * @param message the original message - * @return the processed message - */ - private String replaceWorkspaceCommand(String message) { - if (!StringUtils.isBlank(message) - && chatServiceManager.getUserPreferenceService().getActiveChatMode() == ChatMode.Ask - && message.trim().startsWith(ChatCompletionService.AGENT_MARK + "workspace")) { - return message.replaceFirst(ChatCompletionService.AGENT_MARK + "workspace", - ChatCompletionService.AGENT_MARK + "project"); - } - - return message; - } - private void displayErrorAndResetSendButton(String workDoneToken, String message) { if (message == null) { message = Messages.chat_warnWidget_defaultErrorMsg; 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 1f0953cd..d1500745 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,6 @@ import java.util.Set; import java.util.concurrent.ExecutionException; -import org.eclipse.core.resources.IResource; -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.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -29,12 +23,10 @@ import com.microsoft.copilot.eclipse.core.AuthStatusManager; import com.microsoft.copilot.eclipse.core.CopilotAuthStatusListener; import com.microsoft.copilot.eclipse.core.CopilotCore; -import com.microsoft.copilot.eclipse.core.FeatureFlags; import com.microsoft.copilot.eclipse.core.chat.service.ICustomizationFileService.CustomizationType; import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants; import com.microsoft.copilot.eclipse.core.lsp.CopilotLanguageServerConnection; import com.microsoft.copilot.eclipse.core.lsp.protocol.ChatMode; -import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationAgent; import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotScope; import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult; @@ -45,11 +37,9 @@ * Service for handling slash commands. */ public class ChatCompletionService implements CopilotAuthStatusListener { - public static final String AGENT_MARK = "@"; public static final String TEMPLATE_MARK = "/"; private volatile List templates = List.of(); - private volatile List agents = List.of(); private volatile Set allCommands = Set.of(); // Exclude intelliJ sepcific slash commands private static final Set EXCLUDED_COMMANDS = Set.of("help", "feedback"); @@ -57,13 +47,9 @@ public class ChatCompletionService implements CopilotAuthStatusListener { "com.microsoft.copilot.eclipse.chat.services.SlashCommandService.refreshJob"; private CopilotLanguageServerConnection lsConnection; private AuthStatusManager authStatusManager; - private IResourceChangeListener skillFileListener; private IEventBroker eventBroker; private EventHandler customPromptsChangedHandler; - private static final String SKILL_FILE_NAME = "SKILL.md"; - private static final String PROMPT_FILE_SUFFIX = ".prompt.md"; - /** * Constructor for the SlashCommandService. */ @@ -71,10 +57,6 @@ public ChatCompletionService(CopilotLanguageServerConnection lsConnection, AuthS this.authStatusManager = authStatusManager; this.lsConnection = lsConnection; this.authStatusManager.addCopilotAuthStatusListener(this); - // TODO: Remove this listener once workspace-root is removed from workspaceFolders in CopilotLanguageClient as CLS - // can watch the project prompt file change directly. - this.skillFileListener = new SkillFileChangeListener(); - ResourcesPlugin.getWorkspace().addResourceChangeListener(skillFileListener, IResourceChangeEvent.POST_CHANGE); this.eventBroker = PlatformUI.getWorkbench().getService(IEventBroker.class); if (this.eventBroker != null) { // Templates only surface skills and prompts, so ignore instruction/agent changes. @@ -114,7 +96,6 @@ public boolean belongsTo(Object family) { private void initConversationTemplates(IProgressMonitor monitor) { List newTemplates = new ArrayList<>(); - List newAgents = new ArrayList<>(); Set newCommands = new HashSet<>(); boolean skillsEnabled = PreferencesUtils.isSkillsEnabled(); @@ -140,41 +121,9 @@ private void initConversationTemplates(IProgressMonitor monitor) { CopilotCore.LOGGER.error(e); } - if (monitor.isCanceled()) { - return; - } - - // Command: @*** - try { - ConversationAgent[] rawAgents = this.lsConnection.listConversationAgents().get(); - if (monitor.isCanceled()) { - return; - } - for (ConversationAgent agent : rawAgents) { - String agentSlug = agent.getSlug(); - // @see ui.chat.ChatView#replaceWorkspaceCommand(String) - if (agentSlug.equals("project")) { - if (!FeatureFlags.isWorkspaceContextEnabled()) { - continue; - } - - agent.setSlug("workspace"); - } - newAgents.add(agent); - newCommands.add(AGENT_MARK + agent.getSlug()); - } - } catch (InterruptedException | ExecutionException e) { - CopilotCore.LOGGER.error(e); - } - - if (monitor.isCanceled()) { - return; - } - // Atomically swap the cached data so readers always see a consistent snapshot. // Publish immutable snapshots so readers cannot accidentally mutate a live collection. this.templates = List.copyOf(newTemplates); - this.agents = List.copyOf(newAgents); this.allCommands = Set.copyOf(newCommands); } @@ -241,14 +190,6 @@ public boolean isTempaltesReady() { return templates != null && templates.size() > 0; } - public boolean isAgentsReady() { - return agents != null && agents.size() > 0; - } - - public ConversationAgent[] getAgents() { - return agents.toArray(new ConversationAgent[0]); - } - @Override public void onDidCopilotStatusChange(CopilotStatusResult copilotStatusResult) { String status = copilotStatusResult.getStatus(); @@ -263,7 +204,6 @@ private void syncCommands(String status) { default: this.allCommands = Set.of(); this.templates = List.of(); - this.agents = List.of(); break; } } @@ -273,70 +213,8 @@ private void syncCommands(String status) { */ public void dispose() { this.authStatusManager.removeCopilotAuthStatusListener(this); - ResourcesPlugin.getWorkspace().removeResourceChangeListener(skillFileListener); if (this.eventBroker != null && this.customPromptsChangedHandler != null) { this.eventBroker.unsubscribe(this.customPromptsChangedHandler); } } - - /** - * Listens for workspace resource changes involving SKILL.md or .prompt.md files and triggers a template refresh when - * such files are added, removed, or changed. - * - *

TODO: Remove this listener once workspace-root is removed from workspaceFolders in CopilotLanguageClient as CLS - * can watch the project prompt file change directly. - */ - private class SkillFileChangeListener implements IResourceChangeListener { - @Override - public void resourceChanged(IResourceChangeEvent event) { - IResourceDelta delta = event.getDelta(); - if (delta == null) { - return; - } - boolean[] needsRefresh = { false }; - try { - delta.accept(childDelta -> { - if (needsRefresh[0]) { - return false; - } - if (!shouldVisitDelta(childDelta)) { - return false; - } - if (isPromptOrSkillFileDelta(childDelta)) { - needsRefresh[0] = true; - return false; - } - return true; - }); - } catch (CoreException e) { - CopilotCore.LOGGER.error("Error visiting resource delta for skill file changes", e); - } - if (needsRefresh[0]) { - fetchAsync(); - } - } - - private boolean shouldVisitDelta(IResourceDelta delta) { - IResource resource = delta.getResource(); - return resource != null && !resource.isDerived() && !resource.isTeamPrivateMember(); - } - - private boolean isPromptOrSkillFileDelta(IResourceDelta delta) { - IResource resource = delta.getResource(); - if (resource.getType() != IResource.FILE || !isRelevantFileDelta(delta)) { - return false; - } - - String name = resource.getName(); - return SKILL_FILE_NAME.equals(name) || name.endsWith(PROMPT_FILE_SUFFIX); - } - - private boolean isRelevantFileDelta(IResourceDelta delta) { - int kind = delta.getKind(); - if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) { - return true; - } - return kind == IResourceDelta.CHANGED && (delta.getFlags() & IResourceDelta.CONTENT) != 0; - } - } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ChatPreferencesPage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ChatPreferencesPage.java index bff5641f..7e65bfe5 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ChatPreferencesPage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ChatPreferencesPage.java @@ -3,8 +3,6 @@ package com.microsoft.copilot.eclipse.ui.preferences; -import org.eclipse.core.runtime.preferences.InstanceScope; -import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.preference.BooleanFieldEditor; import org.eclipse.jface.preference.FieldEditorPreferencePage; @@ -16,11 +14,8 @@ import org.eclipse.swt.widgets.Label; 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; -import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.ui.CopilotUi; /** @@ -44,15 +39,6 @@ public void createFieldEditors() { GridDataFactory gdf = GridDataFactory.fillDefaults().span(2, 1).align(SWT.FILL, SWT.FILL).grab(true, false); - Composite workspaceContextComposite = createSectionComposite(parent, gdf); - BooleanFieldEditor workspaceContextField = new BooleanFieldEditor(Constants.WORKSPACE_CONTEXT_ENABLED, - Messages.preferences_page_watched_files, SWT.WRAP, workspaceContextComposite); - applyFieldWidthHint(workspaceContextField, workspaceContextComposite); - addField(workspaceContextField); - - addNote(parent, Messages.preferences_page_watched_files_note_content); - addSeparator(parent); - Composite skillsComposite = createSectionComposite(parent, gdf); BooleanFieldEditor skillsField = new BooleanFieldEditor(Constants.ENABLE_SKILLS, Messages.preferences_page_skills_enabled, SWT.WRAP, skillsComposite); @@ -79,34 +65,6 @@ public void init(IWorkbench workbench) { setPreferenceStore(CopilotUi.getPlugin().getPreferenceStore()); } - @Override - public boolean performOk() { - final boolean oldWorkspaceContextValue = getPreferenceStore().getBoolean(Constants.WORKSPACE_CONTEXT_ENABLED); - - final boolean result = super.performOk(); - boolean newWorkspaceContextValue = getPreferenceStore().getBoolean(Constants.WORKSPACE_CONTEXT_ENABLED); - - boolean isWorkspaceContextChanged = oldWorkspaceContextValue ^ newWorkspaceContextValue; - if (isWorkspaceContextChanged) { - try { - InstanceScope.INSTANCE.getNode(CopilotUi.getPlugin().getBundle().getSymbolicName()).flush(); - } catch (BackingStoreException e) { - CopilotCore.LOGGER.error("Failed to save preference 'Enable workspace context'", e); - } - - boolean restart = MessageDialog.openQuestion(getShell(), Messages.preferences_page_restart_required, - Messages.preferences_page_restart_question); - - if (restart) { - getShell().getDisplay().asyncExec(() -> { - PlatformUI.getWorkbench().restart(); - }); - } - } - - return result; - } - private Composite createSectionComposite(Composite parent, GridDataFactory gdf) { Composite composite = new Composite(parent, SWT.NONE); composite.setLayout(new GridLayout(1, true)); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java index 2e0f33af..639afd4c 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java @@ -30,7 +30,6 @@ public void initializeDefaultPreferences() { pref.setDefault(Constants.ENABLE_STRICT_SSL, true); pref.setDefault(Constants.PROXY_KERBEROS_SP, ""); pref.setDefault(Constants.GITHUB_ENTERPRISE, ""); - pref.setDefault(Constants.WORKSPACE_CONTEXT_ENABLED, false); pref.setDefault(Constants.AGENT_MAX_REQUESTS, 25); pref.setDefault(Constants.ENABLE_SKILLS, true); pref.setDefault(Constants.CUSTOM_INSTRUCTIONS_WORKSPACE_ENABLED, false); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java index d8053973..7c17d6d6 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java @@ -49,15 +49,11 @@ public class Messages extends NLS { public static String preferences_page_byok_disabled_tip; public static String preferences_page_completions_codeMiningNote; public static String preferences_page_completions_enableNes; - public static String preferences_page_restart_required; public static String preferences_page_enable_strict_ssl; public static String preferences_page_whats_new_settings; public static String preferences_page_enable_whats_new; public static String preferences_page_enable_whats_new_tooltip; public static String preferences_page_github_enterprise; - public static String preferences_page_watched_files; - public static String preferences_page_watched_files_note_content; - public static String preferences_page_restart_question; public static String preferences_page_mcp; public static String preferences_page_proxy_config_link; public static String preferences_page_proxy_settings; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties index bd10669e..34b638c7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties @@ -91,13 +91,9 @@ preferences_page_custom_instructions_chat_load_scope_label=Load custom instructi preferences_page_custom_instructions_chat_load_scope_all=all projects in workspace preferences_page_custom_instructions_chat_load_scope_referenced=projects inferred from chat-attached files preferences_page_custom_instructions_chat_load_scope_combo_tooltip=Decide which of the custom instructions will be used in the Copilot chat. -preferences_page_watched_files= Enable workspace context (experimental) preferences_page_custom_instructions_git_commit= Git Commit Instructions preferences_page_custom_instructions_git_commit_desc=Set custom instructions for Copilot Chat when generating commit messages. preferences_page_custom_instructions_git_commit_note= Access this feature in the Git Staging view by clicking the Copilot icon. You can find this view in the Git perspective or add it via the 'Window' > 'Show View' menu. -preferences_page_watched_files_note_content= Allow the use of @workspace in Ask Mode. Enabling this feature may affect startup performance. -preferences_page_restart_question=You need to restart Eclipse to apply the changes. Would you like to restart now? -preferences_page_restart_required= Restart Required # CustomModesPreferencePage customModes_page_description=Configure custom agents stored as .agent.md files in .github/agents directory. customModes_table_column_modeName=Agent Name