// routines related to tracking the editor state /// <summary> /// To be called whenever a file is opened in the editor. /// Does nothing if the given file is listed as to be ignored. /// Otherwise publishes suitable diagnostics for it. /// Invokes the given Action showError with a suitable message if the given file cannot be loaded. /// Invokes the given Action logError with a suitable message if the given file cannot be associated with a compilation unit, /// or if the given file is already listed as being open in the editor. /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// Throws an ArgumentNullException if the given content is null. /// </summary> internal Task OpenFileAsync(TextDocumentItem textDocument, Action <string, MessageType> showError = null, Action <string, MessageType> logError = null) { if (!ValidFileUri(textDocument?.Uri)) { throw new ArgumentException("invalid text document identifier"); } if (textDocument.Text == null) { throw new ArgumentNullException(nameof(textDocument.Text)); } _ = this.Projects.ManagerTaskAsync(textDocument.Uri, (manager, associatedWithProject) => { if (IgnoreFile(textDocument.Uri)) { return; } var newManager = CompilationUnitManager.InitializeFileManager(textDocument.Uri, textDocument.Text, this.Publish, ex => { showError?.Invoke($"Failed to load file '{textDocument.Uri.LocalPath}'", MessageType.Error); manager.LogException(ex); }); // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. // To mitigate the impact of failures we choose to just log them as info. var file = this.OpenFiles.GetOrAdd(textDocument.Uri, newManager); if (file != newManager) // this may be the case (depending on the editor) e.g. when opening a version control diff ... { showError?.Invoke($"Version control and opening multiple versions of the same file in the editor are currently not supported. \n" + $"Intellisense has been disable for the file '{textDocument.Uri.LocalPath}'. An editor restart is required to enable intellisense again.", MessageType.Error); #if DEBUG if (showError == null) { logError?.Invoke("Attempting to open a file that is already open in the editor.", MessageType.Error); } #endif this.IgnoreEditorUpdatesFor(textDocument.Uri); this.OpenFiles.TryRemove(textDocument.Uri, out FileContentManager _); if (!associatedWithProject) { _ = manager.TryRemoveSourceFileAsync(textDocument.Uri); } this.Publish(new PublishDiagnosticParams { Uri = textDocument.Uri, Diagnostics = new Diagnostic[0] }); return; } if (!associatedWithProject) { logError?.Invoke( $"The file {textDocument.Uri.LocalPath} is not associated with a compilation unit. Only syntactic diagnostics are generated." , MessageType.Info); } _ = manager.AddOrUpdateSourceFileAsync(file); }); // reloading from disk in case we encountered a file already open error above return(this.Projects.SourceFileChangedOnDiskAsync(textDocument.Uri, GetOpenFile)); // NOTE: relies on that the manager task is indeed executed first! }
/// <summary> /// Used to reload the file content when a file is saved. /// Does nothing if the given file is listed as to be ignored. /// Expects to get the entire content of the file at the time of saving as argument. /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// Throws an ArgumentNullException if the given content is null. /// </summary> internal Task SaveFileAsync(TextDocumentIdentifier textDocument, string fileContent) { if (!ValidFileUri(textDocument?.Uri)) { throw new ArgumentException("invalid text document identifier"); } if (fileContent == null) { throw new ArgumentNullException(nameof(fileContent)); } return(this.Projects.ManagerTaskAsync(textDocument.Uri, (manager, __) => { if (IgnoreFile(textDocument.Uri)) { return; } // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. // To mitigate the impact of failures we choose to ignore them silently and do our best to recover. if (!this.OpenFiles.TryGetValue(textDocument.Uri, out var file)) { file = CompilationUnitManager.InitializeFileManager(textDocument.Uri, fileContent, this.Publish, manager.LogException); this.OpenFiles.TryAdd(textDocument.Uri, file); _ = manager.AddOrUpdateSourceFileAsync(file); } else { _ = manager.AddOrUpdateSourceFileAsync(file, fileContent); // let's reload the file content on saving } })); }
private FileContentManager InitializeFileManager(IEnumerable <string> examples, QsCompilation compilation, string nsName = null) { var(pre, post) = ($"namespace {nsName ?? DefaultNamespaceName}{{ {Environment.NewLine}", $"{Environment.NewLine}}}"); var openDirs = String.Join(Environment.NewLine, OpenedForTesting .Where(nsName => ContainsNamespace(compilation, nsName)) .Select(nsName => $"open {nsName};")); string WrapInNamespace(string example) => pre + openDirs + example + post; examples = examples.Where(ex => !String.IsNullOrWhiteSpace(ex)); var sourceCode = String.Join(Environment.NewLine, examples.Select(WrapInNamespace)); var sourceName = NonNullable <string> .New(Path.GetFullPath($"{nsName}{CodeSource}")); return(CompilationUnitManager.TryGetUri(sourceName, out var sourceUri) ? CompilationUnitManager.InitializeFileManager(sourceUri, sourceCode) : null); }