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
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

package com.microsoft.copilot.eclipse.ui.chat.services;

import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.eclipse.core.resources.IFile;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

import com.microsoft.copilot.eclipse.ui.utils.UiUtils;

@ExtendWith(MockitoExtension.class)
class ReferencedFileServiceTest {

@Mock
private IWorkbench workbench;

@Mock
private IWorkbenchWindow window;

@Mock
private IPartService partService;

@Mock
private IWorkbenchPage activePage;

@Mock
private IWorkbenchPage otherPage;

@Mock
private IEditorReference closedEditorReference;

@Mock
private IEditorReference remainingEditorReference;

@Mock
private IEditorPart closedEditor;

@Mock
private IFileEditorInput closedEditorInput;

@Mock
private IEditorInput nonFileEditorInput;

@Mock
private IFile currentFile;

@Test
void partClosed_WhenClosedEditorIsCurrentFileAndEditorReferencesRemain_ShouldClearCurrentFile()
throws Exception {
try (MockedStatic<PlatformUI> platformUi = mockStatic(PlatformUI.class);
MockedStatic<UiUtils> uiUtils = mockStatic(UiUtils.class)) {
platformUi.when(PlatformUI::getWorkbench).thenReturn(workbench);
when(workbench.getWorkbenchWindows()).thenReturn(new IWorkbenchWindow[] { window });
when(window.getPartService()).thenReturn(partService);

TestReferencedFileService service = new TestReferencedFileService();
try {
IPartListener2 listener = getRegisteredPartListener();
when(closedEditorReference.getEditorInput()).thenReturn(nonFileEditorInput);
when(closedEditorReference.getPart(false)).thenReturn(closedEditor);
uiUtils.when(() -> UiUtils.getFileFromEditorPart(closedEditor)).thenReturn(currentFile);
when(window.getPages()).thenReturn(new IWorkbenchPage[] { activePage });
when(activePage.getEditorReferences()).thenReturn(new IEditorReference[] { remainingEditorReference });

service.setCurrentFile(currentFile);
assertSame(currentFile, service.getCurrentFile());

listener.partClosed(closedEditorReference);

assertNull(service.getCurrentFile());
} finally {
service.dispose();
}
}
}

@Test
void partClosed_WhenEditorPartIsDisposedButReferenceInputIsCurrentFile_ShouldClearCurrentFile()
throws Exception {
try (MockedStatic<PlatformUI> platformUi = mockStatic(PlatformUI.class)) {
platformUi.when(PlatformUI::getWorkbench).thenReturn(workbench);
when(workbench.getWorkbenchWindows()).thenReturn(new IWorkbenchWindow[] { window });
when(window.getPartService()).thenReturn(partService);

TestReferencedFileService service = new TestReferencedFileService();
try {
IPartListener2 listener = getRegisteredPartListener();
when(closedEditorReference.getEditorInput()).thenReturn(closedEditorInput);
when(closedEditorInput.getFile()).thenReturn(currentFile);
when(window.getPages()).thenReturn(new IWorkbenchPage[] { activePage });
when(activePage.getEditorReferences()).thenReturn(new IEditorReference[] { remainingEditorReference });

service.setCurrentFile(currentFile);
assertSame(currentFile, service.getCurrentFile());

listener.partClosed(closedEditorReference);

assertNull(service.getCurrentFile());
} finally {
service.dispose();
}
}
}

@Test
void partClosed_WhenActivePageHasNoEditorsButAnotherPageHasEditors_ShouldKeepCurrentFile()
throws Exception {
try (MockedStatic<PlatformUI> platformUi = mockStatic(PlatformUI.class);
MockedStatic<UiUtils> uiUtils = mockStatic(UiUtils.class)) {
platformUi.when(PlatformUI::getWorkbench).thenReturn(workbench);
when(workbench.getWorkbenchWindows()).thenReturn(new IWorkbenchWindow[] { window });
when(window.getPartService()).thenReturn(partService);

TestReferencedFileService service = new TestReferencedFileService();
try {
IPartListener2 listener = getRegisteredPartListener();
when(closedEditorReference.getEditorInput()).thenReturn(nonFileEditorInput);
when(closedEditorReference.getPart(false)).thenReturn(closedEditor);
uiUtils.when(() -> UiUtils.getFileFromEditorPart(closedEditor)).thenReturn(null);
when(window.getPages()).thenReturn(new IWorkbenchPage[] { activePage, otherPage });
when(activePage.getEditorReferences()).thenReturn(new IEditorReference[0]);
when(otherPage.getEditorReferences()).thenReturn(new IEditorReference[] { remainingEditorReference });

service.setCurrentFile(currentFile);
assertSame(currentFile, service.getCurrentFile());

listener.partClosed(closedEditorReference);

assertSame(currentFile, service.getCurrentFile());
} finally {
service.dispose();
}
}
}

private IPartListener2 getRegisteredPartListener() {
ArgumentCaptor<IPartListener2> listenerCaptor = ArgumentCaptor.forClass(IPartListener2.class);
verify(partService).addPartListener(listenerCaptor.capture());
return listenerCaptor.getValue();
}

private static class TestReferencedFileService extends ReferencedFileService {
void setCurrentFile(IFile file) {
setCurrentFileForTest(file);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.texteditor.ITextEditor;

import com.microsoft.copilot.eclipse.core.Constants;
Expand Down Expand Up @@ -105,12 +111,10 @@ public void partBroughtToTop(IWorkbenchPartReference partRef) {

@Override
public void partClosed(IWorkbenchPartReference partRef) {
IWorkbenchPage page = UiUtils.getActivePage();
if (page == null || page.getEditorReferences().length == 0) {
ensureRealm(() -> {
currentFileObservable.setValue(null);
currentSelectionObservable.setValue(null);
});
boolean closedCurrentFile = isCurrentReferencedFile(partRef);
boolean noOpenEditors = hasNoOpenEditors();
if (closedCurrentFile || noOpenEditors) {
clearCurrentReferencedFile();
}
untrackSelectionInEditor(partRef);
}
Expand Down Expand Up @@ -479,6 +483,98 @@ private void updateCurrentReferencedFile(IWorkbenchPartReference partRef) {
}
}

private boolean isCurrentReferencedFile(IWorkbenchPartReference partRef) {
IFile closedFile = getFileFromPartReference(partRef);
if (closedFile == null) {
return false;
}

AtomicReference<IFile> currentFile = new AtomicReference<>();
ensureRealm(() -> currentFile.set(currentFileObservable.getValue()));
return closedFile.equals(currentFile.get());
}
Comment thread
nanookclaw marked this conversation as resolved.

private IFile getFileFromPartReference(IWorkbenchPartReference partRef) {
if (partRef instanceof IEditorReference editorReference) {
IFile file = getFileFromEditorReference(editorReference);
if (file != null) {
return file;
}
}

IWorkbenchPart part = partRef.getPart(false);
if (part instanceof IEditorPart editorPart) {
return UiUtils.getFileFromEditorPart(editorPart);
}
return null;
}

private IFile getFileFromEditorReference(IEditorReference editorReference) {
try {
return getFileFromEditorInput(editorReference.getEditorInput());
} catch (PartInitException e) {
CopilotCore.LOGGER.error("Failed to get editor input from part reference", e);
return null;
}
}

private IFile getFileFromEditorInput(IEditorInput input) {
if (input instanceof IFileEditorInput fileEditorInput) {
return fileEditorInput.getFile();
}
return ResourceUtil.getFile(input);
}

private boolean hasNoOpenEditors() {
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench == null) {
return true;
}

IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
if (windows == null || windows.length == 0) {
return true;
}

for (IWorkbenchWindow window : windows) {
if (window == null) {
continue;
}

IWorkbenchPage[] pages = window.getPages();
if (pages == null || pages.length == 0) {
IWorkbenchPage activePage = window.getActivePage();
pages = activePage == null ? new IWorkbenchPage[0] : new IWorkbenchPage[] { activePage };
}

for (IWorkbenchPage page : pages) {
if (page == null) {
continue;
}

IEditorReference[] editorReferences = page.getEditorReferences();
if (editorReferences != null && editorReferences.length > 0) {
return false;
}
}
}
return true;
}
Comment thread
nanookclaw marked this conversation as resolved.

private void clearCurrentReferencedFile() {
ensureRealm(() -> {
currentFileObservable.setValue(null);
currentSelectionObservable.setValue(null);
});
}

/**
* Sets the current file for tests.
*/
protected void setCurrentFileForTest(IFile file) {
ensureRealm(() -> currentFileObservable.setValue(file));
}

private void updateCurrentReferencedFile(IEditorPart editorPart) {
if (editorPart == null) {
updateObservable(currentFileObservable, null);
Expand Down Expand Up @@ -529,4 +625,4 @@ private void unregisterPartListener() {
}
}

}
}