Skip to content

Commit 715df7f

Browse files
authored
Merge branch 'main' into rename-function-customvisitor
2 parents 7cebf5e + caa9d28 commit 715df7f

17 files changed

Lines changed: 454 additions & 68 deletions

File tree

‎src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -267,18 +267,12 @@ private HostStartupInfo CreateHostStartupInfo()
267267
{
268268
_logger.Log(PsesLogLevel.Debug, "Creating startup info object");
269269

270-
ProfilePathInfo profilePaths = null;
271-
if (_config.ProfilePaths.AllUsersAllHosts != null
272-
|| _config.ProfilePaths.AllUsersCurrentHost != null
273-
|| _config.ProfilePaths.CurrentUserAllHosts != null
274-
|| _config.ProfilePaths.CurrentUserCurrentHost != null)
275-
{
276-
profilePaths = new ProfilePathInfo(
277-
_config.ProfilePaths.CurrentUserAllHosts,
278-
_config.ProfilePaths.CurrentUserCurrentHost,
279-
_config.ProfilePaths.AllUsersAllHosts,
280-
_config.ProfilePaths.AllUsersCurrentHost);
281-
}
270+
ProfilePathInfo profilePaths = new(
271+
_config.ProfilePaths.CurrentUserAllHosts,
272+
_config.ProfilePaths.CurrentUserCurrentHost,
273+
_config.ProfilePaths.AllUsersAllHosts,
274+
_config.ProfilePaths.AllUsersCurrentHost
275+
);
282276

283277
return new HostStartupInfo(
284278
_config.HostInfo.Name,

‎src/PowerShellEditorServices/Extensions/EditorWorkspace.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public sealed class EditorWorkspace
2828
/// </summary>
2929
public string[] Paths => editorOperations.GetWorkspacePaths();
3030

31+
/// <summary>
32+
/// Get all currently open documents in the workspace.
33+
/// </summary>
34+
public WorkspaceOpenDocument[] Documents => editorOperations.GetWorkspaceOpenDocuments();
35+
3136
#endregion
3237

3338
#region Constructors
@@ -76,13 +81,15 @@ public sealed class EditorWorkspace
7681
/// <param name="filePath">The path to the file to be closed.</param>
7782
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Supporting synchronous API.")]
7883
public void CloseFile(string filePath) => editorOperations.CloseFileAsync(filePath).Wait();
84+
public void CloseFile(WorkspaceOpenDocument document) => CloseFile(document.Path);
7985

8086
/// <summary>
8187
/// Saves an open file in the workspace.
8288
/// </summary>
8389
/// <param name="filePath">The path to the file to be saved.</param>
8490
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Supporting synchronous API.")]
8591
public void SaveFile(string filePath) => editorOperations.SaveFileAsync(filePath).Wait();
92+
public void SaveFile(WorkspaceOpenDocument document) => SaveFile(document.Path);
8693

8794
/// <summary>
8895
/// Saves a file with a new name AKA a copy.

‎src/PowerShellEditorServices/Extensions/IEditorOperations.cs‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,34 @@
33

44
using System.Threading.Tasks;
55
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
6+
#nullable enable
67

78
namespace Microsoft.PowerShell.EditorServices.Extensions
89
{
10+
public readonly struct WorkspaceOpenDocument(string path, bool saved)
11+
{
12+
/// <summary>
13+
/// Gets the path or URI of the open document.
14+
/// </summary>
15+
public string Path { get; } = path;
16+
17+
/// <summary>
18+
/// Gets whether the document is backed by a saved file path (not in-memory).
19+
/// </summary>
20+
public bool Saved { get; } = saved;
21+
22+
/// <summary>
23+
/// Gets the display name of this document and unsaved status.
24+
/// </summary>
25+
/// <returns>The display name of this document.</returns>
26+
public override string ToString()
27+
{
28+
string documentPath = Path ?? string.Empty;
29+
string fileName = System.IO.Path.GetFileName(documentPath);
30+
return Saved ? fileName : fileName + " [Unsaved]";
31+
}
32+
}
33+
934
/// <summary>
1035
/// Provides an interface that must be implemented by an editor
1136
/// host to perform operations invoked by extensions written in
@@ -32,6 +57,12 @@ internal interface IEditorOperations
3257
/// <returns></returns>
3358
string[] GetWorkspacePaths();
3459

60+
/// <summary>
61+
/// Get all open documents in the current workspace session.
62+
/// </summary>
63+
/// <returns>All currently open documents.</returns>
64+
WorkspaceOpenDocument[] GetWorkspaceOpenDocuments();
65+
3566
/// <summary>
3667
/// Resolves the given file path relative to the current workspace path.
3768
/// </summary>

‎src/PowerShellEditorServices/Hosting/HostStartupInfo.cs‎

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3+
#nullable enable
34

45
using System;
56
using System.Collections.Generic;
@@ -190,32 +191,13 @@ public HostStartupInfo(
190191
}
191192

192193
/// <summary>
193-
/// This is a strange class that is generally <c>null</c> or otherwise just has a single path
194-
/// set. It is eventually parsed one-by-one when setting up the PowerShell runspace.
194+
/// Stores profile information passed from Start-EditorServices to be used for loading profiles if configured
195+
/// and for the $PROFILE variable in the initial session state.
195196
/// </summary>
196-
/// <remarks>
197-
/// TODO: Simplify this as a <see langword="record"/>.
198-
/// </remarks>
199-
public sealed class ProfilePathInfo
200-
{
201-
public ProfilePathInfo(
202-
string currentUserAllHosts,
203-
string currentUserCurrentHost,
204-
string allUsersAllHosts,
205-
string allUsersCurrentHost)
206-
{
207-
CurrentUserAllHosts = currentUserAllHosts;
208-
CurrentUserCurrentHost = currentUserCurrentHost;
209-
AllUsersAllHosts = allUsersAllHosts;
210-
AllUsersCurrentHost = allUsersCurrentHost;
211-
}
212-
213-
public string CurrentUserAllHosts { get; }
214-
215-
public string CurrentUserCurrentHost { get; }
216-
217-
public string AllUsersAllHosts { get; }
218-
219-
public string AllUsersCurrentHost { get; }
220-
}
197+
public readonly record struct ProfilePathInfo(
198+
string CurrentUserAllHosts,
199+
string CurrentUserCurrentHost,
200+
string AllUsersAllHosts,
201+
string AllUsersCurrentHost
202+
);
221203
}

‎src/PowerShellEditorServices/Services/Extension/EditorOperationsService.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ public async Task SaveFileAsync(string currentPath, string newSavePath)
198198

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

201+
public WorkspaceOpenDocument[] GetWorkspaceOpenDocuments()
202+
=> [..
203+
_workspaceService
204+
.GetOpenedFiles()
205+
.Where(static scriptFile => scriptFile.IsOpen)
206+
.Select(static scriptFile => new WorkspaceOpenDocument(scriptFile.FilePath, !scriptFile.IsInMemory))
207+
];
208+
201209
public string GetWorkspaceRelativePath(ScriptFile scriptFile) => _workspaceService.GetRelativePath(scriptFile);
202210

203211
public async Task ShowInformationMessageAsync(string message)

‎src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,20 @@ public async Task<bool> TryStartAsync(HostStartOptions startOptions, Cancellatio
306306
_logger.LogDebug("InitialWorkingDirectory set!");
307307
}
308308

309+
_logger.LogDebug("Setting profile variable...");
310+
await SetProfileVariableAsync(cancellationToken).ConfigureAwait(false);
311+
_logger.LogDebug("Profile variable set!");
312+
309313
if (startOptions.LoadProfiles)
310314
{
311315
_logger.LogDebug("Loading profiles...");
312316
await LoadHostProfilesAsync(cancellationToken).ConfigureAwait(false);
313317
_logger.LogDebug("Profiles loaded!");
314318
}
319+
else
320+
{
321+
_logger.LogDebug("Profile loading skipped per configuration!");
322+
}
315323

316324
if (!string.IsNullOrEmpty(startOptions.ShellIntegrationScript))
317325
{
@@ -583,13 +591,35 @@ internal void DisableTranscribeOnly()
583591
}
584592
}
585593

594+
internal Task SetProfileVariableAsync(CancellationToken cancellationToken)
595+
{
596+
// If the CurrentUserCurrentHost profile is null then we cannot create the profile variable
597+
if (_hostInfo.ProfilePaths.CurrentUserCurrentHost is null)
598+
{
599+
return Task.CompletedTask;
600+
}
601+
602+
// NOTE: This is a special task run on startup!
603+
return ExecuteDelegateAsync(
604+
"SetProfileVariable",
605+
executionOptions: null,
606+
(pwsh, _) => pwsh.SetProfileVariable(_hostInfo.ProfilePaths),
607+
cancellationToken);
608+
}
609+
586610
internal Task LoadHostProfilesAsync(CancellationToken cancellationToken)
587611
{
612+
// If the CurrentUserCurrentHost profile is null then we cannot instantiate
613+
if (_hostInfo.ProfilePaths.CurrentUserCurrentHost is null)
614+
{
615+
return Task.CompletedTask;
616+
}
617+
588618
// NOTE: This is a special task run on startup!
589619
return ExecuteDelegateAsync(
590620
"LoadProfiles",
591621
executionOptions: null,
592-
(pwsh, _) => pwsh.LoadProfiles(_hostInfo.ProfilePaths),
622+
(pwsh, _) => pwsh.LoadProfileScripts(_hostInfo.ProfilePaths),
593623
cancellationToken);
594624
}
595625

@@ -812,8 +842,9 @@ private void RunTopLevelExecutionLoop()
812842
{
813843
// Make sure we execute any startup tasks first. These should be, in order:
814844
// 1. Delegate to register psEditor variable
815-
// 2. LoadProfiles delegate
816-
// 3. Delegate to import PSEditModule
845+
// 2. SetProfileVariable delegate
846+
// 3. Optional LoadProfiles delegate
847+
// 4. Delegate to import PSEditModule
817848
while (_taskQueue.TryTake(out ISynchronousTask task))
818849
{
819850
task.ExecuteSynchronously(CancellationToken.None);

‎src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3-
3+
#nullable enable
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.ObjectModel;
@@ -69,7 +69,7 @@ and not PSInvocationState.Failed
6969
pwsh.InvocationStateChanged += handler;
7070
}
7171

72-
public static Collection<TResult> InvokeAndClear<TResult>(this PowerShell pwsh, PSInvocationSettings invocationSettings = null)
72+
public static Collection<TResult> InvokeAndClear<TResult>(this PowerShell pwsh, PSInvocationSettings? invocationSettings = null)
7373
{
7474
try
7575
{
@@ -81,7 +81,7 @@ public static Collection<TResult> InvokeAndClear<TResult>(this PowerShell pwsh,
8181
}
8282
}
8383

84-
public static void InvokeAndClear(this PowerShell pwsh, PSInvocationSettings invocationSettings = null)
84+
public static void InvokeAndClear(this PowerShell pwsh, PSInvocationSettings? invocationSettings = null)
8585
{
8686
try
8787
{
@@ -93,13 +93,13 @@ public static void InvokeAndClear(this PowerShell pwsh, PSInvocationSettings inv
9393
}
9494
}
9595

96-
public static Collection<TResult> InvokeCommand<TResult>(this PowerShell pwsh, PSCommand psCommand, PSInvocationSettings invocationSettings = null)
96+
public static Collection<TResult> InvokeCommand<TResult>(this PowerShell pwsh, PSCommand psCommand, PSInvocationSettings? invocationSettings = null)
9797
{
9898
pwsh.Commands = psCommand;
9999
return pwsh.InvokeAndClear<TResult>(invocationSettings);
100100
}
101101

102-
public static void InvokeCommand(this PowerShell pwsh, PSCommand psCommand, PSInvocationSettings invocationSettings = null)
102+
public static void InvokeCommand(this PowerShell pwsh, PSCommand psCommand, PSInvocationSettings? invocationSettings = null)
103103
{
104104
pwsh.Commands = psCommand;
105105
pwsh.InvokeAndClear(invocationSettings);
@@ -193,7 +193,7 @@ public static void SetCorrectExecutionPolicy(this PowerShell pwsh, ILogger logge
193193
}
194194
}
195195

196-
public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePaths)
196+
public static void SetProfileVariable(this PowerShell pwsh, ProfilePathInfo profilePaths)
197197
{
198198
// Per the documentation, "the `$PROFILE` variable stores the path to the 'Current User,
199199
// Current Host' profile. The other profiles are saved in note properties of the
@@ -202,15 +202,24 @@ public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePat
202202
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.1#the-profile-variable
203203
PSObject profileVariable = PSObject.AsPSObject(profilePaths.CurrentUserCurrentHost);
204204

205+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts));
206+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost));
207+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts));
208+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost));
209+
210+
pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable);
211+
}
212+
213+
public static void LoadProfileScripts(this PowerShell pwsh, ProfilePathInfo profilePaths)
214+
{
215+
PSObject profileVariable = PSObject.AsPSObject(profilePaths.CurrentUserCurrentHost);
216+
205217
PSCommand psCommand = new PSCommand()
206218
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts)
207219
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost)
208220
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts)
209221
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost);
210222

211-
// NOTE: This must be set before the profiles are loaded.
212-
pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable);
213-
214223
// NOTE: Because it's possible there are no profiles defined, we might have an empty
215224
// command. Since this is being executed directly, we can't rely on `ThrowOnError =
216225
// false` to avoid an exception here. Instead, we must just not execute it.
@@ -253,7 +262,7 @@ private static StringBuilder AddErrorString(this StringBuilder sb, ErrorRecord e
253262
.AppendLine("Exception:")
254263
.Append(" ").Append(error.Exception.ToString() ?? "<null>");
255264

256-
Exception innerException = error.Exception?.InnerException;
265+
Exception? innerException = error.Exception?.InnerException;
257266
while (innerException != null)
258267
{
259268
sb.AppendLine("InnerException:")

‎src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ private CompletionItem CreateProviderItemCompletion(
366366
if (textToBeReplaced.IndexOf(PSScriptRootVariable, StringComparison.OrdinalIgnoreCase) is int variableIndex and not -1
367367
&& System.IO.Path.GetDirectoryName(scriptFile.FilePath) is string scriptFolder and not ""
368368
&& completionText.IndexOf(scriptFolder, StringComparison.OrdinalIgnoreCase) is int pathIndex and not -1
369-
&& !scriptFile.IsInMemory)
369+
&& !scriptFile.IsUntitled)
370370
{
371371
completionText = completionText
372372
.Remove(pathIndex, scriptFolder.Length)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ public override Task<Unit> Handle(DidCloseTextDocumentParams notification, Cance
111111
{
112112
fileToClose.IsOpen = false;
113113

114-
// If the file watcher is supported, only close in-memory files when this
114+
// If the file watcher is supported, only close non-file-backed documents when this
115115
// notification is triggered. This lets us keep workspace files open so we can scan
116116
// for references. When a file is deleted, the file watcher will close the file.
117-
if (!_isFileWatcherSupported || fileToClose.IsInMemory)
117+
bool isBackedByFile = !fileToClose.IsUntitled;
118+
if (!_isFileWatcherSupported || !isBackedByFile)
118119
{
119120
_workspaceService.CloseFile(fileToClose);
120121
}
@@ -132,6 +133,9 @@ public override async Task<Unit> Handle(DidSaveTextDocumentParams notification,
132133

133134
if (savedFile != null)
134135
{
136+
// On a save, untitled files will remain in memory, so this won't change for those
137+
savedFile.IsInMemory = savedFile.IsUntitled;
138+
135139
if (_remoteFileManagerService.IsUnderRemoteTempPath(savedFile.FilePath))
136140
{
137141
await _remoteFileManagerService.SaveRemoteFileAsync(savedFile.FilePath).ConfigureAwait(false);

0 commit comments

Comments
 (0)