// 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 System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.GoToDefinition; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Undo; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices { [Export(typeof(VisualStudioWorkspace))] [Export(typeof(VisualStudioWorkspaceImpl))] internal class RoslynVisualStudioWorkspace : VisualStudioWorkspaceImpl { private readonly IEnumerable> _streamingPresenters; [ImportingConstructor] private RoslynVisualStudioWorkspace( ExportProvider exportProvider, [ImportMany] IEnumerable> streamingPresenters, [ImportMany] IEnumerable documentOptionsProviderFactories) : base(exportProvider.AsExportProvider()) { _streamingPresenters = streamingPresenters; foreach (var providerFactory in documentOptionsProviderFactories) { Services.GetRequiredService().RegisterDocumentOptionsProvider(providerFactory.Create(this)); } } public override EnvDTE.FileCodeModel GetFileCodeModel(DocumentId documentId) { if (documentId == null) { throw new ArgumentNullException(nameof(documentId)); } if (DeferredState == null) { // We haven't gotten any projects added yet, so we don't know where this came from throw new ArgumentException(ServicesVSResources.The_given_DocumentId_did_not_come_from_the_Visual_Studio_workspace, nameof(documentId)); } var project = DeferredState.ProjectTracker.GetProject(documentId.ProjectId); if (project == null) { throw new ArgumentException(ServicesVSResources.The_given_DocumentId_did_not_come_from_the_Visual_Studio_workspace, nameof(documentId)); } var document = project.GetDocumentOrAdditionalDocument(documentId); if (document == null) { throw new ArgumentException(ServicesVSResources.The_given_DocumentId_did_not_come_from_the_Visual_Studio_workspace, nameof(documentId)); } var provider = project as IProjectCodeModelProvider; if (provider != null) { var projectCodeModel = provider.ProjectCodeModel; if (projectCodeModel.CanCreateFileCodeModelThroughProject(document.FilePath)) { return (EnvDTE.FileCodeModel)projectCodeModel.CreateFileCodeModelThroughProject(document.FilePath); } } return null; } internal override bool RenameFileCodeModelInstance(DocumentId documentId, string newFilePath) { if (documentId == null) { return false; } var project = DeferredState.ProjectTracker.GetProject(documentId.ProjectId); if (project == null) { return false; } var document = project.GetDocumentOrAdditionalDocument(documentId); if (document == null) { return false; } var codeModelProvider = project as IProjectCodeModelProvider; if (codeModelProvider == null) { return false; } var codeModelCache = codeModelProvider.ProjectCodeModel.GetCodeModelCache(); if (codeModelCache == null) { return false; } codeModelCache.OnSourceFileRenaming(document.FilePath, newFilePath); return true; } internal override IInvisibleEditor OpenInvisibleEditor(DocumentId documentId) { var hostDocument = GetHostDocument(documentId); return OpenInvisibleEditor(hostDocument); } internal override IInvisibleEditor OpenInvisibleEditor(IVisualStudioHostDocument hostDocument) { var globalUndoService = this.Services.GetService(); var needsUndoDisabled = false; // Do not save the file if is open and there is not a global undo transaction. var needsSave = globalUndoService.IsGlobalTransactionOpen(this) || !hostDocument.IsOpen; if (needsSave) { if (this.CurrentSolution.ContainsDocument(hostDocument.Id)) { // Disable undo on generated documents needsUndoDisabled = this.CurrentSolution.GetDocument(hostDocument.Id).IsGeneratedCode(CancellationToken.None); } else { // Enable undo on "additional documents" or if no document can be found. needsUndoDisabled = false; } } return new InvisibleEditor(DeferredState.ServiceProvider, hostDocument.FilePath, hostDocument.Project, needsSave, needsUndoDisabled); } private static bool TryResolveSymbol(ISymbol symbol, Project project, CancellationToken cancellationToken, out ISymbol resolvedSymbol, out Project resolvedProject) { resolvedSymbol = null; resolvedProject = null; var currentProject = project.Solution.Workspace.CurrentSolution.GetProject(project.Id); if (currentProject == null) { return false; } var originalCompilation = project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); var symbolId = SymbolKey.Create(symbol, cancellationToken); var currentCompilation = currentProject.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); var symbolInfo = symbolId.Resolve(currentCompilation, cancellationToken: cancellationToken); if (symbolInfo.Symbol == null) { return false; } resolvedSymbol = symbolInfo.Symbol; resolvedProject = currentProject; return true; } public override bool TryGoToDefinition( ISymbol symbol, Project project, CancellationToken cancellationToken) { if (!_streamingPresenters.Any()) { return false; } if (!TryResolveSymbol(symbol, project, cancellationToken, out var searchSymbol, out var searchProject)) { return false; } return GoToDefinitionHelpers.TryGoToDefinition( searchSymbol, searchProject, _streamingPresenters, cancellationToken); } public override bool TryFindAllReferences(ISymbol symbol, Project project, CancellationToken cancellationToken) { // Legacy API. Previously used by ObjectBrowser to support 'FindRefs' off of an // object browser item. Now ObjectBrowser goes through the streaming-FindRefs system. return false; } public override void DisplayReferencedSymbols(Solution solution, IEnumerable referencedSymbols) { // Legacy API. Previously used by ObjectBrowser to support 'FindRefs' off of an // object browser item. Now ObjectBrowser goes through the streaming-FindRefs system. } internal override object GetBrowseObject(SymbolListItem symbolListItem) { var compilation = symbolListItem.GetCompilation(this); if (compilation == null) { return null; } var symbol = symbolListItem.ResolveSymbol(compilation); var sourceLocation = symbol.Locations.Where(l => l.IsInSource).FirstOrDefault(); if (sourceLocation == null) { return null; } var projectId = symbolListItem.ProjectId; if (projectId == null) { return null; } var project = this.CurrentSolution.GetProject(projectId); if (project == null) { return null; } var codeModelService = project.LanguageServices.GetService(); if (codeModelService == null) { return null; } var tree = sourceLocation.SourceTree; var document = project.GetDocument(tree); var vsFileCodeModel = this.GetFileCodeModel(document.Id); var fileCodeModel = ComAggregate.GetManagedObject(vsFileCodeModel); if (fileCodeModel != null) { var syntaxNode = tree.GetRoot().FindNode(sourceLocation.SourceSpan); while (syntaxNode != null) { if (!codeModelService.TryGetNodeKey(syntaxNode).IsEmpty) { break; } syntaxNode = syntaxNode.Parent; } if (syntaxNode != null) { var codeElement = fileCodeModel.GetOrCreateCodeElement(syntaxNode); if (codeElement != null) { return codeElement; } } } return null; } } }