Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,33 @@

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 {

@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();
Expand All @@ -36,4 +55,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<LSPEclipseUtils> 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<LSPEclipseUtils> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,40 @@ private FileUtils() {
* @return A URI string or null if no URI could be determined
*/
public static String getResourceUri(IResource resource) {
URI uri = getResourceAsUri(resource);
return uri != null ? uri.toASCIIString() : null;
}

/**
* 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) {
Comment thread
HHartvig marked this conversation as resolved.
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();
URI uri = LSPEclipseUtils.toUri(resource);
if (uri != null) {
return uri;
}
}

// Try getting direct URI (works for remote resources)
if (resource.getLocationURI() != null) {
URI url = resource.getLocationURI();
if (url != null) {
return url.toASCIIString();
}
return resource.getLocationURI();
}

// 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();
String fileUri = "platform:/resource" + resource.getFullPath().toPortableString();
try {
return new URI("platform", null, "/resource" + resource.getFullPath().toPortableString(), null);
} catch (URISyntaxException e) {
CopilotCore.LOGGER.error("Invalid file URI: " + fileUri, e);
}
return null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public static List<WorkspaceFolder> listWorkspaceFolders() {

List<WorkspaceFolder> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -90,12 +91,12 @@ private List<IResource> selectFiles() {
Set<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Loading