From 46e49a1237aa613fd6484cb9e1344d53dd6cae61 Mon Sep 17 00:00:00 2001 From: Nanook Date: Fri, 5 Jun 2026 20:59:05 +0000 Subject: [PATCH] fix: clear closed editor file context --- .../services/ReferencedFileServiceTest.java | 126 ++++++++++++++++++ .../chat/services/ReferencedFileService.java | 40 +++++- 2 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileServiceTest.java diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileServiceTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileServiceTest.java new file mode 100644 index 00000000..1a2a0d3a --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileServiceTest.java @@ -0,0 +1,126 @@ +// 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 java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.eclipse.core.resources.IFile; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IPartService; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPartReference; +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 IEditorReference remainingEditorReference; + + @Mock + private IWorkbenchPartReference closedPartReference; + + @Mock + private IEditorPart closedEditor; + + @Mock + private IFile currentFile; + + @Test + void partClosed_WhenClosedEditorIsCurrentFileAndEditorReferencesRemain_ShouldClearCurrentFile() + throws Exception { + try (MockedStatic platformUi = mockStatic(PlatformUI.class); + MockedStatic uiUtils = mockStatic(UiUtils.class)) { + platformUi.when(PlatformUI::getWorkbench).thenReturn(workbench); + when(workbench.getWorkbenchWindows()).thenReturn(new IWorkbenchWindow[] { window }); + when(window.getPartService()).thenReturn(partService); + + ReferencedFileService service = new ReferencedFileService(); + try { + IPartListener2 listener = getRegisteredPartListener(); + when(closedPartReference.getPart(false)).thenReturn(closedEditor); + uiUtils.when(() -> UiUtils.getFileFromEditorPart(closedEditor)).thenReturn(currentFile); + uiUtils.when(UiUtils::getActivePage).thenReturn(activePage); + when(activePage.getEditorReferences()).thenReturn(new IEditorReference[] { remainingEditorReference }); + + setCurrentFile(service, currentFile); + assertSame(currentFile, service.getCurrentFile()); + + listener.partClosed(closedPartReference); + + assertNull(service.getCurrentFile()); + } finally { + service.dispose(); + } + } + } + + private IPartListener2 getRegisteredPartListener() { + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(IPartListener2.class); + verify(partService).addPartListener(listenerCaptor.capture()); + return listenerCaptor.getValue(); + } + + private static void setCurrentFile(ReferencedFileService service, IFile file) throws Exception { + Object currentFileObservable = getField(service, "currentFileObservable"); + runOnDisplayThread(() -> invokeSetValue(currentFileObservable, file)); + } + + private static Object getField(Object target, String fieldName) throws NoSuchFieldException, + IllegalAccessException { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } + + private static void runOnDisplayThread(Runnable runnable) { + if (Display.getCurrent() != null) { + runnable.run(); + return; + } + Display.getDefault().syncExec(runnable); + } + + private static void invokeSetValue(Object observable, Object value) { + try { + Method setValue = observable.getClass().getMethod("setValue", Object.class); + setValue.invoke(observable, value); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException("Failed to set observable value", e); + } + } +} diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileService.java index e447b648..14b497b7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ReferencedFileService.java @@ -105,12 +105,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); } @@ -479,6 +477,34 @@ private void updateCurrentReferencedFile(IWorkbenchPartReference partRef) { } } + private boolean isCurrentReferencedFile(IWorkbenchPartReference partRef) { + IWorkbenchPart part = partRef.getPart(false); + if (!(part instanceof IEditorPart editorPart)) { + return false; + } + + IFile closedFile = UiUtils.getFileFromEditorPart(editorPart); + if (closedFile == null) { + return false; + } + + AtomicReference currentFile = new AtomicReference<>(); + ensureRealm(() -> currentFile.set(currentFileObservable.getValue())); + return closedFile.equals(currentFile.get()); + } + + private boolean hasNoOpenEditors() { + IWorkbenchPage page = UiUtils.getActivePage(); + return page == null || page.getEditorReferences().length == 0; + } + + private void clearCurrentReferencedFile() { + ensureRealm(() -> { + currentFileObservable.setValue(null); + currentSelectionObservable.setValue(null); + }); + } + private void updateCurrentReferencedFile(IEditorPart editorPart) { if (editorPart == null) { updateObservable(currentFileObservable, null); @@ -529,4 +555,4 @@ private void unregisterPartListener() { } } -} \ No newline at end of file +}