diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/IDocumentServiceFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/IDocumentServiceFactory.cs
new file mode 100644
index 0000000000..fd8253234c
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/IDocumentServiceFactory.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until this gets merged into Roslyn
+#if DOCUMENT_SERVICE_FACTORY
+namespace Microsoft.CodeAnalysis.Experiment
+{
+ public interface IDocumentServiceFactory
+ {
+ }
+}
+#endif
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/ISpanMapper.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/ISpanMapper.cs
new file mode 100644
index 0000000000..6d3c276116
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/ISpanMapper.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until this gets merged into Roslyn
+#if DOCUMENT_SERVICE_FACTORY
+namespace Microsoft.CodeAnalysis.Experiment
+{
+ public interface ISpanMapper
+ {
+ }
+}
+#endif
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs
new file mode 100644
index 0000000000..531f6adead
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Experiment/SpanMapResult.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until this gets merged into Roslyn
+#if DOCUMENT_SERVICE_FACTORY
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Experiment
+{
+ public class SpanMapResult
+ {
+ public SpanMapResult(Document document, LinePositionSpan linePositionSpan)
+ {
+
+ }
+
+ }
+}
+#endif
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj
index e7f29a2fb0..03e0c91ef3 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Microsoft.CodeAnalysis.Razor.Workspaces.csproj
@@ -4,6 +4,7 @@
Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure.
net46;netstandard2.0
false
+ $(DefineConstants);DOCUMENT_SERVICE_FACTORY
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs
index d1fb05b67a..681ce5fd9a 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs
@@ -65,6 +65,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public HostWorkspaceServices Services { get; }
+ public GeneratedCodeContainer GeneratedCodeContainer => HostDocument.GeneratedCodeContainer;
+
public DocumentGeneratedOutputTracker GeneratedOutput
{
get
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
new file mode 100644
index 0000000000..342da76b09
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
@@ -0,0 +1,143 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.Experiment;
+using Microsoft.AspNetCore.Razor.Language;
+using System.Collections.Immutable;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class GeneratedCodeContainer : IDocumentServiceFactory, ISpanMapper
+ {
+ private readonly TextContainer _textContainer;
+
+ public GeneratedCodeContainer()
+ {
+ _textContainer = new TextContainer();
+ }
+
+ public SourceText Source { get; private set; }
+
+ public VersionStamp SourceVersion { get; private set; }
+
+ public RazorCSharpDocument Output { get; private set; }
+
+ public SourceTextContainer SourceTextContainer => _textContainer;
+
+ public TService GetService()
+ {
+ if (this is TService service)
+ {
+ return service;
+ }
+
+ return default(TService);
+ }
+
+ public void SetOutput(SourceText source, RazorCodeDocument codeDocument)
+ {
+ Source = source;
+ Output = codeDocument.GetCSharpDocument();
+
+ _textContainer.SetText(SourceText.From(Output.GeneratedCode));
+ }
+
+ public Task> MapSpansAsync(
+ Document document,
+ IEnumerable spans,
+ CancellationToken cancellationToken)
+ {
+ if (Output == null)
+ {
+ return Task.FromResult(ImmutableArray.Empty);
+ }
+
+ var results = ImmutableArray.CreateBuilder();
+ foreach (var span in spans)
+ {
+ if (TryGetLinePositionSpan(span, out var linePositionSpan))
+ {
+ results.Add(new SpanMapResult(document, linePositionSpan));
+ }
+ }
+
+ return Task.FromResult(results.ToImmutable());
+ }
+
+ // Internal for testing.
+ internal bool TryGetLinePositionSpan(TextSpan span, out LinePositionSpan linePositionSpan)
+ {
+ for (var i = 0; i < Output.SourceMappings.Count; i++)
+ {
+ var mapping = Output.SourceMappings[i];
+ if (span.Length > mapping.GeneratedSpan.Length)
+ {
+ // If the length of the generated span is smaller they can't match. A C# expression
+ // won't cover multiple generated spans.
+ //
+ // This heuristic is useful in the Razor context to filter out zero-length
+ // spans.
+ continue;
+ }
+
+ var original = mapping.OriginalSpan.AsTextSpan();
+ var generated = mapping.GeneratedSpan.AsTextSpan();
+
+ var leftOffset = span.Start - generated.Start;
+ var rightOffset = span.End - generated.End;
+ if (leftOffset >= 0 && rightOffset <= 0)
+ {
+ // This span mapping contains the span.
+ var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset));
+ linePositionSpan = Source.Lines.GetLinePositionSpan(adjusted);
+ return true;
+ }
+ }
+
+ linePositionSpan = default;
+ return false;
+ }
+
+ private class TextContainer : SourceTextContainer
+ {
+ public override event EventHandler TextChanged;
+
+ private SourceText _currentText;
+
+ public TextContainer()
+ : this(SourceText.From(string.Empty))
+ {
+ }
+
+ public TextContainer(SourceText sourceText)
+ {
+ if (sourceText == null)
+ {
+ throw new ArgumentNullException(nameof(sourceText));
+ }
+
+ _currentText = sourceText;
+ }
+
+ public override SourceText CurrentText => _currentText;
+
+ public void SetText(SourceText sourceText)
+ {
+ if (sourceText == null)
+ {
+ throw new ArgumentNullException(nameof(sourceText));
+ }
+
+ var e = new TextChangeEventArgs(_currentText, sourceText);
+ _currentText = sourceText;
+
+ TextChanged?.Invoke(this, e);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs
index 859cc0df32..77d62eb854 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostDocument.cs
@@ -21,10 +21,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
FilePath = filePath;
TargetPath = targetPath;
+ GeneratedCodeContainer = new GeneratedCodeContainer();
}
public string FilePath { get; }
public string TargetPath { get; }
+
+ public GeneratedCodeContainer GeneratedCodeContainer { get; }
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor/SourceSpanExtensions.cs b/src/Microsoft.CodeAnalysis.Razor/SourceSpanExtensions.cs
new file mode 100644
index 0000000000..51fd517024
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/SourceSpanExtensions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal static class SourceSpanExtensions
+ {
+ public static TextSpan AsTextSpan(this SourceSpan sourceSpan)
+ {
+ return new TextSpan(sourceSpan.AbsoluteIndex, sourceSpan.Length);
+ }
+ }
+}
diff --git a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Microsoft.NET.Sdk.Razor.DesignTime.targets b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Microsoft.NET.Sdk.Razor.DesignTime.targets
index 64c5d8f61a..9aaafbd17c 100644
--- a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Microsoft.NET.Sdk.Razor.DesignTime.targets
+++ b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Microsoft.NET.Sdk.Razor.DesignTime.targets
@@ -43,6 +43,9 @@ Copyright (c) .NET Foundation. All rights reserved.
Project
+
+ Project
+
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
similarity index 76%
rename from src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs
rename to src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
index 122e577abe..b0a99e8e24 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentGenerator/BackgroundDocumentGenerator.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
@@ -11,14 +11,13 @@ using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
- // Deliberately not exported for now, until this feature is working end to end.
- // [Export(typeof(ProjectSnapshotChangeTrigger))]
+ [Export(typeof(ProjectSnapshotChangeTrigger))]
internal class BackgroundDocumentGenerator : ProjectSnapshotChangeTrigger
{
- private ForegroundDispatcher _foregroundDispatcher;
+ private readonly ForegroundDispatcher _foregroundDispatcher;
private ProjectSnapshotManagerBase _projectManager;
- private readonly Dictionary _files;
+ private readonly Dictionary _work;
private Timer _timer;
[ImportingConstructor]
@@ -30,17 +29,16 @@ namespace Microsoft.CodeAnalysis.Razor
}
_foregroundDispatcher = foregroundDispatcher;
-
- _files = new Dictionary();
+ _work = new Dictionary();
}
public bool HasPendingNotifications
{
get
{
- lock (_files)
+ lock (_work)
{
- return _files.Count > 0;
+ return _work.Count > 0;
}
}
}
@@ -134,11 +132,11 @@ namespace Microsoft.CodeAnalysis.Razor
_foregroundDispatcher.AssertForegroundThread();
- lock (_files)
+ lock (_work)
{
// We only want to store the last 'seen' version of any given document. That way when we pick one to process
// it's always the best version to use.
- _files[new DocumentKey(project.FilePath, document.FilePath)] = document;
+ _work[new DocumentKey(project.FilePath, document.FilePath)] = document;
StartWorker();
}
@@ -165,18 +163,18 @@ namespace Microsoft.CodeAnalysis.Razor
OnStartingBackgroundWork();
- DocumentSnapshot[] work;
- lock (_files)
+ KeyValuePair[] work;
+ lock (_work)
{
- work = _files.Values.ToArray();
- _files.Clear();
+ work = _work.ToArray();
+ _work.Clear();
}
OnBackgroundCapturedWorkload();
for (var i = 0; i < work.Length; i++)
{
- var document = work[i];
+ var document = work[i].Value;
try
{
await ProcessDocument(document);
@@ -189,14 +187,20 @@ namespace Microsoft.CodeAnalysis.Razor
OnCompletingBackgroundWork();
- lock (_files)
+ await Task.Factory.StartNew(
+ () => ReportUpdates(work),
+ CancellationToken.None,
+ TaskCreationOptions.None,
+ _foregroundDispatcher.ForegroundScheduler);
+
+ lock (_work)
{
// Resetting the timer allows another batch of work to start.
_timer.Dispose();
_timer = null;
// If more work came in while we were running start the worker again.
- if (_files.Count > 0)
+ if (_work.Count > 0)
{
StartWorker();
}
@@ -215,6 +219,22 @@ namespace Microsoft.CodeAnalysis.Razor
}
}
+ private void ReportUpdates(KeyValuePair[] work)
+ {
+ for (var i = 0; i < work.Length; i++)
+ {
+ var key = work[i].Key;
+ var document = work[i].Value;
+
+ if (document.TryGetText(out var source) &&
+ document.TryGetGeneratedOutput(out var output))
+ {
+ var container = ((DefaultDocumentSnapshot)document).State.GeneratedCodeContainer;
+ container.SetOutput(source, output);
+ }
+ }
+ }
+
private void ReportError(DocumentSnapshot document, Exception ex)
{
GC.KeepAlive(Task.Factory.StartNew(
@@ -229,22 +249,34 @@ namespace Microsoft.CodeAnalysis.Razor
switch (e.Kind)
{
case ProjectChangeKind.ProjectAdded:
+ {
+ var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath);
+ foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
+ {
+ Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath));
+ }
+
+ break;
+ }
case ProjectChangeKind.ProjectChanged:
{
- var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
- foreach (var documentFilePath in project.DocumentFilePaths)
+ var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath);
+ foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
{
- Enqueue(project, project.GetDocument(documentFilePath));
+ Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath));
}
break;
}
- case ProjectChangeKind.ProjectRemoved:
- // ignore
- break;
-
case ProjectChangeKind.DocumentAdded:
+ {
+ var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
+ Enqueue(project, project.GetDocument(e.DocumentFilePath));
+
+ break;
+ }
+
case ProjectChangeKind.DocumentChanged:
{
var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
@@ -253,10 +285,12 @@ namespace Microsoft.CodeAnalysis.Razor
break;
}
-
+ case ProjectChangeKind.ProjectRemoved:
case ProjectChangeKind.DocumentRemoved:
- // ignore
- break;
+ {
+ // ignore
+ break;
+ }
default:
throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}");
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
index 32296b51c6..64d66873f8 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
@@ -5,6 +5,7 @@
Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure for Visual Studio.
false
..\Microsoft.NET.Sdk.Razor\build\netstandard2.0\Rules\
+ $(DefineConstants);WORKSPACE_PROJECT_CONTEXT_FACTORY
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
index 68dfc4312a..97978c18cd 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
@@ -7,15 +7,22 @@ using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.VisualStudio;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.Properties;
+using Microsoft.VisualStudio.TextManager.Interop;
using Item = System.Collections.Generic.KeyValuePair>;
+#if WORKSPACE_PROJECT_CONTEXT_FACTORY
+using IWorkspaceProjectContextFactory = Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory2;
+#endif
+
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// Somewhat similar to https://github.com/dotnet/project-system/blob/fa074d228dcff6dae9e48ce43dd4a3a5aa22e8f0/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/LanguageServiceHost.cs
@@ -23,16 +30,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// This class is responsible for intializing the Razor ProjectSnapshotManager for cases where
// MSBuild provides configuration support (>= 2.1).
[AppliesTo("DotNetCoreRazor & DotNetCoreRazorConfiguration")]
+ [ExportVsProfferedProjectService(typeof(IVsContainedLanguageProjectNameProvider))]
[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
- internal class DefaultRazorProjectHost : RazorProjectHostBase
+ internal class DefaultRazorProjectHost : RazorProjectHostBase, IVsContainedLanguageProjectNameProvider
{
private IDisposable _subscription;
[ImportingConstructor]
public DefaultRazorProjectHost(
IUnconfiguredProjectCommonServices commonServices,
- [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
- : base(commonServices, workspace)
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace,
+ Lazy projectContextFactory)
+ : base(commonServices, workspace, projectContextFactory)
{
}
@@ -68,6 +77,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Rules.RazorConfiguration.SchemaName,
Rules.RazorExtension.SchemaName,
Rules.RazorGenerateWithTargetPath.SchemaName,
+ ManagedProjectSystemSchema.CompilerCommandLineArgs.SchemaName,
+ ManagedProjectSystemSchema.ConfigurationGeneral.SchemaName,
+ ManagedProjectSystemSchema.ResolvedCompilationReference.SchemaName,
});
}
@@ -107,9 +119,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
var documents = GetCurrentDocuments(update.Value);
var changedDocuments = GetChangedAndRemovedDocuments(update.Value);
+ var references = GetReferences(update.Value);
+ TryGetCommandLineOptions(update.Value.CurrentState, out var commandLineOptions);
+
await UpdateAsync(() =>
{
UpdateProjectUnsafe(hostProject);
+ UpdateWorkspaceProjectOptionsUnsafe(commandLineOptions);
+ UpdateWorkspaceProjectReferencesUnsafe(references);
for (var i = 0; i < changedDocuments.Length; i++)
{
@@ -302,6 +319,77 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return true;
}
+
+ // This is temporary code for initializing the companion project. We expect
+ // this to be provided by the Managed Project System in the near future.
+ internal static bool TryGetReferences(
+ IImmutableDictionary state,
+ out string[] references)
+ {
+ if (!state.TryGetValue(ManagedProjectSystemSchema.ResolvedCompilationReference.ItemName, out var rule))
+ {
+ references = null;
+ return false;
+ }
+
+ var items = rule.Items;
+ var referencesList = new List();
+ foreach (var item in items)
+ {
+ var reference = item.Key;
+ if (!referencesList.Contains(reference, FilePathComparer.Instance))
+ {
+ referencesList.Add(reference);
+ }
+ }
+
+ references = referencesList.ToArray();
+ return true;
+ }
+
+ // This is temporary code for initializing the companion project. We expect
+ // this to be provided by the Managed Project System in the near future.
+ internal static bool TryGetCommandLineOptions(
+ IImmutableDictionary state,
+ out string commandLineOptions)
+ {
+ if (!state.TryGetValue(ManagedProjectSystemSchema.CompilerCommandLineArgs.ItemName, out var rule))
+ {
+ commandLineOptions = null;
+ return false;
+ }
+
+ commandLineOptions = string.Join(" ", rule.Items.Select(kvp => kvp.Key));
+ return true;
+ }
+
+ // This is temporary code for initializing the companion project. We expect
+ // this to be provided by the Managed Project System in the near future.
+ internal static bool TryGetTargetPath(
+ IImmutableDictionary state,
+ out string targetPath)
+ {
+ if (!state.TryGetValue(ManagedProjectSystemSchema.ConfigurationGeneral.SchemaName, out var rule))
+ {
+ targetPath = null;
+ return false;
+ }
+
+ if (!rule.Properties.TryGetValue(ManagedProjectSystemSchema.ConfigurationGeneral.TargetPathPropertyName, out targetPath))
+ {
+ targetPath = null;
+ return false;
+ }
+
+ if (string.IsNullOrEmpty(targetPath))
+ {
+ targetPath = null;
+ return false;
+ }
+
+ return true;
+ }
+
private HostDocument[] GetCurrentDocuments(IProjectSubscriptionUpdate update)
{
if (!update.CurrentState.TryGetValue(Rules.RazorGenerateWithTargetPath.SchemaName, out var rule))
@@ -348,5 +436,37 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return documents.ToArray();
}
+
+ // This is temporary code for initializing the companion project. We expect
+ // this to be provided by the Managed Project System in the near future.
+ private string[] GetReferences(IProjectSubscriptionUpdate update)
+ {
+ if (!TryGetReferences(update.CurrentState, out var references))
+ {
+ return Array.Empty();
+ }
+
+ if (TryGetTargetPath(update.CurrentState, out var targetPath))
+ {
+ references = references.Concat(new[] { targetPath, }).ToArray();
+ }
+
+ return references;
+ }
+
+ // This is temporary code for initializing the companion project. We expect
+ // this to be provided by the Managed Project System in the near future.
+ public int GetProjectName([In] uint itemid, [MarshalAs(UnmanagedType.BStr)] out string pbstrProjectName)
+ {
+ if (Current == null)
+ {
+ pbstrProjectName = null;
+
+ return VSConstants.E_INVALIDARG;
+ }
+
+ pbstrProjectName = Path.GetFileNameWithoutExtension(Current.FilePath) + " (Razor)";
+ return VSConstants.S_OK;
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWorkspaceProjectContextFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWorkspaceProjectContextFactory.cs
new file mode 100644
index 0000000000..b562bd74c1
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWorkspaceProjectContextFactory.cs
@@ -0,0 +1,102 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until we get access to these APIs
+#if WORKSPACE_PROJECT_CONTEXT_FACTORY
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Experiment;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;
+using IWorkspaceProjectContextFactory = Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory2;
+
+namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem
+{
+ [Export(typeof(IWorkspaceProjectContextFactory))]
+ internal class DefaultWorkspaceProjectContextFactory : IWorkspaceProjectContextFactory
+ {
+ public IWorkspaceProjectContext CreateProjectContext(string languageName, string projectDisplayName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath)
+ {
+ return new WorkspaceProjectContext();
+ }
+
+ public IWorkspaceProjectContext CreateProjectContext(string languageName, string projectDisplayName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath, ProjectExternalErrorReporter errorReporter)
+ {
+ return new WorkspaceProjectContext();
+ }
+
+ private class WorkspaceProjectContext : IWorkspaceProjectContext
+ {
+ public string DisplayName { get; set; }
+ public string ProjectFilePath { get; set; }
+ public Guid Guid { get; set; }
+ public bool LastDesignTimeBuildSucceeded { get; set; }
+ public string BinOutputPath { get; set; }
+
+ public void AddAdditionalFile(string filePath, bool isInCurrentContext = true)
+ {
+ }
+
+ public void AddAnalyzerReference(string referencePath)
+ {
+ }
+
+ public void AddMetadataReference(string referencePath, MetadataReferenceProperties properties)
+ {
+ }
+
+ public void AddProjectReference(IWorkspaceProjectContext project, MetadataReferenceProperties properties)
+ {
+ }
+
+ public void AddSourceFile(string filePath, bool isInCurrentContext, IEnumerable folderNames, SourceCodeKind sourceCodeKind)
+ {
+ }
+
+ public void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IDocumentServiceFactory documentServiceFactory = null)
+ {
+ }
+
+ public void AddSourceFile(string filePath, SourceTextContainer container, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IDocumentServiceFactory documentServiceFactory = null)
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public void RemoveAdditionalFile(string filePath)
+ {
+ }
+
+ public void RemoveAnalyzerReference(string referencePath)
+ {
+ }
+
+ public void RemoveMetadataReference(string referencePath)
+ {
+ }
+
+ public void RemoveProjectReference(IWorkspaceProjectContext project)
+ {
+ }
+
+ public void RemoveSourceFile(string filePath)
+ {
+ }
+
+ public void SetOptions(string commandLineForOptions)
+ {
+ }
+
+ public void SetRuleSetFile(string filePath)
+ {
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
index 5dedf67af9..ef33b79723 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
@@ -38,7 +38,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public FallbackRazorProjectHost(
IUnconfiguredProjectCommonServices commonServices,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
- : base(commonServices, workspace)
+ : base(commonServices, workspace, projectContextFactory: null)
{
}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IWorkspaceProjectContext.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IWorkspaceProjectContext.cs
new file mode 100644
index 0000000000..8b34558d85
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IWorkspaceProjectContext.cs
@@ -0,0 +1,47 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until we get access to these APIs
+#if WORKSPACE_PROJECT_CONTEXT_FACTORY
+
+using System;
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Experiment;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem
+{
+ internal interface IWorkspaceProjectContext : IDisposable
+ {
+ // Project properties.
+ string DisplayName { get; set; }
+ string ProjectFilePath { get; set; }
+ Guid Guid { get; set; }
+ bool LastDesignTimeBuildSucceeded { get; set; }
+ string BinOutputPath { get; set; }
+
+ // Options.
+ void SetOptions(string commandLineForOptions);
+
+ // References.
+ void AddMetadataReference(string referencePath, MetadataReferenceProperties properties);
+ void RemoveMetadataReference(string referencePath);
+ void AddProjectReference(IWorkspaceProjectContext project, MetadataReferenceProperties properties);
+ void RemoveProjectReference(IWorkspaceProjectContext project);
+ void AddAnalyzerReference(string referencePath);
+ void RemoveAnalyzerReference(string referencePath);
+
+ // Files.
+ void AddSourceFile(string filePath, bool isInCurrentContext, IEnumerable folderNames, SourceCodeKind sourceCodeKind); // This overload just for binary compat with existing code
+ void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IDocumentServiceFactory documentServiceFactory = null);
+ void AddSourceFile(string filePath, SourceTextContainer container, bool isInCurrentContext = true, IEnumerable folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IDocumentServiceFactory documentServiceFactory = null);
+
+ void RemoveSourceFile(string filePath);
+ void AddAdditionalFile(string filePath, bool isInCurrentContext = true);
+ void RemoveAdditionalFile(string filePath);
+ void SetRuleSetFile(string filePath);
+ }
+}
+
+#endif
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IWorkspaceProjectContextFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IWorkspaceProjectContextFactory.cs
new file mode 100644
index 0000000000..0cb912b627
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IWorkspaceProjectContextFactory.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until we get access to these APIs
+#if WORKSPACE_PROJECT_CONTEXT_FACTORY
+
+using System;
+using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;
+
+namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem
+{
+ // The 2 is needed here to prevent clashes with the real interface. MEF is based on the FullName
+ // as a string.
+ internal interface IWorkspaceProjectContextFactory2
+ {
+ IWorkspaceProjectContext CreateProjectContext(string languageName, string projectDisplayName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath);
+
+
+ IWorkspaceProjectContext CreateProjectContext(string languageName, string projectDisplayName, string projectFilePath, Guid projectGuid, object hierarchy, string binOutputPath, ProjectExternalErrorReporter errorReporter);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManagedProjectSystemSchema.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManagedProjectSystemSchema.cs
index a56464e495..9f67217eb6 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManagedProjectSystemSchema.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManagedProjectSystemSchema.cs
@@ -6,6 +6,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Well-Known Schema and property names defined by the ManagedProjectSystem
internal static class ManagedProjectSystemSchema
{
+ public static class CompilerCommandLineArgs
+ {
+ public static readonly string SchemaName = "CompilerCommandLineArgs";
+
+ public static readonly string ItemName = "CompilerCommandLineArgs";
+ }
+
+ public static class ConfigurationGeneral
+ {
+ public static readonly string SchemaName = "ConfigurationGeneral";
+
+ public static readonly string TargetPathPropertyName = "TargetPath";
+ }
+
public static class ResolvedCompilationReference
{
public static readonly string SchemaName = "ResolvedCompilationReference";
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ProjectExternalErrorReporter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ProjectExternalErrorReporter.cs
new file mode 100644
index 0000000000..d9c230f65d
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ProjectExternalErrorReporter.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// Temporary code until we get access to these APIs
+#if WORKSPACE_PROJECT_CONTEXT_FACTORY
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
+{
+ internal class ProjectExternalErrorReporter
+ {
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
index e39a866f05..930262b57a 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
@@ -5,27 +5,40 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
+using System.IO;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.Threading;
+#if WORKSPACE_PROJECT_CONTEXT_FACTORY
+using IWorkspaceProjectContextFactory = Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory2;
+#endif
+
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class RazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent
{
private readonly Workspace _workspace;
+ private readonly Lazy _projectContextFactory;
private readonly AsyncSemaphore _lock;
private ProjectSnapshotManagerBase _projectManager;
private HostProject _current;
+ private IWorkspaceProjectContext _projectContext;
private Dictionary _currentDocuments;
+ private HashSet _references;
+ private string _commandLineOptions;
public RazorProjectHostBase(
IUnconfiguredProjectCommonServices commonServices,
- [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace,
+ Lazy projectContextFactory)
: base(commonServices.ThreadingService.JoinableTaskContext)
{
if (commonServices == null)
@@ -40,9 +53,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
CommonServices = commonServices;
_workspace = workspace;
+ _projectContextFactory = projectContextFactory;
_lock = new AsyncSemaphore(initialCount: 1);
_currentDocuments = new Dictionary(FilePathComparer.Instance);
+ _references = new HashSet(FilePathComparer.Instance);
}
// Internal for testing
@@ -73,6 +88,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_lock = new AsyncSemaphore(initialCount: 1);
_currentDocuments = new Dictionary(FilePathComparer.Instance);
+ _references = new HashSet(FilePathComparer.Instance);
}
protected HostProject Current => _current;
@@ -129,6 +145,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
var filePath = CommonServices.UnconfiguredProject.FullPath;
UpdateProjectUnsafe(new HostProject(filePath, old.Configuration));
+ UpdateWorkspaceProjectOptionsUnsafe(_commandLineOptions);
+ UpdateWorkspaceProjectReferencesUnsafe(_references.ToArray());
// This should no-op in the common case, just putting it here for insurance.
for (var i = 0; i < oldDocuments.Length; i++)
@@ -153,6 +171,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return _projectManager;
}
+ private IWorkspaceProjectContextFactory GetProjectContextFactory()
+ {
+ CommonServices.ThreadingService.VerifyOnUIThread();
+ return _projectContextFactory?.Value;
+ }
+
protected async Task UpdateAsync(Action action)
{
await CommonServices.ThreadingService.SwitchToUIThread();
@@ -174,12 +198,40 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
else if (_current == null && project != null)
{
+ // This is temporary code for initializing the companion project. We expect
+ // this to be provided by the Managed Project System in the near future.
+ var projectContextFactory = GetProjectContextFactory();
+ if (projectContextFactory != null)
+ {
+ var assembly = Assembly.Load("Microsoft.VisualStudio.ProjectSystem.Managed, Version=2.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
+ var type = assembly.GetType("Microsoft.VisualStudio.ProjectSystem.LanguageServices.IProjectHostProvider");
+
+ var exportProviderType = CommonServices.UnconfiguredProject.Services.ExportProvider.GetType();
+ var method = exportProviderType.GetMethod(nameof(ExportProvider.GetExportedValue), Array.Empty()).MakeGenericMethod(type);
+ var export = method.Invoke(CommonServices.UnconfiguredProject.Services.ExportProvider, Array.Empty