From 442efa037dd768358e4983eea5eba096e42b84c2 Mon Sep 17 00:00:00 2001 From: Henrik Hartvig Jensen Date: Tue, 15 Sep 2026 12:50:10 +0200 Subject: [PATCH 1/5] Support for different SCM URI's when adding custom agents --- .../eclipse/core/utils/FileUtilsTests.java | 63 +++++++++++++++++++ .../copilot/eclipse/core/utils/FileUtils.java | 32 ++++++++++ .../eclipse/core/utils/WorkspaceUtils.java | 2 +- 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java index cbbf6d55d..0844c35f4 100644 --- a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java +++ b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java @@ -5,11 +5,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import java.io.File; +import java.net.URI; import java.nio.file.Path; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; +import org.eclipse.lsp4e.LSPEclipseUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedStatic; class FileUtilsTests { @@ -36,4 +45,58 @@ void testGetLocalFilePath_relativePath_returnsNull() { void testGetLocalFilePath_nonFileUri_returnsNull() { assertNull(FileUtils.getLocalFilePath("https://example.com/file.java")); } + + @Test + void testGetResourceAsUri_withLocation_returnsLspEclipseUri() throws Exception { + IResource resource = mock(IResource.class); + IPath location = mock(IPath.class); + when(resource.getLocation()).thenReturn(location); + URI expected = new URI("file:///project/file.txt"); + + try (MockedStatic lspMock = mockStatic(LSPEclipseUtils.class)) { + lspMock.when(() -> LSPEclipseUtils.toUri(resource)).thenReturn(expected); + + assertEquals(expected, FileUtils.getResourceAsUri(resource)); + } + } + + @Test + void testGetResourceAsUri_withLocationDerivedFromAbsolutePath_prefersFileUriOverLocationUri() throws Exception { + IResource resource = mock(IResource.class); + IPath location = mock(IPath.class); + when(location.toFile()).thenReturn(new File("/someproject/somefile.txt")); + when(resource.getLocation()).thenReturn(location); + // getLocationURI() reflects the source-control provider's scheme; it must be ignored since + // getLocation() is non-null and LSPEclipseUtils.toUri() resolves the local filesystem URI first. + when(resource.getLocationURI()).thenReturn(new URI("sourcecontrol:///someproject/somefile.txt")); + URI expected = new URI("file:///someproject/somefile.txt"); + + try (MockedStatic lspMock = mockStatic(LSPEclipseUtils.class)) { + lspMock.when(() -> LSPEclipseUtils.toUri(resource)).thenReturn(expected); + + assertEquals(expected, FileUtils.getResourceAsUri(resource)); + } + } + + @Test + void testGetResourceAsUri_withoutLocationButWithLocationUri_returnsLocationUri() throws Exception { + IResource resource = mock(IResource.class); + when(resource.getLocation()).thenReturn(null); + URI expected = new URI("semanticfs:///project/file.txt"); + when(resource.getLocationURI()).thenReturn(expected); + + assertEquals(expected, FileUtils.getResourceAsUri(resource)); + } + + @Test + void testGetResourceAsUri_withoutLocationAndLocationUri_returnsPlatformResourceUri() throws Exception { + IResource resource = mock(IResource.class); + IPath fullPath = mock(IPath.class); + when(resource.getLocation()).thenReturn(null); + when(resource.getLocationURI()).thenReturn(null); + when(resource.getFullPath()).thenReturn(fullPath); + when(fullPath.toPortableString()).thenReturn("/Project/file.txt"); + + assertEquals(new URI("platform:/resource/Project/file.txt"), FileUtils.getResourceAsUri(resource)); + } } diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java index 06bbb6118..1c5d8cdb3 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java @@ -102,6 +102,38 @@ public static String getResourceUri(IResource resource) { return "platform:/resource" + resource.getFullPath().toString(); } + /** + * Gets a URI for a resource using multiple approaches. + * + * @param resource The resource + * @return A URI or null if no URI could be determined + */ + public static URI getResourceAsUri(IResource resource) { + if (resource == null) { + return null; + } + + // Try standard file location first + if (resource.getLocation() != null) { + URI uri = LSPEclipseUtils.toUri(resource); + if (uri != null) { + return uri; + } + } + + // Try getting direct URI (works for remote resources) + if (resource.getLocationURI() != null) { + return resource.getLocationURI(); + } + String fileUri = "platform:/resource" + resource.getFullPath().toPortableString(); + try { + return new URI(fileUri); + } catch (URISyntaxException e) { + CopilotCore.LOGGER.error("Invalid file URI: " + fileUri, e); + } + return null; + } + /** * Get all the IFile instance in a List of resources. * diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/WorkspaceUtils.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/WorkspaceUtils.java index 28fce9ebf..26a942e61 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/WorkspaceUtils.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/WorkspaceUtils.java @@ -89,7 +89,7 @@ public static List listWorkspaceFolders() { List folders = new ArrayList<>(); for (IProject project : projects) { - URI uri = project.getLocationURI(); + URI uri = FileUtils.getResourceAsUri(project); if (uri != null) { WorkspaceFolder folder = new WorkspaceFolder(); folder.setUri(uri.toASCIIString()); From 4f98cd88f821190f1eec95cde09f03c1b311c980 Mon Sep 17 00:00:00 2001 From: Henrik Hartvig Jensen Date: Tue, 15 Sep 2026 13:53:23 +0200 Subject: [PATCH 2/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java index 1c5d8cdb3..ddb91aa35 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java @@ -127,7 +127,7 @@ public static URI getResourceAsUri(IResource resource) { } String fileUri = "platform:/resource" + resource.getFullPath().toPortableString(); try { - return new URI(fileUri); + return new URI("platform", null, "/resource" + resource.getFullPath().toPortableString(), null); } catch (URISyntaxException e) { CopilotCore.LOGGER.error("Invalid file URI: " + fileUri, e); } From 2ff2a196168b94ca4078474df6bed82dc049acc9 Mon Sep 17 00:00:00 2001 From: Henrik Hartvig Jensen Date: Wed, 16 Sep 2026 16:19:26 +0200 Subject: [PATCH 3/5] Better support for other SCM URI's in code. --- .../copilot/eclipse/ui/jobs/views/JobsView.java | 3 ++- .../microsoft/copilot/eclipse/ui/chat/ActionBar.java | 2 +- .../copilot/eclipse/ui/chat/AddContextButton.java | 5 +++-- .../ui/handlers/ConfigureToolsCommandHandler.java | 11 ++++++++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java index a3352d738..a681ff47b 100644 --- a/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java +++ b/com.microsoft.copilot.eclipse.ui.jobs/src/com/microsoft/copilot/eclipse/ui/jobs/views/JobsView.java @@ -48,6 +48,7 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.GitHubPullRequestItem; import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.SearchPrParams; import com.microsoft.copilot.eclipse.core.lsp.protocol.githubapi.SearchPrResponse; +import com.microsoft.copilot.eclipse.core.utils.FileUtils; import com.microsoft.copilot.eclipse.core.utils.WorkspaceUtils; import com.microsoft.copilot.eclipse.ui.jobs.CopilotJobsImages; import com.microsoft.copilot.eclipse.ui.jobs.events.JobsViewEvents; @@ -324,7 +325,7 @@ protected IStatus run(IProgressMonitor monitor) { */ private void loadPullRequestsForProject(IProject project) { String projectName = project.getName(); - URI projectUri = project.getLocationURI(); + URI projectUri = FileUtils.getResourceAsUri(project); if (projectUri == null) { return; } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java index 43930884d..004890496 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java @@ -948,7 +948,7 @@ private List selectFile() { Set selectedFileUris = new HashSet<>(); for (Object selectedFile : selectedFiles) { if (selectedFile instanceof IFile file) { - URI fileUri = file.getLocationURI(); + URI fileUri = FileUtils.getResourceAsUri(file); if (fileUri != null && selectedFileUris.add(fileUri.toASCIIString())) { result.add(file); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java index 3c023fd07..4ed304de7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AddContextButton.java @@ -27,6 +27,7 @@ import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; +import com.microsoft.copilot.eclipse.core.utils.FileUtils; import com.microsoft.copilot.eclipse.ui.CopilotImages; import com.microsoft.copilot.eclipse.ui.CopilotUi; import com.microsoft.copilot.eclipse.ui.chat.services.ReferencedFileService; @@ -90,12 +91,12 @@ private List selectFiles() { Set selectedFileUris = new HashSet<>(); for (Object selectedFile : selectedFiles) { if (selectedFile instanceof IFile file) { - URI fileUri = file.getLocationURI(); + URI fileUri = FileUtils.getResourceAsUri(file); if (fileUri != null && selectedFileUris.add(fileUri.toASCIIString())) { result.add(file); } } else if (selectedFile instanceof IFolder folder) { - URI folderUri = folder.getLocationURI(); + URI folderUri = FileUtils.getResourceAsUri(folder); if (folderUri != null && selectedFileUris.add(folderUri.toASCIIString())) { result.add(folder); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ConfigureToolsCommandHandler.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ConfigureToolsCommandHandler.java index 3d4508c91..20cdedb62 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ConfigureToolsCommandHandler.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/handlers/ConfigureToolsCommandHandler.java @@ -3,6 +3,7 @@ package com.microsoft.copilot.eclipse.ui.handlers; +import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -22,6 +23,7 @@ import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.core.chat.CustomChatMode; import com.microsoft.copilot.eclipse.core.chat.CustomChatModeManager; +import com.microsoft.copilot.eclipse.core.utils.FileUtils; import com.microsoft.copilot.eclipse.ui.preferences.McpPreferencePage; import com.microsoft.copilot.eclipse.ui.utils.PreferencesUtils; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; @@ -101,7 +103,14 @@ private static String extractModeIdFromFile(IFile file) { if (UiUtils.isAgentFile(file)) { // Get the file's absolute path - Path filePath = Paths.get(file.getLocationURI()); + URI resourceUri = FileUtils.getResourceAsUri(file); + if (resourceUri == null) { + return null; + } + Path filePath = FileUtils.getLocalFilePath(resourceUri.toString()); + if (filePath == null) { + return null; + } // Look up the custom mode by matching the file path try { From 5a792fa2f7dbb37d3eb52fc406018cec0dacb0fb Mon Sep 17 00:00:00 2001 From: Henrik Hartvig Jensen Date: Mon, 12 Oct 2026 10:43:21 +0200 Subject: [PATCH 4/5] Refactor getResourceUri to use getResourceAsUri --- .../eclipse/core/utils/FileUtilsTests.java | 10 +++++++ .../copilot/eclipse/core/utils/FileUtils.java | 26 ++----------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java index 0844c35f4..58b282c29 100644 --- a/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java +++ b/com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/utils/FileUtilsTests.java @@ -22,6 +22,16 @@ class FileUtilsTests { + @Test + void testGetResourceUri_withNonAsciiLocationUri_returnsAsciiString() { + IResource resource = mock(IResource.class); + URI expected = URI.create("semanticfs:///project/caf\u00e9.txt"); + when(resource.getLocation()).thenReturn(null); + when(resource.getLocationURI()).thenReturn(expected); + + assertEquals(expected.toASCIIString(), FileUtils.getResourceUri(resource)); + } + @Test void testGetLocalFilePath_absolutePath_returnsNormalizedPath(@TempDir Path tempDir) { Path expected = tempDir.resolve("external-file.txt").toAbsolutePath().normalize(); diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java index ddb91aa35..16eca2822 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/utils/FileUtils.java @@ -76,30 +76,8 @@ private FileUtils() { * @return A URI string or null if no URI could be determined */ public static String getResourceUri(IResource resource) { - if (resource == null) { - return null; - } - - // Try standard file location first - if (resource.getLocation() != null) { - URI url = LSPEclipseUtils.toUri(resource); - if (url != null) { - return url.toASCIIString(); - } - } - - // Try getting direct URI (works for remote resources) - if (resource.getLocationURI() != null) { - URI url = resource.getLocationURI(); - if (url != null) { - return url.toASCIIString(); - } - } - - // Fall back to platform resource URI - // See: - // https://help.eclipse.org/latest/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/e4/ui/css/swt/helpers/URI.html#createPlatformResourceURI(java.lang.String,boolean) - return "platform:/resource" + resource.getFullPath().toString(); + URI uri = getResourceAsUri(resource); + return uri != null ? uri.toASCIIString() : null; } /** From 03044d6a0cc8eae7dac9b8d60a8ebe3bfe4aafba Mon Sep 17 00:00:00 2001 From: Henrik Hartvig Jensen Date: Mon, 19 Oct 2026 10:26:46 +0200 Subject: [PATCH 5/5] Merging removed function in ActionBar --- .../copilot/eclipse/ui/chat/ActionBar.java | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java index a72d74498..ed5a5d9c9 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java @@ -923,34 +923,6 @@ private void updateSendToJobBtn(boolean enable) { }, sendToJobButton); } - /** - * Popup a file picker dialog to select files. It's guaranteed that the selected files are unique. - */ - @NonNull - private List selectFile() { - Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IContainer container = root.getContainerForLocation(root.getLocation()); - AttachFileSelectionDialog dialog = new AttachFileSelectionDialog(shell, true, container); - dialog.setTitle(Messages.chat_filePicker_title); - dialog.setMessage(Messages.chat_filePicker_message); - List result = new ArrayList<>(); - if (dialog.open() == Window.OK) { - Object[] selectedFiles = dialog.getResult(); - Set selectedFileUris = new HashSet<>(); - for (Object selectedFile : selectedFiles) { - if (selectedFile instanceof IFile file) { - URI fileUri = FileUtils.getResourceAsUri(file); - if (fileUri != null && selectedFileUris.add(fileUri.toASCIIString())) { - result.add(file); - } - } - } - return result; - } - return result; - } - /** * Show the rate-limit static banner above the input area, with a single "Get more info" action link. *