Skip to content

Commit 2ad0568

Browse files
authored
feat - Support edit/create local files outside of workspace (#248)
1 parent e5e3208 commit 2ad0568

12 files changed

Lines changed: 1103 additions & 299 deletions

File tree

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Support Editing and Creating Local Files Outside the Workspace
2+
3+
## Overview
4+
Verify that Copilot Agent mode can edit and create local filesystem files that are outside the Eclipse workspace, and
5+
that those changes are surfaced through the file change summary bar with the same review actions users expect for
6+
workspace files.
7+
8+
This covers the user-visible flow for the `insert_edit_into_file` and `create_file` tools when the target is an
9+
absolute local path rather than an Eclipse `IFile`.
10+
11+
Entry points:
12+
- Window -> Show View -> Other... -> Copilot -> Copilot Chat -> Agent mode
13+
14+
Not exercised:
15+
- Direct unit-level invocation of the file tools.
16+
- Workspace-file edit coverage.
17+
- Low-level compare editor APIs; this plan verifies the Compare UI through the summary bar.
18+
19+
---
20+
21+
## Prerequisites
22+
23+
- Eclipse IDE with the GitHub Copilot for Eclipse plugin installed and activated.
24+
- The user is signed in to GitHub Copilot and Agent mode is available in the Copilot Chat view.
25+
- A writable local directory outside the Eclipse workspace is available, for example:
26+
- Windows: `%TEMP%\\copilot-eclipse-local-file-tools`
27+
- macOS/Linux: `/tmp/copilot-eclipse-local-file-tools`
28+
- The local directory contains an existing text file named `existing-local-file.txt` with this content:
29+
`before local edit`
30+
- The local directory does not contain `created-local-file.txt` before the create-file test starts.
31+
32+
---
33+
34+
## 1. Edit an existing local file outside the workspace
35+
36+
### TC-001: Agent edits a local file and exposes the change in the summary bar
37+
38+
**Type:** `Happy Path`
39+
**Priority:** `P0`
40+
41+
#### Preconditions
42+
- The Eclipse workbench is open.
43+
- Copilot Chat is open in a fresh or cleared conversation.
44+
- `existing-local-file.txt` exists outside the workspace and contains `before local edit`.
45+
46+
#### Steps
47+
1. Open **Copilot Chat** from `Window -> Show View -> Other... -> Copilot -> Copilot Chat`.
48+
2. Switch the chat mode selector to **Agent**.
49+
3. Send a prompt that asks Agent mode to edit the external local file by absolute path, for example:
50+
`Edit <absolute path to existing-local-file.txt> so its entire content is exactly "after local edit".`
51+
4. If Copilot asks for tool confirmation, approve the file edit operation.
52+
5. Wait for the Agent turn to complete.
53+
6. Verify the file change summary bar appears in the Chat view.
54+
7. Verify the summary bar includes `existing-local-file.txt` and displays a local filesystem path for that file.
55+
8. Click **View Diff** for `existing-local-file.txt`.
56+
9. Verify the Compare editor opens and shows the original content `before local edit` against the modified content
57+
`after local edit`.
58+
10. Close the Compare editor.
59+
60+
#### Expected Result
61+
- Copilot completes the edit without reporting that the file is outside the workspace or cannot be edited.
62+
- The local file on disk contains `after local edit`.
63+
- The summary bar lists `existing-local-file.txt` even though it is not an Eclipse workspace file.
64+
- The Compare editor opens from **View Diff** and shows the correct before/after content.
65+
- No error dialog is shown. The Eclipse error log has no uncaught exception from `insert_edit_into_file`, local file
66+
path handling, or compare editor creation.
67+
68+
#### Key Screenshots
69+
- [ ] **Agent edit prompt** -- Copilot Chat in Agent mode with the absolute local file path visible.
70+
- [ ] **Summary bar after local edit** -- The changed local file appears in the file change summary bar.
71+
- [ ] **Local file Compare editor** -- The Compare editor shows `before local edit` vs. `after local edit`.
72+
73+
#### Notes on failure modes
74+
- The edit succeeds on disk but the file is missing from the summary bar -- the local `Path` change may not be tracked
75+
by the summary bar model.
76+
- **View Diff** does nothing or throws an error -- local files may not be routed through the local Compare input path.
77+
- The diff baseline shows the modified content on both sides -- the original content may not have been cached before
78+
applying the edit.
79+
80+
### TC-002: Keep clears the local file change and later edits use a new baseline
81+
82+
**Type:** `Happy Path`
83+
**Priority:** `P0`
84+
85+
#### Preconditions
86+
- The Eclipse workbench is open.
87+
- Copilot Chat is open in a fresh or cleared conversation.
88+
- `existing-local-file.txt` exists outside the workspace and contains `before local edit`.
89+
- Agent mode has edited `existing-local-file.txt` so it contains `after local edit`, and the file is listed in the
90+
summary bar.
91+
92+
#### Steps
93+
1. Click **Keep** for `existing-local-file.txt` in the file change summary bar.
94+
2. Verify the file is removed from the summary bar.
95+
3. Send another Agent prompt to edit the same absolute file path so its entire content is exactly `second local edit`.
96+
4. Approve the edit if prompted and wait for the turn to complete.
97+
5. Click **View Diff** for `existing-local-file.txt`.
98+
6. Verify the Compare editor shows `after local edit` as the original content and `second local edit` as the modified
99+
content.
100+
101+
#### Expected Result
102+
- **Keep** accepts the current local file content and clears the tracked change.
103+
- The next edit of the same local file starts a new diff baseline from the kept content.
104+
- The file remains accessible through the summary bar and Compare editor after the second edit.
105+
106+
#### Key Screenshots
107+
- [ ] **After Keep** -- The summary bar no longer lists the local file.
108+
- [ ] **Second local diff** -- The Compare editor shows the kept content as the new baseline.
109+
110+
### TC-003: Undo restores the original local file content
111+
112+
**Type:** `Happy Path`
113+
**Priority:** `P0`
114+
115+
#### Preconditions
116+
- The Eclipse workbench is open.
117+
- Copilot Chat is open in a fresh or cleared conversation.
118+
- `existing-local-file.txt` exists outside the workspace and contains `before local edit`.
119+
- Agent mode has edited `existing-local-file.txt` so it contains `after local edit`, and the file is listed in the
120+
summary bar.
121+
122+
#### Steps
123+
1. Click **Undo** for `existing-local-file.txt` in the file change summary bar.
124+
2. Verify the file is removed from the summary bar.
125+
3. Open `existing-local-file.txt` from the local filesystem and inspect its content.
126+
127+
#### Expected Result
128+
- **Undo** restores the file to the original content captured before the tracked edit.
129+
- The file is removed from the summary bar after undo completes.
130+
- No error dialog is shown and the Eclipse error log has no local file undo exception.
131+
132+
#### Key Screenshots
133+
- [ ] **Before Undo** -- The summary bar lists the edited local file.
134+
- [ ] **After Undo** -- The summary bar no longer lists the local file and the file content is restored.
135+
136+
---
137+
138+
## 2. Create a new local file outside the workspace
139+
140+
### TC-004: Agent creates a local file and opens it from the summary bar
141+
142+
**Type:** `Happy Path`
143+
**Priority:** `P0`
144+
145+
#### Preconditions
146+
- `created-local-file.txt` does not exist in the local test directory.
147+
- Copilot Chat is open in Agent mode.
148+
149+
#### Steps
150+
1. Send a prompt that asks Agent mode to create the external local file by absolute path, for example:
151+
`Create <absolute path to created-local-file.txt> with the exact content "created local content".`
152+
2. If Copilot asks for tool confirmation, approve the file create operation.
153+
3. Wait for the Agent turn to complete.
154+
4. Verify `created-local-file.txt` exists on disk and contains `created local content`.
155+
5. Verify the file change summary bar lists `created-local-file.txt`.
156+
6. Click **View Diff** for `created-local-file.txt`.
157+
7. Verify Eclipse opens `created-local-file.txt` in an editor and shows `created local content`.
158+
159+
#### Expected Result
160+
- Copilot creates the local file without requiring it to be inside an Eclipse workspace project.
161+
- The created file is listed in the summary bar.
162+
- The created local file can be opened from the summary bar.
163+
- No error dialog is shown and the Eclipse error log has no local file create or editor-open exception.
164+
165+
#### Key Screenshots
166+
- [ ] **Agent create prompt** -- Copilot Chat in Agent mode with the absolute create path visible.
167+
- [ ] **Summary bar after local create** -- The created local file appears in the file change summary bar.
168+
- [ ] **Created local file editor** -- The external local file opens in an editor with the created content.
169+
170+
### TC-005: Undo removes a created local file
171+
172+
**Type:** `Happy Path`
173+
**Priority:** `P0`
174+
175+
#### Preconditions
176+
- The Eclipse workbench is open.
177+
- Copilot Chat is open in Agent mode.
178+
- `created-local-file.txt` does not exist in the local test directory.
179+
- Agent mode has created `created-local-file.txt` with content `created local content`, and the file is listed in the
180+
summary bar.
181+
182+
#### Steps
183+
1. Click **Undo** for `created-local-file.txt` in the file change summary bar.
184+
2. Verify the file is removed from the summary bar.
185+
3. Verify `created-local-file.txt` no longer exists on disk.
186+
187+
#### Expected Result
188+
- **Undo** for a created local file deletes the file, matching the create-file semantics.
189+
- The summary bar no longer lists the created file after undo completes.
190+
- No error dialog is shown and the Eclipse error log has no local file deletion exception.
191+
192+
#### Key Screenshots
193+
- [ ] **Before created-file Undo** -- The summary bar lists `created-local-file.txt`.
194+
- [ ] **After created-file Undo** -- The summary bar is clear and the file is absent from disk.

com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/WorkingSetBarTest.java

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
import com.microsoft.copilot.eclipse.ui.CopilotUi;
4040
import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager;
41+
import com.microsoft.copilot.eclipse.ui.chat.tools.ChangedFile;
4142
import com.microsoft.copilot.eclipse.ui.chat.tools.FileToolService;
4243
import com.microsoft.copilot.eclipse.ui.chat.tools.FileToolService.FileChangeProperty;
4344
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
@@ -103,7 +104,7 @@ private void setupMocks() {
103104
void testNoScrollForFewFiles() {
104105
SwtUtils.invokeOnDisplayThread(() -> {
105106
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
106-
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
107+
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3);
107108

108109
workingSetBar.buildSummaryBarFor(filesMap);
109110

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

127128
workingSetBar.buildSummaryBarFor(filesMap);
128129

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

146147
workingSetBar.buildSummaryBarFor(filesMap);
147148

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

169170
workingSetBar.buildSummaryBarFor(filesMap);
170171

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

195196
workingSetBar.buildSummaryBarFor(filesMap);
196197

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

220221
workingSetBar.buildSummaryBarFor(filesMap);
221222

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

247248
workingSetBar.buildSummaryBarFor(filesMap);
248249

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

268269
// First build with few files (no scroll)
269-
Map<IFile, FileChangeProperty> fewFiles = createMockFilesMap(3, false);
270+
Map<ChangedFile, FileChangeProperty> fewFiles = createMockFilesMap(3);
270271
workingSetBar.buildSummaryBarFor(fewFiles);
271272

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

277278
// Rebuild with many files (should have scroll)
278-
Map<IFile, FileChangeProperty> manyFiles = createMockFilesMap(10, false);
279+
Map<ChangedFile, FileChangeProperty> manyFiles = createMockFilesMap(10);
279280
workingSetBar.buildSummaryBarFor(manyFiles);
280281

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

299300
workingSetBar.buildSummaryBarFor(filesMap);
300301

@@ -322,7 +323,7 @@ void testExpandIconImageWhenExpanded() {
322323
void testExpandIconImageWhenCollapsed() {
323324
SwtUtils.invokeOnDisplayThread(() -> {
324325
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
325-
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
326+
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3);
326327

327328
workingSetBar.buildSummaryBarFor(filesMap);
328329

@@ -354,7 +355,7 @@ void testExpandIconImageWhenCollapsed() {
354355
void testTooltipTextWhenExpanded() {
355356
SwtUtils.invokeOnDisplayThread(() -> {
356357
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
357-
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(3, false);
358+
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(3);
358359

359360
workingSetBar.buildSummaryBarFor(filesMap);
360361

@@ -395,7 +396,7 @@ void testTooltipTextWhenExpanded() {
395396
void testTooltipTextWhenCollapsed() {
396397
SwtUtils.invokeOnDisplayThread(() -> {
397398
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
398-
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(5, false);
399+
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(5);
399400

400401
workingSetBar.buildSummaryBarFor(filesMap);
401402

@@ -436,7 +437,7 @@ void testTooltipTextWhenCollapsed() {
436437
void testTooltipAndImageToggleBehavior() {
437438
SwtUtils.invokeOnDisplayThread(() -> {
438439
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
439-
Map<IFile, FileChangeProperty> filesMap = createMockFilesMap(4, false);
440+
Map<ChangedFile, FileChangeProperty> filesMap = createMockFilesMap(4);
440441

441442
workingSetBar.buildSummaryBarFor(filesMap);
442443

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

478479
// Test with 1 file
479-
Map<IFile, FileChangeProperty> oneFile = createMockFilesMap(1, false);
480+
Map<ChangedFile, FileChangeProperty> oneFile = createMockFilesMap(1);
480481
workingSetBar.buildSummaryBarFor(oneFile);
481482

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

490491
// Test with 10 files
491-
Map<IFile, FileChangeProperty> tenFiles = createMockFilesMap(10, false);
492+
Map<ChangedFile, FileChangeProperty> tenFiles = createMockFilesMap(10);
492493
workingSetBar.buildSummaryBarFor(tenFiles);
493494

494495
titleBar = getFieldValue(workingSetBar, "titleBar");
@@ -508,7 +509,7 @@ void testTooltipContainsCorrectFileCount() {
508509
void testEmptyFilesMapDoesNotCreateChangedFiles() {
509510
SwtUtils.invokeOnDisplayThread(() -> {
510511
workingSetBar = new WorkingSetBar(parent, SWT.NONE);
511-
Map<IFile, FileChangeProperty> emptyMap = new LinkedHashMap<>();
512+
Map<ChangedFile, FileChangeProperty> emptyMap = new LinkedHashMap<>();
512513

513514
workingSetBar.buildSummaryBarFor(emptyMap);
514515

@@ -524,11 +525,11 @@ void testEmptyFilesMapDoesNotCreateChangedFiles() {
524525
/**
525526
* Creates a map of mock files with the specified count.
526527
*/
527-
private Map<IFile, FileChangeProperty> createMockFilesMap(int count, boolean isHandled) {
528-
Map<IFile, FileChangeProperty> filesMap = new LinkedHashMap<>();
528+
private Map<ChangedFile, FileChangeProperty> createMockFilesMap(int count) {
529+
Map<ChangedFile, FileChangeProperty> filesMap = new LinkedHashMap<>();
529530
for (int i = 0; i < count; i++) {
530531
IFile mockFile = createMockFile("TestFile" + i + ".java");
531-
filesMap.put(mockFile, new FileChangeProperty(FileChangeType.Created));
532+
filesMap.put(ChangedFile.workspace(mockFile), new FileChangeProperty(FileChangeType.Created));
532533
}
533534
return filesMap;
534535
}

0 commit comments

Comments
 (0)