Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand All @@ -32,6 +34,8 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationContextParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CurrentEditorContext;
import com.microsoft.copilot.eclipse.core.lsp.protocol.DidChangeFeatureFlagsParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.FileStat;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ReadFileResult;
import com.microsoft.copilot.eclipse.core.utils.FileUtils;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -83,6 +87,134 @@ void testResolveCurrentEditorSkill() throws InterruptedException, ExecutionExcep
}
}

@Test
void testResolveCurrentEditorSkillWithVisibleEditorUri() throws InterruptedException, ExecutionException {
// Arrange
ConversationContextParams params = new ConversationContextParams("", "",
ConversationCapabilities.CURRENT_EDITOR_SKILL);
String expectedUri = "copilot-visible-editor://current/1";

try (MockedStatic<CopilotCore> copilotCoreMock = Mockito.mockStatic(CopilotCore.class)) {
copilotCoreMock.when(CopilotCore::getPlugin).thenReturn(plugin);
when(plugin.getChatServiceManager()).thenReturn(chatServiceManager);
when(chatServiceManager.getReferencedFileService()).thenReturn(fileService);
when(fileService.getCurrentFile()).thenReturn(null);
when(fileService.getCurrentEditorUri()).thenReturn(expectedUri);

// Act
CompletableFuture<Object[]> future = client.getConversationContext(params);
Object[] result = future.get();

// Assert
assertNotNull(result);
assertEquals(2, result.length);
assertEquals(CurrentEditorContext.class, result[0].getClass());
assertEquals(expectedUri, ((CurrentEditorContext) result[0]).getUri());
assertNull(result[1]);
}
}

@Test
void testResolveCurrentEditorSkillWithoutFileOrVisibleEditorUri() throws InterruptedException, ExecutionException {
// Arrange
ConversationContextParams params = new ConversationContextParams("", "",
ConversationCapabilities.CURRENT_EDITOR_SKILL);

try (MockedStatic<CopilotCore> copilotCoreMock = Mockito.mockStatic(CopilotCore.class)) {
copilotCoreMock.when(CopilotCore::getPlugin).thenReturn(plugin);
when(plugin.getChatServiceManager()).thenReturn(chatServiceManager);
when(chatServiceManager.getReferencedFileService()).thenReturn(fileService);
when(fileService.getCurrentFile()).thenReturn(null);
when(fileService.getCurrentEditorUri()).thenReturn(null);

// Act
CompletableFuture<Object[]> future = client.getConversationContext(params);
Object[] result = future.get();

// Assert
assertNotNull(result);
assertEquals(2, result.length);
assertNull(result[0]);
assertNull(result[1]);
}
}

@Test
void testReadFileFallsBackToCurrentEditorWhenWorkspaceFileMissing()
throws InterruptedException, ExecutionException {
// Arrange
String uri = "copilot-visible-editor://current/1";
ReadFileResult fileResult = new ReadFileResult("workspace read failed", null);
ReadFileResult currentEditorResult = new ReadFileResult("class Example {}", null);

try (MockedStatic<CopilotCore> copilotCoreMock = Mockito.mockStatic(CopilotCore.class);
MockedStatic<FileUtils> fileUtilsMock = Mockito.mockStatic(FileUtils.class)) {
copilotCoreMock.when(CopilotCore::getPlugin).thenReturn(plugin);
when(plugin.getChatServiceManager()).thenReturn(chatServiceManager);
when(chatServiceManager.getReferencedFileService()).thenReturn(fileService);
when(fileService.readCurrentEditor(uri)).thenReturn(currentEditorResult);
fileUtilsMock.when(() -> FileUtils.readFileWithStats(uri)).thenReturn(fileResult);
fileUtilsMock.when(() -> FileUtils.getFileFromUri(uri)).thenReturn(null);

// Act
ReadFileResult result = new CopilotLanguageClient(Runnable::run).readFile(uri).get();

// Assert
assertSame(currentEditorResult, result);
}
}

@Test
void testReadFilePreservesFileUtilsResultWhenCurrentEditorFallbackMissing()
throws InterruptedException, ExecutionException {
// Arrange
String uri = "copilot-visible-editor://current/1";
ReadFileResult fileResult = new ReadFileResult("workspace read failed", null);

try (MockedStatic<CopilotCore> copilotCoreMock = Mockito.mockStatic(CopilotCore.class);
MockedStatic<FileUtils> fileUtilsMock = Mockito.mockStatic(FileUtils.class)) {
copilotCoreMock.when(CopilotCore::getPlugin).thenReturn(plugin);
when(plugin.getChatServiceManager()).thenReturn(chatServiceManager);
when(chatServiceManager.getReferencedFileService()).thenReturn(fileService);
when(fileService.readCurrentEditor(uri)).thenReturn(null);
fileUtilsMock.when(() -> FileUtils.readFileWithStats(uri)).thenReturn(fileResult);
fileUtilsMock.when(() -> FileUtils.getFileFromUri(uri)).thenReturn(null);

// Act
ReadFileResult result = new CopilotLanguageClient(Runnable::run).readFile(uri).get();

// Assert
assertSame(fileResult, result);
}
}

@Test
void testReadFileDoesNotFallbackToCurrentEditorWhenFileFound()
throws InterruptedException, ExecutionException {
// Arrange
String uri = "file:///path/to/file.txt";
FileStat stat = new FileStat();
stat.setSize(18);
IFile file = mock(IFile.class);
ReadFileResult fileResult = new ReadFileResult("class Example {}", stat);

try (MockedStatic<CopilotCore> copilotCoreMock = Mockito.mockStatic(CopilotCore.class);
MockedStatic<FileUtils> fileUtilsMock = Mockito.mockStatic(FileUtils.class)) {
copilotCoreMock.when(CopilotCore::getPlugin).thenReturn(plugin);
when(plugin.getChatServiceManager()).thenReturn(chatServiceManager);
when(chatServiceManager.getReferencedFileService()).thenReturn(fileService);
fileUtilsMock.when(() -> FileUtils.readFileWithStats(uri)).thenReturn(fileResult);
fileUtilsMock.when(() -> FileUtils.getFileFromUri(uri)).thenReturn(file);

// Act
ReadFileResult result = new CopilotLanguageClient(Runnable::run).readFile(uri).get();

// Assert
assertSame(fileResult, result);
verify(fileService, never()).readCurrentEditor(uri);
}
}

@Test
void testOnDidChangeFeatureFlags() {
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.lsp4j.Range;

import com.microsoft.copilot.eclipse.core.lsp.protocol.ReadFileResult;

/**
* Interface for managing referenced files in the Copilot chat.
*/
Expand All @@ -19,6 +21,22 @@ public interface IReferencedFileService {
*/
IFile getCurrentFile();

/**
* Get the URI for the current editor when it is not backed by a workspace file.
*/
@Nullable
default String getCurrentEditorUri() {
return null;
}

/**
* Read the current editor contents for a URI returned by {@link #getCurrentEditorUri()}.
*/
@Nullable
default ReadFileResult readCurrentEditor(String uri) {
return null;
}

/**
* Get the referenced files that is attached by user.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
Expand Down Expand Up @@ -82,13 +85,19 @@ public class CopilotLanguageClient extends LanguageClientImpl {
private WatchedFileManager watchedFileManager;

private IEventBroker eventBroker;
private final Executor readFileExecutor;

private static final String SIGNUP_URL = "https://github.com/github-copilot/signup";

/**
* Constructor for CopilotLanguageClient.
*/
public CopilotLanguageClient() {
this(ForkJoinPool.commonPool());
}

CopilotLanguageClient(Executor readFileExecutor) {
this.readFileExecutor = readFileExecutor;
this.eventBroker = EclipseContextFactory.getServiceContext(FrameworkUtil.getBundle(getClass()).getBundleContext())
.get(IEventBroker.class);
}
Expand Down Expand Up @@ -125,11 +134,16 @@ public CompletableFuture<Object[]> getConversationContext(ConversationContextPar
}

IFile file = fileService.getCurrentFile();
if (file == null) {
break;
if (file != null) {
String uri = FileUtils.getResourceUri(file);
return CompletableFuture.completedFuture(new Object[] { new CurrentEditorContext(uri), null });
}

String uri = fileService.getCurrentEditorUri();
if (StringUtils.isNotBlank(uri)) {
return CompletableFuture.completedFuture(new Object[] { new CurrentEditorContext(uri), null });
}
String uri = FileUtils.getResourceUri(file);
return CompletableFuture.completedFuture(new Object[] { new CurrentEditorContext(uri), null });
break;
default:
break;
}
Expand Down Expand Up @@ -393,7 +407,29 @@ public void onCompressionCompleted(CompressionCompletedParams params) {
*/
@JsonRequest("workspace/readFile")
public CompletableFuture<ReadFileResult> readFile(String uri) {
return CompletableFuture.supplyAsync(() -> FileUtils.readFileWithStats(uri));
IReferencedFileService fileService = getReferencedFileService();
return CompletableFuture.supplyAsync(() -> {
ReadFileResult result = FileUtils.readFileWithStats(uri);
if (!shouldFallbackToCurrentEditor(uri)) {
return result;
}

ReadFileResult currentEditorResult = fileService != null ? fileService.readCurrentEditor(uri) : null;
return currentEditorResult != null ? currentEditorResult : result;
}, readFileExecutor);
}

private static boolean shouldFallbackToCurrentEditor(String uri) {
return FileUtils.getFileFromUri(uri) == null;
}

private static IReferencedFileService getReferencedFileService() {
CopilotCore plugin = CopilotCore.getPlugin();
if (plugin == null || plugin.getChatServiceManager() == null) {
return null;
}

return plugin.getChatServiceManager().getReferencedFileService();
}

/**
Expand Down Expand Up @@ -458,4 +494,4 @@ protected IStatus run(IProgressMonitor monitor) {
return super.showDocument(params);
}
}
}
}
Loading