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,194 @@
# Support Editing and Creating Local Files Outside the Workspace

## Overview
Verify that Copilot Agent mode can edit and create local filesystem files that are outside the Eclipse workspace, and
that those changes are surfaced through the file change summary bar with the same review actions users expect for
workspace files.

This covers the user-visible flow for the `insert_edit_into_file` and `create_file` tools when the target is an
absolute local path rather than an Eclipse `IFile`.

Entry points:
- Window -> Show View -> Other... -> Copilot -> Copilot Chat -> Agent mode

Not exercised:
- Direct unit-level invocation of the file tools.
- Workspace-file edit coverage.
- Low-level compare editor APIs; this plan verifies the Compare UI through the summary bar.

---

## Prerequisites

- Eclipse IDE with the GitHub Copilot for Eclipse plugin installed and activated.
- The user is signed in to GitHub Copilot and Agent mode is available in the Copilot Chat view.
- A writable local directory outside the Eclipse workspace is available, for example:
- Windows: `%TEMP%\\copilot-eclipse-local-file-tools`
- macOS/Linux: `/tmp/copilot-eclipse-local-file-tools`
- The local directory contains an existing text file named `existing-local-file.txt` with this content:
`before local edit`
- The local directory does not contain `created-local-file.txt` before the create-file test starts.

---

## 1. Edit an existing local file outside the workspace

### TC-001: Agent edits a local file and exposes the change in the summary bar

**Type:** `Happy Path`
**Priority:** `P0`

#### Preconditions
- The Eclipse workbench is open.
- Copilot Chat is open in a fresh or cleared conversation.
- `existing-local-file.txt` exists outside the workspace and contains `before local edit`.

#### Steps
1. Open **Copilot Chat** from `Window -> Show View -> Other... -> Copilot -> Copilot Chat`.
2. Switch the chat mode selector to **Agent**.
3. Send a prompt that asks Agent mode to edit the external local file by absolute path, for example:
`Edit <absolute path to existing-local-file.txt> so its entire content is exactly "after local edit".`
4. If Copilot asks for tool confirmation, approve the file edit operation.
5. Wait for the Agent turn to complete.
6. Verify the file change summary bar appears in the Chat view.
7. Verify the summary bar includes `existing-local-file.txt` and displays a local filesystem path for that file.
8. Click **View Diff** for `existing-local-file.txt`.
9. Verify the Compare editor opens and shows the original content `before local edit` against the modified content
`after local edit`.
10. Close the Compare editor.

#### Expected Result
- Copilot completes the edit without reporting that the file is outside the workspace or cannot be edited.
- The local file on disk contains `after local edit`.
- The summary bar lists `existing-local-file.txt` even though it is not an Eclipse workspace file.
- The Compare editor opens from **View Diff** and shows the correct before/after content.
- No error dialog is shown. The Eclipse error log has no uncaught exception from `insert_edit_into_file`, local file
path handling, or compare editor creation.

#### Key Screenshots
- [ ] **Agent edit prompt** -- Copilot Chat in Agent mode with the absolute local file path visible.
- [ ] **Summary bar after local edit** -- The changed local file appears in the file change summary bar.
- [ ] **Local file Compare editor** -- The Compare editor shows `before local edit` vs. `after local edit`.

#### Notes on failure modes
- The edit succeeds on disk but the file is missing from the summary bar -- the local `Path` change may not be tracked
by the summary bar model.
- **View Diff** does nothing or throws an error -- local files may not be routed through the local Compare input path.
- The diff baseline shows the modified content on both sides -- the original content may not have been cached before
applying the edit.

### TC-002: Keep clears the local file change and later edits use a new baseline

**Type:** `Happy Path`
**Priority:** `P0`

#### Preconditions
- The Eclipse workbench is open.
- Copilot Chat is open in a fresh or cleared conversation.
- `existing-local-file.txt` exists outside the workspace and contains `before local edit`.
- Agent mode has edited `existing-local-file.txt` so it contains `after local edit`, and the file is listed in the
summary bar.

#### Steps
1. Click **Keep** for `existing-local-file.txt` in the file change summary bar.
2. Verify the file is removed from the summary bar.
3. Send another Agent prompt to edit the same absolute file path so its entire content is exactly `second local edit`.
4. Approve the edit if prompted and wait for the turn to complete.
5. Click **View Diff** for `existing-local-file.txt`.
6. Verify the Compare editor shows `after local edit` as the original content and `second local edit` as the modified
content.

#### Expected Result
- **Keep** accepts the current local file content and clears the tracked change.
- The next edit of the same local file starts a new diff baseline from the kept content.
- The file remains accessible through the summary bar and Compare editor after the second edit.

#### Key Screenshots
- [ ] **After Keep** -- The summary bar no longer lists the local file.
- [ ] **Second local diff** -- The Compare editor shows the kept content as the new baseline.

### TC-003: Undo restores the original local file content

**Type:** `Happy Path`
**Priority:** `P0`

#### Preconditions
- The Eclipse workbench is open.
- Copilot Chat is open in a fresh or cleared conversation.
- `existing-local-file.txt` exists outside the workspace and contains `before local edit`.
- Agent mode has edited `existing-local-file.txt` so it contains `after local edit`, and the file is listed in the
summary bar.

#### Steps
1. Click **Undo** for `existing-local-file.txt` in the file change summary bar.
2. Verify the file is removed from the summary bar.
3. Open `existing-local-file.txt` from the local filesystem and inspect its content.

#### Expected Result
- **Undo** restores the file to the original content captured before the tracked edit.
- The file is removed from the summary bar after undo completes.
- No error dialog is shown and the Eclipse error log has no local file undo exception.

#### Key Screenshots
- [ ] **Before Undo** -- The summary bar lists the edited local file.
- [ ] **After Undo** -- The summary bar no longer lists the local file and the file content is restored.

---

## 2. Create a new local file outside the workspace

### TC-004: Agent creates a local file and shows an empty-baseline diff

**Type:** `Happy Path`
**Priority:** `P0`

#### Preconditions
- `created-local-file.txt` does not exist in the local test directory.
- Copilot Chat is open in Agent mode.

#### Steps
1. Send a prompt that asks Agent mode to create the external local file by absolute path, for example:
`Create <absolute path to created-local-file.txt> with the exact content "created local content".`
2. If Copilot asks for tool confirmation, approve the file create operation.
3. Wait for the Agent turn to complete.
4. Verify `created-local-file.txt` exists on disk and contains `created local content`.
5. Verify the file change summary bar lists `created-local-file.txt`.
6. Click **View Diff** for `created-local-file.txt`.
7. Verify the Compare editor shows an empty original side and `created local content` on the modified side.

#### Expected Result
- Copilot creates the local file without requiring it to be inside an Eclipse workspace project.
- The created file is listed in the summary bar.
- The diff baseline for the created file is empty.
- No error dialog is shown and the Eclipse error log has no local file create or Compare UI exception.

#### Key Screenshots
- [ ] **Agent create prompt** -- Copilot Chat in Agent mode with the absolute create path visible.
- [ ] **Summary bar after local create** -- The created local file appears in the file change summary bar.
- [ ] **Created file diff** -- The Compare editor shows empty original content vs. the created content.

### TC-005: Undo removes a created local file

**Type:** `Happy Path`
**Priority:** `P0`

#### Preconditions
- The Eclipse workbench is open.
- Copilot Chat is open in Agent mode.
- `created-local-file.txt` does not exist in the local test directory.
- Agent mode has created `created-local-file.txt` with content `created local content`, and the file is listed in the
summary bar.

#### Steps
1. Click **Undo** for `created-local-file.txt` in the file change summary bar.
2. Verify the file is removed from the summary bar.
3. Verify `created-local-file.txt` no longer exists on disk.

#### Expected Result
- **Undo** for a created local file deletes the file, matching the create-file semantics.
- The summary bar no longer lists the created file after undo completes.
- No error dialog is shown and the Eclipse error log has no local file deletion exception.

#### Key Screenshots
- [ ] **Before created-file Undo** -- The summary bar lists `created-local-file.txt`.
- [ ] **After created-file Undo** -- The summary bar is clear and the file is absent from disk.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

import com.microsoft.copilot.eclipse.ui.CopilotUi;
import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager;
import com.microsoft.copilot.eclipse.ui.chat.tools.ChangedFile;
import com.microsoft.copilot.eclipse.ui.chat.tools.FileToolService;
import com.microsoft.copilot.eclipse.ui.chat.tools.FileToolService.FileChangeProperty;
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
Expand Down Expand Up @@ -103,7 +104,7 @@ private void setupMocks() {
void testNoScrollForFewFiles() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -122,7 +123,7 @@ void testNoScrollForFewFiles() {
void testNoScrollForExactlyMaxFiles() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(5, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(5, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -141,7 +142,7 @@ void testNoScrollForExactlyMaxFiles() {
void testScrollCreatedForManyFiles() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(10, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(10, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -164,7 +165,7 @@ void testScrollCreatedForManyFiles() {
void testScrollHeightHintForManyFiles() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(8, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(8, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -190,7 +191,7 @@ void testAllFileRowsRenderedWithScroll() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
int fileCount = 7;
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(fileCount, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(fileCount, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -215,7 +216,7 @@ void testAllFileRowsRenderedWithScroll() {
void testContentAreaSetInScrolledComposite() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(8, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(8, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -242,7 +243,7 @@ void testContentAreaSetInScrolledComposite() {
void testMinHeightSetForScrolledComposite() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(10, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(10, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand All @@ -266,7 +267,7 @@ void testRebuildSummaryBarChangesScrollBehavior() {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);

// First build with few files (no scroll)
Map<IFile, FileChangeProperty> fewFiles = createMockFilesMap(3, false);
Map<ChangedFile, FileChangeProperty> fewFiles = createMockFilesMap(3, false);
workingSetBar.buildSummaryBarFor(fewFiles);

Object changedFiles1 = getFieldValue(workingSetBar, "changedFiles");
Expand All @@ -275,7 +276,7 @@ void testRebuildSummaryBarChangesScrollBehavior() {
assertNull(scroll1, "No scroll should exist for 3 files");

// Rebuild with many files (should have scroll)
Map<IFile, FileChangeProperty> manyFiles = createMockFilesMap(10, false);
Map<ChangedFile, FileChangeProperty> manyFiles = createMockFilesMap(10, false);
workingSetBar.buildSummaryBarFor(manyFiles);

Object changedFiles2 = getFieldValue(workingSetBar, "changedFiles");
Expand All @@ -294,7 +295,7 @@ void testRebuildSummaryBarChangesScrollBehavior() {
void testExpandIconImageWhenExpanded() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand Down Expand Up @@ -322,7 +323,7 @@ void testExpandIconImageWhenExpanded() {
void testExpandIconImageWhenCollapsed() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand Down Expand Up @@ -354,7 +355,7 @@ void testExpandIconImageWhenCollapsed() {
void testTooltipTextWhenExpanded() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand Down Expand Up @@ -395,7 +396,7 @@ void testTooltipTextWhenExpanded() {
void testTooltipTextWhenCollapsed() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(5, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(5, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand Down Expand Up @@ -436,7 +437,7 @@ void testTooltipTextWhenCollapsed() {
void testTooltipAndImageToggleBehavior() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(4, false);
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(4, false);

workingSetBar.buildSummaryBarFor(filesMap);

Expand Down Expand Up @@ -476,7 +477,7 @@ void testTooltipContainsCorrectFileCount() {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);

// Test with 1 file
Map<IFile, FileChangeProperty> oneFile = createMockFilesMap(1, false);
Map<ChangedFile, FileChangeProperty> oneFile = createMockFilesMap(1, false);
workingSetBar.buildSummaryBarFor(oneFile);

Object titleBar = getFieldValue(workingSetBar, "titleBar");
Expand All @@ -488,7 +489,7 @@ void testTooltipContainsCorrectFileCount() {
"Tooltip should contain 'file' (singular)");

// Test with 10 files
Map<IFile, FileChangeProperty> tenFiles = createMockFilesMap(10, false);
Map<ChangedFile, FileChangeProperty> tenFiles = createMockFilesMap(10, false);
workingSetBar.buildSummaryBarFor(tenFiles);

titleBar = getFieldValue(workingSetBar, "titleBar");
Expand All @@ -508,7 +509,7 @@ void testTooltipContainsCorrectFileCount() {
void testEmptyFilesMapDoesNotCreateChangedFiles() {
SwtUtils.invokeOnDisplayThread(() -> {
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
Map<IFile, FileChangeProperty> emptyMap = new LinkedHashMap<>();
Map<ChangedFile, FileChangeProperty> emptyMap = new LinkedHashMap<>();

workingSetBar.buildSummaryBarFor(emptyMap);

Expand All @@ -524,11 +525,11 @@ void testEmptyFilesMapDoesNotCreateChangedFiles() {
/**
* Creates a map of mock files with the specified count.
*/
private Map<IFile, FileChangeProperty> createMockFilesMap(int count, boolean isHandled) {
Map<IFile, FileChangeProperty> filesMap = new LinkedHashMap<>();
private Map<ChangedFile, FileChangeProperty> createMockFilesMap(int count, boolean isHandled) {
Map<ChangedFile, FileChangeProperty> filesMap = new LinkedHashMap<>();
for (int i = 0; i < count; i++) {
IFile mockFile = createMockFile("TestFile" + i + ".java");
filesMap.put(mockFile, new FileChangeProperty(FileChangeType.Created));
filesMap.put(ChangedFile.workspace(mockFile), new FileChangeProperty(FileChangeType.Created));
}
Comment on lines 525 to 533
return filesMap;
}
Expand Down
Loading
Loading