Skip to content

Commit 9b0f372

Browse files
CopilotJustinGrote
andauthored
Add Saved status to workspace documents
Agent-Logs-Url: https://github.com/PowerShell/PowerShellEditorServices/sessions/3e87bc0c-ffcc-4ce4-9b08-22d248a3f5db Co-authored-by: JustinGrote <15258962+JustinGrote@users.noreply.github.com>
1 parent c8721ba commit 9b0f372

6 files changed

Lines changed: 99 additions & 23 deletions

File tree

src/PowerShellEditorServices/Extensions/EditorWorkspace.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ public sealed class EditorWorkspaceDocument
1010
{
1111
private readonly EditorWorkspace _workspace;
1212

13-
internal EditorWorkspaceDocument(EditorWorkspace workspace, string path)
13+
internal EditorWorkspaceDocument(EditorWorkspace workspace, string path, bool saved)
1414
{
1515
_workspace = workspace;
1616
Path = path;
17+
Saved = saved;
1718
}
1819

1920
/// <summary>
@@ -22,10 +23,19 @@ internal EditorWorkspaceDocument(EditorWorkspace workspace, string path)
2223
public string Path { get; }
2324

2425
/// <summary>
25-
/// Gets the full path of this document.
26+
/// Gets whether the document has unsaved changes.
2627
/// </summary>
27-
/// <returns>The full document path.</returns>
28-
public override string ToString() => Path;
28+
public bool Saved { get; }
29+
30+
/// <summary>
31+
/// Gets the display name of this document and unsaved status.
32+
/// </summary>
33+
/// <returns>The display name of this document.</returns>
34+
public override string ToString()
35+
{
36+
string fileName = System.IO.Path.GetFileName(Path);
37+
return Saved ? fileName : fileName + " [Unsaved]";
38+
}
2939

3040
/// <summary>
3141
/// Opens this document in the editor.
@@ -75,11 +85,11 @@ public EditorWorkspaceDocument[] Documents
7585
{
7686
get
7787
{
78-
string[] openDocumentPaths = editorOperations.GetWorkspaceOpenDocumentPaths();
79-
EditorWorkspaceDocument[] documents = new EditorWorkspaceDocument[openDocumentPaths.Length];
80-
for (int i = 0; i < openDocumentPaths.Length; i++)
88+
WorkspaceOpenDocument[] openDocuments = editorOperations.GetWorkspaceOpenDocuments();
89+
EditorWorkspaceDocument[] documents = new EditorWorkspaceDocument[openDocuments.Length];
90+
for (int i = 0; i < openDocuments.Length; i++)
8191
{
82-
documents[i] = new EditorWorkspaceDocument(this, openDocumentPaths[i]);
92+
documents[i] = new EditorWorkspaceDocument(this, openDocuments[i].Path, openDocuments[i].Saved);
8393
}
8494

8595
return documents;

src/PowerShellEditorServices/Extensions/IEditorOperations.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66

77
namespace Microsoft.PowerShell.EditorServices.Extensions
88
{
9+
internal readonly struct WorkspaceOpenDocument
10+
{
11+
internal WorkspaceOpenDocument(string path, bool saved)
12+
{
13+
Path = path;
14+
Saved = saved;
15+
}
16+
17+
public string Path { get; }
18+
19+
public bool Saved { get; }
20+
}
21+
922
/// <summary>
1023
/// Provides an interface that must be implemented by an editor
1124
/// host to perform operations invoked by extensions written in
@@ -33,10 +46,10 @@ internal interface IEditorOperations
3346
string[] GetWorkspacePaths();
3447

3548
/// <summary>
36-
/// Get all open document paths in the current workspace session.
49+
/// Get all open documents in the current workspace session.
3750
/// </summary>
38-
/// <returns>All currently open document paths.</returns>
39-
string[] GetWorkspaceOpenDocumentPaths();
51+
/// <returns>All currently open documents.</returns>
52+
WorkspaceOpenDocument[] GetWorkspaceOpenDocuments();
4053

4154
/// <summary>
4255
/// Resolves the given file path relative to the current workspace path.

src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ public async Task SaveFileAsync(string currentPath, string newSavePath)
198198

199199
public string[] GetWorkspacePaths() => _workspaceService.WorkspacePaths.ToArray();
200200

201-
public string[] GetWorkspaceOpenDocumentPaths() => _workspaceService.GetOpenedFiles().Select(static scriptFile => scriptFile.FilePath).ToArray();
201+
public WorkspaceOpenDocument[] GetWorkspaceOpenDocuments()
202+
=> _workspaceService.GetOpenedFiles()
203+
.Select(static scriptFile => new WorkspaceOpenDocument(scriptFile.FilePath, scriptFile.IsSaved))
204+
.ToArray();
202205

203206
public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile);
204207

src/PowerShellEditorServices/Services/TextDocument/Handlers/TextDocumentHandler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ public override async Task<Unit> Handle(DidSaveTextDocumentParams notification,
136136
{
137137
await _remoteFileManagerService.SaveRemoteFileAsync(savedFile.FilePath).ConfigureAwait(false);
138138
}
139+
140+
savedFile.IsSaved = true;
139141
}
140142
return Unit.Value;
141143
}

src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ internal sealed class ScriptFile
5555
/// </summary>
5656
public bool IsInMemory { get; }
5757

58+
/// <summary>
59+
/// Gets or sets whether this file has no unsaved changes.
60+
/// </summary>
61+
public bool IsSaved { get; internal set; }
62+
5863
/// <summary>
5964
/// Gets a string containing the full contents of the file.
6065
/// </summary>
@@ -137,6 +142,7 @@ internal ScriptFile(
137142

138143
// SetFileContents() calls ParseFileContents() which initializes the rest of the properties.
139144
SetFileContents(textReader.ReadToEnd());
145+
IsSaved = !IsInMemory;
140146
References = new ReferenceTable(this);
141147
}
142148

@@ -364,6 +370,7 @@ public void ApplyChange(FileChange fileChange)
364370

365371
// Parse the script again to be up-to-date
366372
ParseFileContents();
373+
IsSaved = false;
367374
References.TagAsChanged();
368375
}
369376

test/PowerShellEditorServices.Test/Extensions/EditorWorkspaceTests.cs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ public void DocumentsReturnsOpenWorkspaceDocuments()
1818
{
1919
TestEditorOperations editorOperations = new()
2020
{
21-
OpenDocumentPaths = new[] { @"C:\test\one.ps1", @"C:\test\two.ps1" }
21+
OpenDocuments = new[]
22+
{
23+
new WorkspaceOpenDocument(@"C:\test\one.ps1", saved: true),
24+
new WorkspaceOpenDocument(@"C:\test\two.ps1", saved: true)
25+
}
2226
};
2327

2428
EditorWorkspace workspace = new(editorOperations);
@@ -27,8 +31,16 @@ public void DocumentsReturnsOpenWorkspaceDocuments()
2731

2832
Assert.Collection(
2933
documents,
30-
document => Assert.Equal(@"C:\test\one.ps1", document.Path),
31-
document => Assert.Equal(@"C:\test\two.ps1", document.Path));
34+
document =>
35+
{
36+
Assert.Equal(@"C:\test\one.ps1", document.Path);
37+
Assert.True(document.Saved);
38+
},
39+
document =>
40+
{
41+
Assert.Equal(@"C:\test\two.ps1", document.Path);
42+
Assert.True(document.Saved);
43+
});
3244
}
3345

3446
[Fact]
@@ -37,7 +49,7 @@ public void DocumentOpenSaveAndCloseUseWorkspaceOperations()
3749
const string filePath = @"C:\test\file.ps1";
3850
TestEditorOperations editorOperations = new()
3951
{
40-
OpenDocumentPaths = new[] { filePath }
52+
OpenDocuments = new[] { new WorkspaceOpenDocument(filePath, saved: true) }
4153
};
4254

4355
EditorWorkspace workspace = new(editorOperations);
@@ -55,23 +67,52 @@ public void DocumentOpenSaveAndCloseUseWorkspaceOperations()
5567
}
5668

5769
[Fact]
58-
public void DocumentToStringReturnsDocumentPath()
70+
public void DocumentToStringReturnsFileNameAndSavedStatus()
5971
{
60-
const string filePath = @"C:\test\file.ps1";
72+
const string savedFilePath = @"C:\test\file.ps1";
73+
const string unsavedFilePath = @"C:\test\other.ps1";
6174
TestEditorOperations editorOperations = new()
6275
{
63-
OpenDocumentPaths = new[] { filePath }
76+
OpenDocuments = new[]
77+
{
78+
new WorkspaceOpenDocument(savedFilePath, saved: true),
79+
new WorkspaceOpenDocument(unsavedFilePath, saved: false)
80+
}
6481
};
6582

6683
EditorWorkspace workspace = new(editorOperations);
67-
EditorWorkspaceDocument document = Assert.Single(workspace.Documents);
84+
EditorWorkspaceDocument[] documents = workspace.Documents;
85+
86+
Assert.Collection(
87+
documents,
88+
document => Assert.Equal("file.ps1", document.ToString()),
89+
document => Assert.Equal("other.ps1 [Unsaved]", document.ToString()));
90+
}
91+
92+
[Fact]
93+
public void DocumentSavedReturnsWorkspaceSavedState()
94+
{
95+
TestEditorOperations editorOperations = new()
96+
{
97+
OpenDocuments = new[]
98+
{
99+
new WorkspaceOpenDocument(@"C:\test\saved.ps1", saved: true),
100+
new WorkspaceOpenDocument(@"C:\test\unsaved.ps1", saved: false)
101+
}
102+
};
68103

69-
Assert.Equal(filePath, document.ToString());
104+
EditorWorkspace workspace = new(editorOperations);
105+
EditorWorkspaceDocument[] documents = workspace.Documents;
106+
107+
Assert.Collection(
108+
documents,
109+
document => Assert.True(document.Saved),
110+
document => Assert.False(document.Saved));
70111
}
71112

72113
private sealed class TestEditorOperations : IEditorOperations
73114
{
74-
public string[] OpenDocumentPaths { get; set; } = Array.Empty<string>();
115+
public WorkspaceOpenDocument[] OpenDocuments { get; set; } = Array.Empty<WorkspaceOpenDocument>();
75116

76117
public List<string> Calls { get; } = new();
77118

@@ -81,7 +122,7 @@ private sealed class TestEditorOperations : IEditorOperations
81122

82123
public string[] GetWorkspacePaths() => new[] { @"C:\test" };
83124

84-
public string[] GetWorkspaceOpenDocumentPaths() => OpenDocumentPaths;
125+
public WorkspaceOpenDocument[] GetWorkspaceOpenDocuments() => OpenDocuments;
85126

86127
public string GetWorkspaceRelativePath(ScriptFile scriptFile) => scriptFile.FilePath;
87128

0 commit comments

Comments
 (0)