diff --git a/OmniSharp.sln b/OmniSharp.sln index 7ad964327d..76a7afeab5 100644 --- a/OmniSharp.sln +++ b/OmniSharp.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2005 @@ -11,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json EndProjectSection ProjectSection(FolderGlobals) = preProject - global_1json__JSONSchema = http://json.schemastore.org/global + global_1json__JSONSchema = http://json.schemastore.org/global EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35E025BF-BBB2-4FAC-9F4B-37CBA083EE47}" @@ -68,6 +69,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OmniSharp.Stdio.Driver", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OmniSharp.Script.Tests", "tests\OmniSharp.Script.Tests\OmniSharp.Script.Tests.csproj", "{9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSharp.MiscellaneousFiles", "src\OmniSharp.MiscellaneousFiles\OmniSharp.MiscellaneousFiles.csproj", "{49358F28-883B-4FA0-B853-8774A732E188}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSharp.MiscellaneousFiles.Tests", "tests\OmniSharp.MiscellaneousFiles.Tests\OmniSharp.MiscellaneousFiles.Tests.csproj", "{51F1D224-A543-48C5-BD37-139B935E71D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -390,6 +395,30 @@ Global {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}.Release|x64.Build.0 = Release|Any CPU {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}.Release|x86.ActiveCfg = Release|Any CPU {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F}.Release|x86.Build.0 = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x64.ActiveCfg = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x64.Build.0 = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x86.ActiveCfg = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Debug|x86.Build.0 = Debug|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|Any CPU.Build.0 = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x64.ActiveCfg = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x64.Build.0 = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x86.ActiveCfg = Release|Any CPU + {49358F28-883B-4FA0-B853-8774A732E188}.Release|x86.Build.0 = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x64.Build.0 = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Debug|x86.Build.0 = Debug|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|Any CPU.Build.0 = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x64.ActiveCfg = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x64.Build.0 = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x86.ActiveCfg = Release|Any CPU + {51F1D224-A543-48C5-BD37-139B935E71D8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -421,6 +450,8 @@ Global {BC640CBF-F6E2-42EA-9D61-FB6E515AEA44} = {2C348365-A9D8-459E-9276-56FC46AAEE31} {D2A78CEE-B278-476F-AF34-A7D6F792F973} = {2C348365-A9D8-459E-9276-56FC46AAEE31} {9E4BA68C-7F4B-429A-A0C7-8CE7D41D610F} = {35E025BF-BBB2-4FAC-9F4B-37CBA083EE47} + {49358F28-883B-4FA0-B853-8774A732E188} = {2C348365-A9D8-459E-9276-56FC46AAEE31} + {51F1D224-A543-48C5-BD37-139B935E71D8} = {35E025BF-BBB2-4FAC-9F4B-37CBA083EE47} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4DD725CE-B49A-4151-8B77-BB33FE88E46E} diff --git a/src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs b/src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs new file mode 100644 index 0000000000..b1736d0d1f --- /dev/null +++ b/src/OmniSharp.Abstractions/Mef/ExportProjectSystemAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Composition; +using OmniSharp.Services; + +namespace OmniSharp.Mef +{ + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ExportProjectSystemAttribute: ExportAttribute + { + public string Name { get; } + + public ExportProjectSystemAttribute(string name) : base(typeof(IProjectSystem)) + { + Name = name; + } + } +} diff --git a/src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs b/src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs new file mode 100644 index 0000000000..ef2498dea3 --- /dev/null +++ b/src/OmniSharp.Abstractions/Mef/ProjectSystemMetadata.cs @@ -0,0 +1,7 @@ +namespace OmniSharp.Mef +{ + public class ProjectSystemMetadata + { + public string Name { get; set; } + } +} diff --git a/src/OmniSharp.Abstractions/ProjectSystemNames.cs b/src/OmniSharp.Abstractions/ProjectSystemNames.cs new file mode 100644 index 0000000000..ef468dc4d2 --- /dev/null +++ b/src/OmniSharp.Abstractions/ProjectSystemNames.cs @@ -0,0 +1,11 @@ +namespace OmniSharp +{ + public static class ProjectSystemNames + { + public const string MSBuildProjectSystem = "MSBuildProjectSystem"; + public const string CakeProjectSystem = "CakeProjectSystem"; + public const string DotNetProjectSystem = "DotNetProjectSystem"; + public const string ScriptProjectSystem = "ScriptProjectSystem"; + public const string MiscellaneousFilesProjectSystem = "MiscellaneousFilesProjectSystem"; + } +} diff --git a/src/OmniSharp.Abstractions/Services/IUpdates.cs b/src/OmniSharp.Abstractions/Services/IUpdates.cs new file mode 100644 index 0000000000..4c70f27a1d --- /dev/null +++ b/src/OmniSharp.Abstractions/Services/IUpdates.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace OmniSharp.Services +{ + public interface IWaitableProjectSystem: IProjectSystem + { + Task WaitForUpdatesAsync(); + } +} diff --git a/src/OmniSharp.Cake/CakeProjectSystem.cs b/src/OmniSharp.Cake/CakeProjectSystem.cs index 28ff198a7c..63d1b60860 100644 --- a/src/OmniSharp.Cake/CakeProjectSystem.cs +++ b/src/OmniSharp.Cake/CakeProjectSystem.cs @@ -16,13 +16,14 @@ using OmniSharp.FileSystem; using OmniSharp.FileWatching; using OmniSharp.Helpers; +using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; namespace OmniSharp.Cake { - [Export(typeof(IProjectSystem)), Shared] + [ExportProjectSystem(ProjectSystemNames.CakeProjectSystem), Shared] public class CakeProjectSystem : IProjectSystem { private readonly OmniSharpWorkspace _workspace; diff --git a/src/OmniSharp.DotNet/DotNetProjectSystem.cs b/src/OmniSharp.DotNet/DotNetProjectSystem.cs index cb073018a9..cb57bb455c 100644 --- a/src/OmniSharp.DotNet/DotNetProjectSystem.cs +++ b/src/OmniSharp.DotNet/DotNetProjectSystem.cs @@ -17,14 +17,15 @@ using OmniSharp.Eventing; using OmniSharp.FileWatching; using OmniSharp.Helpers; +using OmniSharp.Mef; using OmniSharp.Models.Events; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Services; namespace OmniSharp.DotNet { - [Export(typeof(IProjectSystem)), Shared] - public class DotNetProjectSystem : IProjectSystem + [ExportProjectSystem(ProjectSystemNames.DotNetProjectSystem), Shared] + public class DotNetProjectSystem : IWaitableProjectSystem { private const string CompilationConfiguration = "Debug"; @@ -423,5 +424,10 @@ private static LanguageVersion ParseLanguageVersion(string value) return languageVersion; } + + async Task IWaitableProjectSystem.WaitForUpdatesAsync() + { + await ((IProjectSystem)this).GetWorkspaceModelAsync(new WorkspaceInformationRequest()); + } } } diff --git a/src/OmniSharp.Host/WorkspaceInitializer.cs b/src/OmniSharp.Host/WorkspaceInitializer.cs index ffe3c0b766..2e4b82f130 100644 --- a/src/OmniSharp.Host/WorkspaceInitializer.cs +++ b/src/OmniSharp.Host/WorkspaceInitializer.cs @@ -1,13 +1,16 @@ using System; using System.Composition.Hosting; +using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OmniSharp.Mef; using OmniSharp.Options; using OmniSharp.Roslyn; using OmniSharp.Roslyn.Options; using OmniSharp.Services; +using OmniSharp.Utilities; namespace OmniSharp { @@ -24,9 +27,11 @@ public static void Initialize( var projectEventForwarder = compositionHost.GetExport(); projectEventForwarder.Initialize(); + var projectSystems = compositionHost.GetExports>(); + var ps = projectSystems.Select(n => n.Value); + var orderedProjectSystems = ExtensionOrderer.GetOrderedOrUnorderedList(ps, eps => eps.Name); - // Initialize all the project systems - foreach (var projectSystem in compositionHost.GetExports()) + foreach (var projectSystem in orderedProjectSystems) { try { diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 4d432acf18..d86f0d5a72 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -10,6 +10,7 @@ using OmniSharp.Eventing; using OmniSharp.FileSystem; using OmniSharp.FileWatching; +using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.MSBuild.Discovery; using OmniSharp.MSBuild.Models; @@ -20,8 +21,8 @@ namespace OmniSharp.MSBuild { - [Export(typeof(IProjectSystem)), Shared] - public class ProjectSystem : IProjectSystem + [ExportProjectSystem(ProjectSystemNames.MSBuildProjectSystem), Shared] + public class ProjectSystem : IWaitableProjectSystem { private readonly IOmniSharpEnvironment _environment; private readonly OmniSharpWorkspace _workspace; @@ -209,5 +210,10 @@ async Task IProjectSystem.GetProjectModelAsync(string filePath) return new MSBuildProjectInfo(projectFileInfo); } + + public async Task WaitForUpdatesAsync() + { + await _manager.WaitForQueueEmptyAsync(); + } } } diff --git a/src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs b/src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs new file mode 100644 index 0000000000..054fb93707 --- /dev/null +++ b/src/OmniSharp.MiscellaneousFiles/MiscellaneousFilesProjectSystem.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using OmniSharp.FileSystem; +using OmniSharp.FileWatching; +using OmniSharp.Mef; +using OmniSharp.Models.WorkspaceInformation; +using OmniSharp.Services; + +namespace OmniSharp.MiscellaneousFile +{ + [ExtensionOrder(After = ProjectSystemNames.MSBuildProjectSystem)] + [ExtensionOrder(After = ProjectSystemNames.DotNetProjectSystem)] + [ExportProjectSystem(ProjectSystemNames.MiscellaneousFilesProjectSystem), Shared] + public class MiscellaneousFilesProjectSystem : IProjectSystem + { + private const string miscFileExtension = ".cs"; + public string Key => ProjectSystemNames.MiscellaneousFilesProjectSystem; + public string Language => LanguageNames.CSharp; + IEnumerable IProjectSystem.Extensions => new[] { miscFileExtension }; + public bool EnabledByDefault { get; } = true; + + private readonly OmniSharpWorkspace _workspace; + private readonly IFileSystemWatcher _fileSystemWatcher; + private readonly FileSystemHelper _fileSystemHelper; + private readonly List _projectSystems; + private readonly ILogger _logger; + + [ImportingConstructor] + public MiscellaneousFilesProjectSystem(OmniSharpWorkspace workspace, IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper, + ILoggerFactory loggerFactory, [ImportMany] IEnumerable> projectSystems) + { + _workspace = workspace; + _fileSystemWatcher = fileSystemWatcher; + _fileSystemHelper = fileSystemHelper; + _logger = loggerFactory.CreateLogger(); + _projectSystems = projectSystems + .Where(ps => ps.Metadata.Name == ProjectSystemNames.MSBuildProjectSystem || + ps.Metadata.Name == ProjectSystemNames.DotNetProjectSystem) + .Select(ps => ps.Value) + .Cast() + .ToList(); + } + + Task IProjectSystem.GetProjectModelAsync(string filePath) + { + return Task.FromResult(null); + } + + Task IProjectSystem.GetWorkspaceModelAsync(WorkspaceInformationRequest request) + { + return Task.FromResult(null); + } + + void IProjectSystem.Initalize(IConfiguration configuration) + { + var allFiles = _fileSystemHelper.GetFiles("**/*.cs"); + foreach (var filePath in allFiles) + TryAddMiscellaneousFile(filePath); + + _fileSystemWatcher.Watch(miscFileExtension, OnMiscellaneousFileChanged); + } + + private async void TryAddMiscellaneousFile(string filePath) + { + //wait for the project systems to finish processing the updates + foreach (var projectSystem in _projectSystems) + { + await projectSystem.WaitForUpdatesAsync(); + } + + var absoluteFilePath = new FileInfo(filePath).FullName; + if (!File.Exists(absoluteFilePath)) + return; + + if (_workspace.TryAddMiscellaneousDocument(absoluteFilePath, Language) != null) + { + _logger.LogInformation($"Successfully added file '{absoluteFilePath}' to workspace"); + } + } + + private void OnMiscellaneousFileChanged(string filePath, FileChangeType changeType) + { + if (changeType == FileChangeType.Unspecified && File.Exists(filePath) || + changeType == FileChangeType.Create) + { + TryAddMiscellaneousFile(filePath); + } + + else if (changeType == FileChangeType.Unspecified && !File.Exists(filePath) || + changeType == FileChangeType.Delete) + { + RemoveFromWorkspace(filePath); + } + } + + private void RemoveFromWorkspace(string filePath) + { + if (_workspace.TryRemoveMiscellaneousDocument(filePath)) + { + _logger.LogDebug($"Removed file '{filePath}' from the workspace."); + } + } + } +} diff --git a/src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj b/src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj new file mode 100644 index 0000000000..89bb4ca4c4 --- /dev/null +++ b/src/OmniSharp.MiscellaneousFiles/OmniSharp.MiscellaneousFiles.csproj @@ -0,0 +1,17 @@ + + + + net461 + AnyCPU + + + + + + + + + + + + diff --git a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs index a2b89de94d..94380327b6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs @@ -24,15 +24,24 @@ internal static DiagnosticLocation ToDiagnosticLocation(this Diagnostic diagnost }; } - internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents) + internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents, OmniSharpWorkspace workspace) { if (documents == null || !documents.Any()) return Enumerable.Empty(); var items = new List(); foreach (var document in documents) { - var semanticModel = await document.GetSemanticModelAsync(); - IEnumerable diagnostics = semanticModel.GetDiagnostics(); + IEnumerable diagnostics; + if (workspace.IsCapableOfSemanticDiagnostics(document)) + { + var semanticModel = await document.GetSemanticModelAsync(); + diagnostics = semanticModel.GetDiagnostics(); + } + else + { + var syntaxModel = await document.GetSyntaxTreeAsync(); + diagnostics = syntaxModel.GetDiagnostics(); + } foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index a857343f2a..4a09494f81 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -27,7 +27,7 @@ public async Task Handle(CodeCheckRequest request) ? _workspace.GetDocuments(request.FileName) : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - var quickFixes = await documents.FindDiagnosticLocationsAsync(); + var quickFixes = await documents.FindDiagnosticLocationsAsync(_workspace); return new QuickFixResponse(quickFixes); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 8c213387b8..9d74803837 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -151,48 +151,14 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl private List GetSortedCodeFixProviders() { - var nodesList = new List>(); - var providerList = new List(); - - foreach (var provider in this.Providers) - { - foreach (var codeFixProvider in provider.CodeFixProviders) - { - providerList.Add(codeFixProvider); - nodesList.Add(ProviderNode.From(codeFixProvider)); - } - } - - var graph = Graph.GetGraph(nodesList); - if (graph.HasCycles()) - { - return providerList; - } - - return graph.TopologicalSort(); + var providerList = this.Providers.SelectMany(provider => provider.CodeFixProviders); + return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } private List GetSortedCodeRefactoringProviders() { - var nodesList = new List>(); - var providerList = new List(); - - foreach (var provider in this.Providers) - { - foreach (var codeRefactoringProvider in provider.CodeRefactoringProviders) - { - providerList.Add(codeRefactoringProvider); - nodesList.Add(ProviderNode.From(codeRefactoringProvider)); - } - } - - var graph = Graph.GetGraph(nodesList); - if (graph.HasCycles()) - { - return providerList; - } - - return graph.TopologicalSort(); + var providerList = this.Providers.SelectMany(provider => provider.CodeRefactoringProviders); + return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs deleted file mode 100644 index 79c8a007c5..0000000000 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.Graph.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Adapted from ExtensionOrderer in Roslyn -using System.Collections.Generic; - -namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 -{ - internal class Graph - { - //Dictionary to map between nodes and the names - private Dictionary> Nodes { get; } - private List> AllNodes { get; } - private Graph(List> nodesList) - { - Nodes = new Dictionary>(); - AllNodes = nodesList; - } - internal static Graph GetGraph(List> nodesList) - { - var graph = new Graph(nodesList); - - foreach (ProviderNode node in graph.AllNodes) - { - graph.Nodes[node.ProviderName] = node; - } - - foreach (ProviderNode node in graph.AllNodes) - { - foreach (var before in node.Before) - { - if (graph.Nodes.ContainsKey(before)) - { - var beforeNode = graph.Nodes[before]; - beforeNode.NodesBeforeMeSet.Add(node); - } - } - - foreach (var after in node.After) - { - if (graph.Nodes.ContainsKey(after)) - { - var afterNode = graph.Nodes[after]; - node.NodesBeforeMeSet.Add(afterNode); - } - } - } - - return graph; - } - - public bool HasCycles() - { - foreach (var node in this.AllNodes) - { - if (node.CheckForCycles()) - return true; - } - return false; - } - - public List TopologicalSort() - { - List result = new List(); - var seenNodes = new HashSet>(); - - foreach (var node in AllNodes) - { - Visit(node, result, seenNodes); - } - - return result; - } - - private void Visit(ProviderNode node, List result, HashSet> seenNodes) - { - if (seenNodes.Add(node)) - { - foreach (var before in node.NodesBeforeMeSet) - { - Visit(before, result, seenNodes); - } - - result.Add(node.Provider); - } - } - } -} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs deleted file mode 100644 index 92be73ac8a..0000000000 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeActionsOrder.ProviderNode.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Adapted from ExtensionOrderer in Roslyn -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; - -namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 -{ - internal class ProviderNode - { - public string ProviderName { get; set; } - public List Before { get; set; } - public List After { get; set; } - public TProvider Provider { get; set; } - public HashSet> NodesBeforeMeSet { get; set; } - - public static ProviderNode From(TProvider provider) - { - string providerName = ""; - if (provider is CodeFixProvider) - { - var exportAttribute = provider.GetType().GetCustomAttribute(typeof(ExportCodeFixProviderAttribute)); - if (exportAttribute is ExportCodeFixProviderAttribute fixAttribute && fixAttribute.Name != null) - { - providerName = fixAttribute.Name; - } - } - else - { - var exportAttribute = provider.GetType().GetCustomAttribute(typeof(ExportCodeRefactoringProviderAttribute)); - if (exportAttribute is ExportCodeRefactoringProviderAttribute refactoringAttribute && refactoringAttribute.Name != null) - { - providerName = refactoringAttribute.Name; - } - } - - var orderAttributes = provider.GetType().GetCustomAttributes(typeof(ExtensionOrderAttribute), true).Select(attr => (ExtensionOrderAttribute)attr).ToList(); - return new ProviderNode(provider, providerName, orderAttributes); - } - - private ProviderNode(TProvider provider, string providerName, List orderAttributes) - { - Provider = provider; - ProviderName = providerName; - Before = new List(); - After = new List(); - NodesBeforeMeSet = new HashSet>(); - orderAttributes.ForEach(attr => AddAttribute(attr)); - } - - private void AddAttribute(ExtensionOrderAttribute attribute) - { - if (attribute.Before != null) - Before.Add(attribute.Before); - if (attribute.After != null) - After.Add(attribute.After); - } - - internal bool CheckForCycles() - { - return CheckForCycles(new HashSet>()); - } - - private bool CheckForCycles(HashSet> seenNodes) - { - if (!seenNodes.Add(this)) - { - //Cycle detected - return true; - } - - foreach (var before in this.NodesBeforeMeSet) - { - if (before.CheckForCycles(seenNodes)) - return true; - } - - seenNodes.Remove(this); - return false; - } - } -} diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 35fc89fe39..03e733f669 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -140,7 +140,7 @@ private async Task Dequeue() private async Task ProcessNextItem(string filePath) { var documents = _workspace.GetDocuments(filePath); - var items = await documents.FindDiagnosticLocationsAsync(); + var items = await documents.FindDiagnosticLocationsAsync(_workspace); return new DiagnosticResult() { diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index d0e371ca5e..495f88a6ca 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Composition; using System.IO; @@ -9,6 +10,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using OmniSharp.Roslyn; +using OmniSharp.Roslyn.Utilities; using OmniSharp.Utilities; namespace OmniSharp @@ -21,6 +23,8 @@ public class OmniSharpWorkspace : Workspace private readonly ILogger _logger; + private readonly ConcurrentDictionary miscDocumentsProjectInfos = new ConcurrentDictionary(); + [ImportingConstructor] public OmniSharpWorkspace(HostServicesAggregator aggregator, ILoggerFactory loggerFactory) : base(aggregator.CreateHostServices(), "Custom") @@ -80,9 +84,50 @@ public void RemoveMetadataReference(ProjectId projectId, MetadataReference metad public void AddDocument(DocumentInfo documentInfo) { + // if the file has already been added as a misc file, + // because of a possible race condition between the updates of the project systems, + // remove the misc file and add the document as required + TryRemoveMiscellaneousDocument(documentInfo.FilePath); + OnDocumentAdded(documentInfo); } + public DocumentId TryAddMiscellaneousDocument(string filePath, string language) + { + if (GetDocument(filePath) != null) + return null; //if the workspace already knows about this document then it is not a miscellaneous document + + var projectInfo = miscDocumentsProjectInfos.GetOrAdd(language, CreateMiscFilesProject(language)); + var documentId = AddDocument(projectInfo.Id, filePath); + return documentId; + } + + public bool TryRemoveMiscellaneousDocument(string filePath) + { + var documentId = GetDocumentId(filePath); + if (documentId == null || !IsMiscellaneousDocument(documentId)) + return false; + + RemoveDocument(documentId); + return true; + } + + private ProjectInfo CreateMiscFilesProject(string language) + { + string assemblyName = Guid.NewGuid().ToString("N"); + var projectInfo = ProjectInfo.Create( + id: ProjectId.CreateNewId(), + version: VersionStamp.Create(), + name: "MiscellaneousFiles", + metadataReferences: DefaultMetadataReferenceHelper.GetDefaultMetadataReferenceLocations() + .Select(loc => MetadataReference.CreateFromFile(loc)), + assemblyName: assemblyName, + language: language); + + AddProject(projectInfo); + return projectInfo; + } + public DocumentId AddDocument(ProjectId projectId, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { var documentId = DocumentId.CreateNewId(projectId); @@ -224,5 +269,15 @@ private void SaveDocumentText(DocumentId id, string fullPath, SourceText newText _logger.LogError(e, $"Error saving document {fullPath}"); } } + + public bool IsCapableOfSemanticDiagnostics(Document document) + { + return !IsMiscellaneousDocument(document.Id); + } + + private bool IsMiscellaneousDocument(DocumentId documentId) + { + return miscDocumentsProjectInfos.Where(p => p.Value.Id == documentId.ProjectId).Any(); + } } } diff --git a/src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs b/src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs new file mode 100644 index 0000000000..7c4ce122ec --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/DefaultMetadataReferencesHelper.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace OmniSharp.Roslyn.Utilities +{ + public static class DefaultMetadataReferenceHelper + { + public static IEnumerable GetDefaultMetadataReferenceLocations() + { + var assemblies = new[] + { + typeof(object).GetTypeInfo().Assembly, + typeof(Enumerable).GetTypeInfo().Assembly, + typeof(Stack<>).GetTypeInfo().Assembly, + typeof(Lazy<,>).GetTypeInfo().Assembly, + FromName("System.Runtime"), + FromName("mscorlib") + }; + + return assemblies + .Where(a => a != null) + .Select(a => a.Location) + .Distinct(); + + Assembly FromName(string assemblyName) + { + try + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + catch + { + return null; + } + } + } + } +} diff --git a/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs new file mode 100644 index 0000000000..4abd245418 --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Graph.cs @@ -0,0 +1,88 @@ +// Adapted from ExtensionOrderer in Roslyn +using System.Collections.Generic; + +namespace OmniSharp.Utilities +{ + static partial class ExtensionOrderer + { + internal class Graph + { + //Dictionary to map between nodes and the names + private Dictionary> Nodes { get; } + private List> AllNodes { get; } + private Graph(List> nodesList) + { + Nodes = new Dictionary>(); + AllNodes = nodesList; + } + internal static Graph GetGraph(List> nodesList) + { + var graph = new Graph(nodesList); + + foreach (Node node in graph.AllNodes) + { + graph.Nodes[node.Name] = node; + } + + foreach (Node node in graph.AllNodes) + { + foreach (var before in node.Before) + { + if (graph.Nodes.ContainsKey(before)) + { + var beforeNode = graph.Nodes[before]; + beforeNode.NodesBeforeMeSet.Add(node); + } + } + + foreach (var after in node.After) + { + if (graph.Nodes.ContainsKey(after)) + { + var afterNode = graph.Nodes[after]; + node.NodesBeforeMeSet.Add(afterNode); + } + } + } + + return graph; + } + + public bool HasCycles() + { + foreach (var node in this.AllNodes) + { + if (node.CheckForCycles()) + return true; + } + return false; + } + + public List TopologicalSort() + { + List result = new List(); + var seenNodes = new HashSet>(); + + foreach (var node in AllNodes) + { + Visit(node, result, seenNodes); + } + + return result; + } + + private void Visit(Node node, List result, HashSet> seenNodes) + { + if (seenNodes.Add(node)) + { + foreach (var before in node.NodesBeforeMeSet) + { + Visit(before, result, seenNodes); + } + + result.Add(node.Extension); + } + } + } + } +} diff --git a/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs new file mode 100644 index 0000000000..383570f8f5 --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.Node.cs @@ -0,0 +1,76 @@ +// Adapted from ExtensionOrderer in Roslyn +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace OmniSharp.Utilities +{ + static partial class ExtensionOrderer + { + internal class Node + { + public string Name { get; set; } + public List Before { get; set; } + public List After { get; set; } + public TNode Extension { get; set; } + public HashSet> NodesBeforeMeSet { get; set; } + + public static Node From(TNode extension, Func nameExtractor) where TNodeAttribute : Attribute + { + string name = string.Empty; + var attribute = extension.GetType().GetCustomAttribute(); + if (attribute is TNodeAttribute && !string.IsNullOrEmpty(nameExtractor(attribute))) + { + name = nameExtractor(attribute); + } + var orderAttributes = extension.GetType().GetCustomAttributes(true); + return new Node(extension, name, orderAttributes); + } + + private Node(TNode extension, string name, IEnumerable orderAttributes) + { + Extension = extension; + Name = name; + Before = new List(); + After = new List(); + NodesBeforeMeSet = new HashSet>(); + foreach (var attribute in orderAttributes) + { + AddAttribute(attribute); + } + } + + private void AddAttribute(ExtensionOrderAttribute attribute) + { + if (attribute.Before != null) + Before.Add(attribute.Before); + if (attribute.After != null) + After.Add(attribute.After); + } + + internal bool CheckForCycles() + { + return CheckForCycles(new HashSet>()); + } + + private bool CheckForCycles(HashSet> seenNodes) + { + if (!seenNodes.Add(this)) + { + //Cycle detected + return true; + } + + foreach (var before in this.NodesBeforeMeSet) + { + if (before.CheckForCycles(seenNodes)) + return true; + } + + seenNodes.Remove(this); + return false; + } + } + } +} diff --git a/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs new file mode 100644 index 0000000000..dfc39e9f09 --- /dev/null +++ b/src/OmniSharp.Roslyn/Utilities/ExtensionOrderer.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OmniSharp.Utilities +{ + public static partial class ExtensionOrderer + { + /* Returns a sorted order of the nodes if such a sorting exists, else returns the unsorted list */ + public static IEnumerable GetOrderedOrUnorderedList(IEnumerable unsortedList, Func nameExtractor) where TAttribute: Attribute + { + var nodesList = unsortedList.Select(elem => Node.From(elem, nameExtractor)); + var graph = Graph.GetGraph(nodesList.ToList()); + return graph.HasCycles() ? unsortedList : graph.TopologicalSort(); + } + } +} diff --git a/src/OmniSharp.Script/ScriptContextProvider.cs b/src/OmniSharp.Script/ScriptContextProvider.cs index a97cd2d648..95b5967f9d 100644 --- a/src/OmniSharp.Script/ScriptContextProvider.cs +++ b/src/OmniSharp.Script/ScriptContextProvider.cs @@ -117,20 +117,7 @@ public ScriptContext CreateScriptContext(ScriptOptions scriptOptions) private void AddDefaultClrMetadataReferences(HashSet commonReferences, HashSet assemblyReferences) { - var assemblies = new[] - { - typeof(object).GetTypeInfo().Assembly, - typeof(Enumerable).GetTypeInfo().Assembly, - typeof(Stack<>).GetTypeInfo().Assembly, - typeof(Lazy<,>).GetTypeInfo().Assembly, - FromName("System.Runtime"), - FromName("mscorlib") - }; - - var references = assemblies - .Where(a => a != null) - .Select(a => a.Location) - .Distinct() + var references = DefaultMetadataReferenceHelper.GetDefaultMetadataReferenceLocations() .Select(l => { assemblyReferences.Add(l); @@ -141,18 +128,6 @@ private void AddDefaultClrMetadataReferences(HashSet commonRe { commonReferences.Add(reference); } - - Assembly FromName(string assemblyName) - { - try - { - return Assembly.Load(new AssemblyName(assemblyName)); - } - catch - { - return null; - } - } } private void AddMetadataReference(ISet referenceCollection, HashSet assemblyReferences, string fileReference) diff --git a/src/OmniSharp.Script/ScriptProjectSystem.cs b/src/OmniSharp.Script/ScriptProjectSystem.cs index 1bf3dacb94..d4b7dd9633 100644 --- a/src/OmniSharp.Script/ScriptProjectSystem.cs +++ b/src/OmniSharp.Script/ScriptProjectSystem.cs @@ -12,12 +12,13 @@ using Microsoft.Extensions.Logging; using OmniSharp.FileSystem; using OmniSharp.FileWatching; +using OmniSharp.Mef; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.Services; namespace OmniSharp.Script { - [Export(typeof(IProjectSystem)), Shared] + [ExportProjectSystem(ProjectSystemNames.ScriptProjectSystem), Shared] public class ScriptProjectSystem : IProjectSystem { private const string CsxExtension = ".csx"; diff --git a/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj b/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj index 29ee1a386c..6b7ca88600 100644 --- a/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj +++ b/src/OmniSharp.Stdio/OmniSharp.Stdio.csproj @@ -8,6 +8,7 @@ + diff --git a/test-assets/test-projects/EmptyProject/README.txt b/test-assets/test-projects/EmptyProject/README.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs b/tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs new file mode 100644 index 0000000000..9933b8fd08 --- /dev/null +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs b/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs new file mode 100644 index 0000000000..9fd6d7647a --- /dev/null +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs @@ -0,0 +1,376 @@ +using System.Linq; +using System.Threading.Tasks; +using OmniSharp.Models; +using OmniSharp.Models.CodeCheck; +using OmniSharp.Models.FilesChanged; +using OmniSharp.Models.FindImplementations; +using OmniSharp.Models.FindSymbols; +using OmniSharp.Models.FindUsages; +using OmniSharp.Models.FixUsings; +using OmniSharp.Models.SignatureHelp; +using OmniSharp.Models.TypeLookup; +using OmniSharp.Roslyn.CSharp.Services.Files; +using OmniSharp.Roslyn.CSharp.Services.Types; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.MiscellaneousFiles.Tests +{ + public class EndpointFacts : AbstractTestFixture + { + public EndpointFacts(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public async Task Returns_only_syntactic_diagnotics() + { + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + var testfile = new TestFile("a.cs", "class C { b a = new b(); int n }"); + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var request = new CodeCheckRequest() { FileName = filePath }; + var actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); + Assert.Single(actual.QuickFixes); + Assert.Equal("; expected", actual.QuickFixes.First().Text); + } + } + } + + [Fact] + public async Task Returns_Signature_help() + { + const string source = +@"class Program +{ + public static void Main(){ + System.Guid.NewGuid($$); + } +}"; + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var point = testfile.Content.GetPointFromPosition(); + var request = new SignatureHelpRequest() + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.SignatureHelp, request); + Assert.Single(actual.Signatures); + Assert.Equal(0, actual.ActiveParameter); + Assert.Equal(0, actual.ActiveSignature); + Assert.Equal("NewGuid", actual.Signatures.ElementAt(0).Name); + Assert.Empty(actual.Signatures.ElementAt(0).Parameters); + } + } + } + + [Fact] + public async Task Returns_Implementations() + { + const string source = @" + public class MyClass + { + public MyClass() { Fo$$o(); } + + public void Foo() {} + }"; + + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var point = testfile.Content.GetPointFromPosition(); + var request = new FindImplementationsRequest() + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.FindImplementations, request); + Assert.Single(actual.QuickFixes); + Assert.Equal("public void Foo() {}", actual.QuickFixes.First().Text.Trim()); + } + } + } + + [Fact] + public async Task Returns_Usages() + { + const string source = @" + public class F$$oo + { + public string prop { get; set; } + } + + public class FooConsumer + { + public FooConsumer() + { + var temp = new Foo(); + var prop = foo.prop; + } + }"; + + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + + var point = testfile.Content.GetPointFromPosition(); + var request = new FindUsagesRequest() + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.FindUsages, request); + Assert.Equal(2, actual.QuickFixes.Count()); + } + } + } + + [Fact] + public async Task Returns_Symbols() + { + const string source = @" + namespace Some.Long.Namespace + { + public class Foo + { + private string _field = 0; + private string AutoProperty { get; } + private string Property + { + get { return _field; } + set { _field = value; } + } + private string Method() {} + private string Method(string param) {} + + private class Nested + { + private string NestedMethod() {} + } + } + }"; + + var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var actual = await host.GetResponse(OmniSharpEndpoints.FindSymbols, null); + var symbols = actual.QuickFixes.Select(q => q.Text); + + var expected = new[] + { + "Foo", + "_field", + "AutoProperty", + "Property", + "Method()", + "Method(string param)", + "Nested", + "NestedMethod()" + }; + + Assert.Equal(expected, symbols); + } + } + } + + [Fact] + public async Task Returns_FixUsings() + { + const string code = @" +namespace nsA +{ + public class classX{} +} + +namespace OmniSharp +{ + public class class1 + { + public method1() + { + var c1 = new classX(); + } + } +}"; + + const string expectedCode = @" +using nsA; + +namespace nsA +{ + public class classX{} +} + +namespace OmniSharp +{ + public class class1 + { + public method1() + { + var c1 = new classX(); + } + } +}"; + + var testfile = new TestFile("a.cs", code); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var request = new FixUsingsRequest(){ FileName = filePath }; + var actual = await host.GetResponse(OmniSharpEndpoints.FixUsings, request); + Assert.Equal(expectedCode.Replace("\r\n", "\n"), actual.Buffer.Replace("\r\n", "\n")); + } + } + } + + [Fact] + public async Task Returns_TypeLookup() + { + const string code = @"class F$$oo {}"; + var testfile = new TestFile("a.cs", code); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var service = host.GetRequestHandler(OmniSharpEndpoints.TypeLookup); + var point = testfile.Content.GetPointFromPosition(); + var request = new TypeLookupRequest + { + FileName = filePath, + Line = point.Line, + Column = point.Offset, + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.TypeLookup, request); + Assert.Equal("Foo", actual.Type); + } + } + } + + [Fact] + public async Task Adds_Multiple_Misc_Files_To_Same_project() + { + const string source1 = +@"class Program +{ + public static void Main(){ + A a = new A(4, $$5); + } +}"; + + const string source2 = +@"class A +{ + A(int a, int b) + { + } +}"; + var testfile1 = new TestFile("file1.cs", source1); + var testfile2 = new TestFile("file2.cs", source2); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath1 = AddTestFile(testProject, testfile1); + var filePath2 = AddTestFile(testProject, testfile2); + await WaitForFileUpdate(filePath1, host); + await WaitForFileUpdate(filePath2, host); + var point = testfile1.Content.GetPointFromPosition(); + var request = new SignatureHelpRequest() + { + FileName = filePath1, + Line = point.Line, + Column = point.Offset, + Buffer = testfile1.Content.Code + }; + + var actual = await host.GetResponse(OmniSharpEndpoints.SignatureHelp, request); + Assert.Single(actual.Signatures); + Assert.Equal(1, actual.ActiveParameter); + Assert.Equal(0, actual.ActiveSignature); + Assert.Equal("A", actual.Signatures.ElementAt(0).Name); + Assert.Equal(2, actual.Signatures.ElementAt(0).Parameters.Count()); + } + } + } + + [Fact] + public async Task Handles_File_Deletion() + { + //When the file is deleted the diagnostics must not be returned + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + { + var testfile = new TestFile("a.cs", "class C { b a = new b(); int n }"); + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var filePath = AddTestFile(testProject, testfile); + await WaitForFileUpdate(filePath, host); + var request = new CodeCheckRequest() { FileName = filePath }; + var actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); + Assert.Single(actual.QuickFixes); + + await WaitForFileUpdate(filePath, host, FileWatching.FileChangeType.Delete); + actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); + Assert.Empty(actual.QuickFixes); + } + } + } + + private string AddTestFile(ITestProject testProject, TestFile testfile) + { + return testProject.AddDisposableFile(testfile.FileName, testfile.Content.Text.ToString()); + } + + private async Task WaitForFileUpdate(string filePath, OmniSharpTestHost host, FileWatching.FileChangeType changeType = FileWatching.FileChangeType.Create) + { + var fileChangedService = host.GetRequestHandler(OmniSharpEndpoints.FilesChanged); + await fileChangedService.Handle(new[] + { + new FilesChangedRequest + { + FileName = filePath, + ChangeType = changeType + } + }); + + await Task.Delay(2000); + } + } +} diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj b/tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj new file mode 100644 index 0000000000..7837179d01 --- /dev/null +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/OmniSharp.MiscellaneousFiles.Tests.csproj @@ -0,0 +1,27 @@ + + + + net461 + AnyCPU + true + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 22ad764a3b..fb78cc746f 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ using OmniSharp.DotNetTest.Models; using OmniSharp.Eventing; using OmniSharp.Mef; +using OmniSharp.MiscellaneousFile; using OmniSharp.Models.WorkspaceInformation; using OmniSharp.MSBuild; using OmniSharp.Options; @@ -40,6 +42,7 @@ public class OmniSharpTestHost : DisposableObject typeof(OmniSharpWorkspace).GetTypeInfo().Assembly, // OmniSharp.Roslyn typeof(RoslynFeaturesHostServicesProvider).GetTypeInfo().Assembly, // OmniSharp.Roslyn.CSharp typeof(CakeProjectSystem).GetTypeInfo().Assembly, // OmniSharp.Cake + typeof(MiscellaneousFilesProjectSystem).GetTypeInfo().Assembly // OmniSharp.MiscellanousFiles }); private readonly TestServiceProvider _serviceProvider; @@ -214,5 +217,12 @@ public void ClearWorkspace() Workspace.RemoveProject(projectId); } } + + public Task GetResponse( + string endpoint, TRequest request) + { + var service = GetRequestHandler>(endpoint); + return service.Handle(request); + } } } diff --git a/tests/TestUtility/TestUtility.csproj b/tests/TestUtility/TestUtility.csproj index 14de90b483..ede88fae3d 100644 --- a/tests/TestUtility/TestUtility.csproj +++ b/tests/TestUtility/TestUtility.csproj @@ -9,6 +9,7 @@ +