diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs index 47b187f12ca622db9e7ad03816692f3161eaaa9f..11b2a500d6b205b53de440c0e286299930e54835 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestHostDocument.cs @@ -32,6 +32,7 @@ public class TestHostDocument private readonly SourceCodeKind _sourceCodeKind; private readonly string _filePath; private readonly IReadOnlyList _folders; + private readonly IDocumentServiceProvider _documentServiceProvider; public DocumentId Id { @@ -101,7 +102,8 @@ public bool IsGenerated IDictionary> spans, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IReadOnlyList folders = null, - bool isLinkFile = false) + bool isLinkFile = false, + IDocumentServiceProvider documentServiceProvider = null) { Contract.ThrowIfNull(textBuffer); Contract.ThrowIfNull(filePath); @@ -117,6 +119,7 @@ public bool IsGenerated this.CursorPosition = cursorPosition; _sourceCodeKind = sourceCodeKind; this.IsLinkFile = isLinkFile; + _documentServiceProvider = documentServiceProvider; this.SelectedSpans = new List(); if (spans.ContainsKey(string.Empty)) @@ -281,7 +284,7 @@ internal void CloseTextView() public DocumentInfo ToDocumentInfo() { - return DocumentInfo.Create(this.Id, this.Name, this.Folders, this.SourceCodeKind, loader: this.Loader, filePath: this.FilePath, isGenerated: this.IsGenerated); + return DocumentInfo.Create(this.Id, this.Name, this.Folders, this.SourceCodeKind, loader: this.Loader, filePath: this.FilePath, isGenerated: this.IsGenerated, _documentServiceProvider); } } } diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index 43ed0981aa756437ee08031e75fd05343b65394f..b5f70dc6111672976b69c37bd0c69adf985c4f83 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -71,12 +71,13 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true, return Create(workspaceElement, completed, openDocuments, exportProvider, workspaceKind); } - public static TestWorkspace Create( + internal static TestWorkspace Create( XElement workspaceElement, bool completed = true, bool openDocuments = true, ExportProvider exportProvider = null, - string workspaceKind = null) + string workspaceKind = null, + IDocumentServiceProvider documentServiceProvider = null) { if (workspaceElement.Name != WorkspaceElementName) { @@ -103,6 +104,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true, workspace, documentElementToFilePath, filePathToTextBufferMap, + documentServiceProvider, ref projectIdentifier, ref documentIdentifier); Assert.False(projectNameToTestHostProject.ContainsKey(project.Name), $"The workspace XML already contains a project with name {project.Name}"); @@ -255,6 +257,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true, TestWorkspace workspace, Dictionary documentElementToFilePath, Dictionary filePathToTextBufferMap, + IDocumentServiceProvider documentServiceProvider, ref int projectId, ref int documentId) { @@ -303,6 +306,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true, exportProvider, languageServices, filePathToTextBufferMap, + documentServiceProvider, ref documentId); documents.Add(document); @@ -584,6 +588,7 @@ private static CompilationOptions CreateCompilationOptions(TestWorkspace workspa ExportProvider exportProvider, HostLanguageServices languageServiceProvider, Dictionary filePathToTextBufferMap, + IDocumentServiceProvider documentServiceProvider, ref int documentId) { string markupCode; @@ -674,7 +679,8 @@ private static CompilationOptions CreateCompilationOptions(TestWorkspace workspa filePathToTextBufferMap.Add(filePath, textBuffer); } - return new TestHostDocument(exportProvider, languageServiceProvider, textBuffer, filePath, cursorPosition, spans, codeKind, folders, isLinkFile); + return new TestHostDocument( + exportProvider, languageServiceProvider, textBuffer, filePath, cursorPosition, spans, codeKind, folders, isLinkFile, documentServiceProvider); } private static string GetFilePath( diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs index 3189900263c96952ac589244703446ca347fa383..7e7676b9ffe1fdcbebd83f81dc5731e8585d10e3 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Roslyn.Utilities; @@ -431,9 +432,14 @@ public void AddSourceFile(string fullPath, SourceCodeKind sourceCodeKind = Sourc _sourceFiles.AddFile(fullPath, sourceCodeKind, folders); } - public DocumentId AddSourceTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, ImmutableArray folders = default) + public DocumentId AddSourceTextContainer( + SourceTextContainer textContainer, + string fullPath, + SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, + ImmutableArray folders = default, + IDocumentServiceProvider documentServiceProvider = null) { - return _sourceFiles.AddTextContainer(textContainer, fullPath, sourceCodeKind, folders); + return _sourceFiles.AddTextContainer(textContainer, fullPath, sourceCodeKind, folders, documentServiceProvider); } public bool ContainsSourceFile(string fullPath) @@ -902,7 +908,7 @@ public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, Immuta return documentId; } - public DocumentId AddTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders) + public DocumentId AddTextContainer(SourceTextContainer textContainer, string fullPath, SourceCodeKind sourceCodeKind, ImmutableArray folders, IDocumentServiceProvider documentServiceProvider) { if (textContainer == null) { @@ -917,7 +923,9 @@ public DocumentId AddTextContainer(SourceTextContainer textContainer, string ful folders: folders.IsDefault ? null : (IEnumerable)folders, sourceCodeKind: sourceCodeKind, loader: textLoader, - filePath: fullPath); + filePath: fullPath, + isGenerated: false, + documentServiceProvider: documentServiceProvider); lock (_project._gate) { diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/IDocumentService.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/IDocumentService.cs new file mode 100644 index 0000000000000000000000000000000000000000..4375b3eddc7cbe883a90c8169d3c65ff9c2e8f01 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/IDocumentService.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Host +{ + /// + /// Empty interface just to mark document services. + /// + internal interface IDocumentService + { + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/IDocumentServiceProvider.cs b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/IDocumentServiceProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..022e72b27c8860b4c7c62980e530e02cb9c3413d --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/DocumentService/IDocumentServiceProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Host +{ + internal interface IDocumentServiceProvider + { + /// + /// Gets a document specific service provided by the host identified by the service type. + /// If the host does not provide the service, this method returns null. + /// + TService GetService() where TService : class, IDocumentService; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DefaultTextDocumentServiceProvider.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DefaultTextDocumentServiceProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..c429c18b40d50dad4bee0dd8b4297d45fec2b109 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DefaultTextDocumentServiceProvider.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis +{ + /// + /// for regular C#/VB files. + /// + internal sealed class DefaultTextDocumentServiceProvider : IDocumentServiceProvider + { + public static readonly DefaultTextDocumentServiceProvider Instance = new DefaultTextDocumentServiceProvider(); + + private DefaultTextDocumentServiceProvider() { } + + public TService GetService() where TService : class, IDocumentService + { + return default; + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs index 696c00df1dbbf13f2fc6bd02ec31bc84eda6dee1..e5e4218af4cff7f2cca08992b299da621e0a8c6c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentInfo.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -53,13 +54,19 @@ public sealed class DocumentInfo /// public TextLoader TextLoader { get; } + /// + /// A associated with this document + /// + internal IDocumentServiceProvider DocumentServiceProvider { get; } + /// /// Create a new instance of a . /// - internal DocumentInfo(DocumentAttributes attributes, TextLoader loader) + internal DocumentInfo(DocumentAttributes attributes, TextLoader loader, IDocumentServiceProvider documentServiceProvider) { Attributes = attributes; TextLoader = loader; + DocumentServiceProvider = documentServiceProvider; } public static DocumentInfo Create( @@ -71,22 +78,39 @@ internal DocumentInfo(DocumentAttributes attributes, TextLoader loader) string filePath = null, bool isGenerated = false) { - return new DocumentInfo(new DocumentAttributes(id, name, folders, sourceCodeKind, filePath, isGenerated), loader); + return Create(id, name, folders, sourceCodeKind, loader, filePath, isGenerated, documentServiceProvider: null); + } + + internal static DocumentInfo Create( + DocumentId id, + string name, + IEnumerable folders, + SourceCodeKind sourceCodeKind, + TextLoader loader, + string filePath, + bool isGenerated, + IDocumentServiceProvider documentServiceProvider) + { + return new DocumentInfo(new DocumentAttributes(id, name, folders, sourceCodeKind, filePath, isGenerated), loader, documentServiceProvider); } private DocumentInfo With( DocumentAttributes attributes = null, - Optional loader = default) + Optional loader = default, + Optional documentServiceProvider = default) { var newAttributes = attributes ?? Attributes; var newLoader = loader.HasValue ? loader.Value : TextLoader; + var newDocumentServiceProvider = documentServiceProvider.HasValue ? documentServiceProvider.Value : DocumentServiceProvider; - if (newAttributes == Attributes && newLoader == TextLoader) + if (newAttributes == Attributes && + newLoader == TextLoader && + newDocumentServiceProvider == DocumentServiceProvider) { return this; } - return new DocumentInfo(newAttributes, newLoader); + return new DocumentInfo(newAttributes, newLoader, newDocumentServiceProvider); } public DocumentInfo WithId(DocumentId id) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 4edb77a917a95249303132913c088931e11831d9..3c0f2c03819f36128015cbfa50d4c2877a15a58a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -28,13 +28,14 @@ internal partial class DocumentState : TextDocumentState private DocumentState( HostLanguageServices languageServices, SolutionServices solutionServices, + IDocumentServiceProvider documentServiceProvider, DocumentInfo.DocumentAttributes attributes, ParseOptions options, SourceText sourceTextOpt, ValueSource textSource, ValueSource treeSource, ValueSource lazyChecksums) - : base(solutionServices, attributes, sourceTextOpt, textSource, lazyChecksums) + : base(solutionServices, documentServiceProvider, attributes, sourceTextOpt, textSource, lazyChecksums) { _languageServices = languageServices; _options = options; @@ -75,6 +76,7 @@ internal bool SupportsSyntaxTree return new DocumentState( languageServices: language, + documentServiceProvider: info.DocumentServiceProvider, solutionServices: services, attributes: info.Attributes, options: options, @@ -332,6 +334,7 @@ private DocumentState SetParseOptions(ParseOptions options) return new DocumentState( this.LanguageServices, this.solutionServices, + this.Services, this.Attributes.With(sourceCodeKind: options.Kind), options, this.sourceTextOpt, @@ -355,6 +358,7 @@ public DocumentState UpdateName(string name) return new DocumentState( _languageServices, this.solutionServices, + this.Services, this.Attributes.With(name: name), _options, this.sourceTextOpt, @@ -368,6 +372,7 @@ public DocumentState UpdateFolders(IList folders) return new DocumentState( _languageServices, this.solutionServices, + this.Services, this.Attributes.With(folders: folders), _options, this.sourceTextOpt, @@ -381,6 +386,7 @@ public DocumentState UpdateFilePath(string filePath) return new DocumentState( _languageServices, this.solutionServices, + this.Services, this.Attributes.With(filePath: filePath), _options, this.sourceTextOpt, @@ -428,6 +434,7 @@ public new DocumentState UpdateText(TextAndVersion newTextAndVersion, Preservati return new DocumentState( this.LanguageServices, this.solutionServices, + this.Services, this.Attributes, _options, sourceTextOpt: null, @@ -469,6 +476,7 @@ internal DocumentState UpdateText(TextLoader loader, SourceText textOpt, Preserv return new DocumentState( this.LanguageServices, this.solutionServices, + this.Services, this.Attributes, _options, sourceTextOpt: textOpt, @@ -511,6 +519,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) return new DocumentState( this.LanguageServices, this.solutionServices, + this.Services, this.Attributes, _options, sourceTextOpt: null, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocument.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocument.cs index 6913819de4e26ebdd6a8f91a8e2f5f4fb2a37940..d87f4d7afe54ad710225da1febd601d49ce306f4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocument.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocument.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -51,6 +52,11 @@ internal TextDocument(Project project, TextDocumentState state) /// public IReadOnlyList Folders => State.Folders; + /// + /// A associated with this document + /// + internal IDocumentServiceProvider Services => State.Services; + /// /// Get the current text for the document if it is already loaded and available. /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 204875cf2498a4f3343e6630aad5813ddb6a38b0..0717ea4309a5d360cd9eb269ca9d673482b11075 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -39,6 +39,7 @@ internal partial class TextDocumentState protected TextDocumentState( SolutionServices solutionServices, + IDocumentServiceProvider documentServiceProvider, DocumentInfo.DocumentAttributes attributes, SourceText sourceTextOpt, ValueSource textAndVersionSource, @@ -49,6 +50,7 @@ internal partial class TextDocumentState this.textAndVersionSource = textAndVersionSource; Attributes = attributes; + Services = documentServiceProvider ?? DefaultTextDocumentServiceProvider.Instance; // for now, let it re-calculate if anything changed. // TODO: optimize this so that we only re-calcuate checksums that are actually changed @@ -57,6 +59,11 @@ internal partial class TextDocumentState public DocumentInfo.DocumentAttributes Attributes { get; } + /// + /// A associated with this document + /// + public IDocumentServiceProvider Services { get; } + public DocumentId Id { get { return Attributes.Id; } @@ -85,6 +92,7 @@ public static TextDocumentState Create(DocumentInfo info, SolutionServices servi return new TextDocumentState( solutionServices: services, + documentServiceProvider: info.DocumentServiceProvider, attributes: info.Attributes, sourceTextOpt: null, textAndVersionSource: textSource, @@ -320,6 +328,7 @@ public TextDocumentState UpdateText(TextAndVersion newTextAndVersion, Preservati return new TextDocumentState( this.solutionServices, + this.Services, this.Attributes, sourceTextOpt: null, textAndVersionSource: newTextSource, @@ -354,6 +363,7 @@ public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode) return new TextDocumentState( this.solutionServices, + this.Services, this.Attributes, sourceTextOpt: null, textAndVersionSource: newTextSource, diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 7f025665bc253290f5de5b3b74868a2d90e06518..62a463f8d69c1904be766471cf80b8ad171113fb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -1384,7 +1384,9 @@ protected virtual void ApplyProjectChanges(ProjectChanges projectChanges) if (newDoc.HasInfoChanged(oldDoc)) { // ApplyDocumentInfoChanged ignores the loader information, so we can pass null for it - ApplyDocumentInfoChanged(documentId, new DocumentInfo(newDoc.State.Attributes, loader: null)); + ApplyDocumentInfoChanged( + documentId, + new DocumentInfo(newDoc.State.Attributes, loader: null, documentServiceProvider: newDoc.State.Services)); } // update text if changed