diff --git a/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs b/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs index 2637ec4cb..a7c385bc6 100644 --- a/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs +++ b/src/PowerShellEditorServices.Protocol/LanguageServer/EditorCommands.cs @@ -38,6 +38,10 @@ public static readonly public class ClientEditorContext { + public string CurrentFileContent { get; set; } + + public string CurrentFileLanguage { get; set; } + public string CurrentFilePath { get; set; } public Position CursorPosition { get; set; } diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 7d1e7b550..f35e9998e 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -494,31 +494,14 @@ protected async Task HandleSetBreakpointsRequest( { ScriptFile scriptFile = null; - // Fix for issue #195 - user can change name of file outside of VSCode in which case - // VSCode sends breakpoint requests with the original filename that doesn't exist anymore. - try - { - // When you set a breakpoint in the right pane of a Git diff window on a PS1 file, - // the Source.Path comes through as Untitled-X. - if (!ScriptFile.IsUntitledPath(setBreakpointsParams.Source.Path)) - { - scriptFile = _editorSession.Workspace.GetFile(setBreakpointsParams.Source.Path); - } - } - catch (Exception e) when ( - e is FileNotFoundException || - e is DirectoryNotFoundException || - e is IOException || - e is NotSupportedException || - e is PathTooLongException || - e is SecurityException || - e is UnauthorizedAccessException) + // When you set a breakpoint in the right pane of a Git diff window on a PS1 file, + // the Source.Path comes through as Untitled-X. That's why we check for IsUntitledPath. + if (!ScriptFile.IsUntitledPath(setBreakpointsParams.Source.Path) && + !_editorSession.Workspace.TryGetFile( + setBreakpointsParams.Source.Path, + out scriptFile)) { - Logger.WriteException( - $"Failed to set breakpoint on file: {setBreakpointsParams.Source.Path}", - e); - - string message = _noDebug ? string.Empty : "Source file could not be accessed, breakpoint not set - " + e.Message; + string message = _noDebug ? string.Empty : "Source file could not be accessed, breakpoint not set."; var srcBreakpoints = setBreakpointsParams.Breakpoints .Select(srcBkpt => Protocol.DebugAdapter.Breakpoint.Create( srcBkpt, setBreakpointsParams.Source.Path, message, verified: _noDebug)); diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs index 84a561b0a..8639f5ee3 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServerEditorOperations.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.PowerShell.EditorServices; using Microsoft.PowerShell.EditorServices.Extensions; using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; @@ -89,10 +90,14 @@ public Task SetSelection(BufferRange selectionRange) public EditorContext ConvertClientEditorContext( ClientEditorContext clientContext) { + ScriptFile scriptFile = this.editorSession.Workspace.CreateScriptFileFromFileBuffer( + clientContext.CurrentFilePath, + clientContext.CurrentFileContent); + return new EditorContext( this, - this.editorSession.Workspace.GetFile(clientContext.CurrentFilePath), + scriptFile, new BufferPosition( clientContext.CursorPosition.Line + 1, clientContext.CursorPosition.Character + 1), @@ -100,7 +105,8 @@ public EditorContext ConvertClientEditorContext( clientContext.SelectionRange.Start.Line + 1, clientContext.SelectionRange.Start.Character + 1, clientContext.SelectionRange.End.Line + 1, - clientContext.SelectionRange.End.Character + 1)); + clientContext.SelectionRange.End.Character + 1), + clientContext.CurrentFileLanguage); } public Task NewFile() diff --git a/src/PowerShellEditorServices/Extensions/EditorContext.cs b/src/PowerShellEditorServices/Extensions/EditorContext.cs index cfccc516e..c29c60fcc 100644 --- a/src/PowerShellEditorServices/Extensions/EditorContext.cs +++ b/src/PowerShellEditorServices/Extensions/EditorContext.cs @@ -48,14 +48,16 @@ public class EditorContext /// The ScriptFile that is in the active editor buffer. /// The position of the user's cursor in the active editor buffer. /// The range of the user's selection in the active editor buffer. + /// Determines the language of the file.false If it is not specified, then it defaults to "Unknown" public EditorContext( IEditorOperations editorOperations, ScriptFile currentFile, BufferPosition cursorPosition, - BufferRange selectedRange) + BufferRange selectedRange, + string language = "Unknown") { this.editorOperations = editorOperations; - this.CurrentFile = new FileContext(currentFile, this, editorOperations); + this.CurrentFile = new FileContext(currentFile, this, editorOperations, language); this.SelectedRange = selectedRange; this.CursorPosition = new FilePosition(currentFile, cursorPosition); } diff --git a/src/PowerShellEditorServices/Extensions/FileContext.cs b/src/PowerShellEditorServices/Extensions/FileContext.cs index 35b7e18fd..1b563dcb2 100644 --- a/src/PowerShellEditorServices/Extensions/FileContext.cs +++ b/src/PowerShellEditorServices/Extensions/FileContext.cs @@ -26,32 +26,33 @@ public class FileContext #region Properties /// - /// Gets the filesystem path of the file. + /// Gets the parsed abstract syntax tree for the file. /// - public string Path + public Ast Ast { - get { return this.scriptFile.FilePath; } + get { return this.scriptFile.ScriptAst; } } /// - /// Gets the workspace-relative path of the file. + /// Gets a BufferRange which represents the entire content + /// range of the file. /// - public string WorkspacePath + public BufferRange FileRange { - get - { - return - this.editorOperations.GetWorkspaceRelativePath( - this.scriptFile.FilePath); - } + get { return this.scriptFile.FileRange; } } /// - /// Gets the parsed abstract syntax tree for the file. + /// Gets the language of the file. /// - public Ast Ast + public string Language { get; private set; } + + /// + /// Gets the filesystem path of the file. + /// + public string Path { - get { return this.scriptFile.ScriptAst; } + get { return this.scriptFile.FilePath; } } /// @@ -63,12 +64,16 @@ public Token[] Tokens } /// - /// Gets a BufferRange which represents the entire content - /// range of the file. + /// Gets the workspace-relative path of the file. /// - public BufferRange FileRange + public string WorkspacePath { - get { return this.scriptFile.FileRange; } + get + { + return + this.editorOperations.GetWorkspaceRelativePath( + this.scriptFile.FilePath); + } } #endregion @@ -81,14 +86,22 @@ public BufferRange FileRange /// The ScriptFile to which this file refers. /// The EditorContext to which this file relates. /// An IEditorOperations implementation which performs operations in the editor. + /// Determines the language of the file.false If it is not specified, then it defaults to "Unknown" public FileContext( ScriptFile scriptFile, EditorContext editorContext, - IEditorOperations editorOperations) + IEditorOperations editorOperations, + string language = "Unknown") { + if (string.IsNullOrWhiteSpace(language)) + { + language = "Unknown"; + } + this.scriptFile = scriptFile; this.editorContext = editorContext; this.editorOperations = editorOperations; + this.Language = language; } #endregion diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index e30c00b8a..383c5f1da 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -342,17 +342,7 @@ public async Task FindReferencesOfSymbol( { if (!fileMap.Contains(file)) { - ScriptFile scriptFile; - try - { - scriptFile = workspace.GetFile(file); - } - catch (Exception e) when (e is IOException - || e is SecurityException - || e is FileNotFoundException - || e is DirectoryNotFoundException - || e is PathTooLongException - || e is UnauthorizedAccessException) + if (!workspace.TryGetFile(file, out ScriptFile scriptFile)) { // If we can't access the file for some reason, just ignore it continue; diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 12bfae3a4..25b02031d 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -57,6 +57,35 @@ public Workspace(Version powerShellVersion, ILogger logger) #region Public Methods + /// + /// Creates a new ScriptFile instance which is identified by the given file + /// path and initially contains the given buffer contents. + /// + /// The file path for which a buffer will be retrieved. + /// The initial buffer contents if there is not an existing ScriptFile for this path. + /// A ScriptFile instance for the specified path. + public ScriptFile CreateScriptFileFromFileBuffer(string filePath, string initialBuffer) + { + Validate.IsNotNullOrEmptyString("filePath", filePath); + + // Resolve the full file path + string resolvedFilePath = this.ResolveFilePath(filePath); + string keyName = resolvedFilePath.ToLower(); + + ScriptFile scriptFile = + new ScriptFile( + resolvedFilePath, + filePath, + initialBuffer, + this.powerShellVersion); + + this.workspaceFiles[keyName] = scriptFile; + + this.logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); + + return scriptFile; + } + /// /// Gets an open file in the workspace. If the file isn't open but /// exists on the filesystem, load and return it. @@ -101,6 +130,34 @@ public ScriptFile GetFile(string filePath) return scriptFile; } + /// + /// Tries to get an open file in the workspace. Returns true if it succeeds, false otherwise. + /// + /// The file path at which the script resides. + /// The out parameter that will contain the ScriptFile object. + public bool TryGetFile(string filePath, out ScriptFile scriptFile) + { + try + { + scriptFile = GetFile(filePath); + return true; + } + catch (Exception e) when ( + e is IOException || + e is SecurityException || + e is FileNotFoundException || + e is DirectoryNotFoundException || + e is PathTooLongException || + e is UnauthorizedAccessException) + { + this.logger.WriteException( + $"Failed to set breakpoint on file: {filePath}", + e); + scriptFile = null; + return false; + } + } + /// /// Gets a new ScriptFile instance which is identified by the given file path. ///