Add generated documents to workspace
This commit is contained in:
parent
b486c5a233
commit
13e13d7632
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure.</Description>
|
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure.</Description>
|
||||||
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
|
||||||
<EnableApiCheck>false</EnableApiCheck>
|
<EnableApiCheck>false</EnableApiCheck>
|
||||||
|
<DefineConstants>$(DefineConstants);DOCUMENT_SERVICE_FACTORY</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
|
||||||
public HostWorkspaceServices Services { get; }
|
public HostWorkspaceServices Services { get; }
|
||||||
|
|
||||||
|
public GeneratedCodeContainer GeneratedCodeContainer => HostDocument.GeneratedCodeContainer;
|
||||||
|
|
||||||
public DocumentGeneratedOutputTracker GeneratedOutput
|
public DocumentGeneratedOutputTracker GeneratedOutput
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -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<TService>()
|
||||||
|
{
|
||||||
|
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<ImmutableArray<SpanMapResult>> MapSpansAsync(
|
||||||
|
Document document,
|
||||||
|
IEnumerable<TextSpan> spans,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (Output == null)
|
||||||
|
{
|
||||||
|
return Task.FromResult(ImmutableArray<SpanMapResult>.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = ImmutableArray.CreateBuilder<SpanMapResult>();
|
||||||
|
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<TextChangeEventArgs> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,10 +21,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
|
||||||
FilePath = filePath;
|
FilePath = filePath;
|
||||||
TargetPath = targetPath;
|
TargetPath = targetPath;
|
||||||
|
GeneratedCodeContainer = new GeneratedCodeContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FilePath { get; }
|
public string FilePath { get; }
|
||||||
|
|
||||||
public string TargetPath { get; }
|
public string TargetPath { get; }
|
||||||
|
|
||||||
|
public GeneratedCodeContainer GeneratedCodeContainer { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,9 @@ Copyright (c) .NET Foundation. All rights reserved.
|
||||||
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorGeneral.xaml">
|
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorGeneral.xaml">
|
||||||
<Context>Project</Context>
|
<Context>Project</Context>
|
||||||
</PropertyPageSchema>
|
</PropertyPageSchema>
|
||||||
|
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Rules\RazorGenerateWithTargetPath.xaml">
|
||||||
|
<Context>Project</Context>
|
||||||
|
</PropertyPageSchema>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="RazorGenerateDesignTime" DependsOnTargets="ResolveRazorGenerateInputs;AssignRazorGenerateTargetPaths" Returns="@(RazorGenerateWithTargetPath)">
|
<Target Name="RazorGenerateDesignTime" DependsOnTargets="ResolveRazorGenerateInputs;AssignRazorGenerateTargetPaths" Returns="@(RazorGenerateWithTargetPath)">
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,13 @@ using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||||
|
|
||||||
namespace Microsoft.CodeAnalysis.Razor
|
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
|
internal class BackgroundDocumentGenerator : ProjectSnapshotChangeTrigger
|
||||||
{
|
{
|
||||||
private ForegroundDispatcher _foregroundDispatcher;
|
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||||
private ProjectSnapshotManagerBase _projectManager;
|
private ProjectSnapshotManagerBase _projectManager;
|
||||||
|
|
||||||
private readonly Dictionary<DocumentKey, DocumentSnapshot> _files;
|
private readonly Dictionary<DocumentKey, DocumentSnapshot> _work;
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
|
|
||||||
[ImportingConstructor]
|
[ImportingConstructor]
|
||||||
|
|
@ -30,17 +29,16 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
_foregroundDispatcher = foregroundDispatcher;
|
_foregroundDispatcher = foregroundDispatcher;
|
||||||
|
_work = new Dictionary<DocumentKey, DocumentSnapshot>();
|
||||||
_files = new Dictionary<DocumentKey, DocumentSnapshot>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasPendingNotifications
|
public bool HasPendingNotifications
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_files)
|
lock (_work)
|
||||||
{
|
{
|
||||||
return _files.Count > 0;
|
return _work.Count > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,11 +132,11 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
|
||||||
_foregroundDispatcher.AssertForegroundThread();
|
_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
|
// 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.
|
// 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();
|
StartWorker();
|
||||||
}
|
}
|
||||||
|
|
@ -165,18 +163,18 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
|
||||||
OnStartingBackgroundWork();
|
OnStartingBackgroundWork();
|
||||||
|
|
||||||
DocumentSnapshot[] work;
|
KeyValuePair<DocumentKey, DocumentSnapshot>[] work;
|
||||||
lock (_files)
|
lock (_work)
|
||||||
{
|
{
|
||||||
work = _files.Values.ToArray();
|
work = _work.ToArray();
|
||||||
_files.Clear();
|
_work.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
OnBackgroundCapturedWorkload();
|
OnBackgroundCapturedWorkload();
|
||||||
|
|
||||||
for (var i = 0; i < work.Length; i++)
|
for (var i = 0; i < work.Length; i++)
|
||||||
{
|
{
|
||||||
var document = work[i];
|
var document = work[i].Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProcessDocument(document);
|
await ProcessDocument(document);
|
||||||
|
|
@ -189,14 +187,20 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
|
||||||
OnCompletingBackgroundWork();
|
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.
|
// Resetting the timer allows another batch of work to start.
|
||||||
_timer.Dispose();
|
_timer.Dispose();
|
||||||
_timer = null;
|
_timer = null;
|
||||||
|
|
||||||
// If more work came in while we were running start the worker again.
|
// If more work came in while we were running start the worker again.
|
||||||
if (_files.Count > 0)
|
if (_work.Count > 0)
|
||||||
{
|
{
|
||||||
StartWorker();
|
StartWorker();
|
||||||
}
|
}
|
||||||
|
|
@ -215,6 +219,22 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReportUpdates(KeyValuePair<DocumentKey, DocumentSnapshot>[] 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)
|
private void ReportError(DocumentSnapshot document, Exception ex)
|
||||||
{
|
{
|
||||||
GC.KeepAlive(Task.Factory.StartNew(
|
GC.KeepAlive(Task.Factory.StartNew(
|
||||||
|
|
@ -229,22 +249,34 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
switch (e.Kind)
|
switch (e.Kind)
|
||||||
{
|
{
|
||||||
case ProjectChangeKind.ProjectAdded:
|
case ProjectChangeKind.ProjectAdded:
|
||||||
|
{
|
||||||
|
var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath);
|
||||||
|
foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
|
||||||
|
{
|
||||||
|
Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ProjectChangeKind.ProjectChanged:
|
case ProjectChangeKind.ProjectChanged:
|
||||||
{
|
{
|
||||||
var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
|
var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath);
|
||||||
foreach (var documentFilePath in project.DocumentFilePaths)
|
foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
|
||||||
{
|
{
|
||||||
Enqueue(project, project.GetDocument(documentFilePath));
|
Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ProjectChangeKind.ProjectRemoved:
|
|
||||||
// ignore
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ProjectChangeKind.DocumentAdded:
|
case ProjectChangeKind.DocumentAdded:
|
||||||
|
{
|
||||||
|
var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
|
||||||
|
Enqueue(project, project.GetDocument(e.DocumentFilePath));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ProjectChangeKind.DocumentChanged:
|
case ProjectChangeKind.DocumentChanged:
|
||||||
{
|
{
|
||||||
var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
|
var project = _projectManager.GetLoadedProject(e.ProjectFilePath);
|
||||||
|
|
@ -253,10 +285,12 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ProjectChangeKind.ProjectRemoved:
|
||||||
case ProjectChangeKind.DocumentRemoved:
|
case ProjectChangeKind.DocumentRemoved:
|
||||||
// ignore
|
{
|
||||||
break;
|
// ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}");
|
throw new InvalidOperationException($"Unknown ProjectChangeKind {e.Kind}");
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure for Visual Studio.</Description>
|
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains the Razor design-time infrastructure for Visual Studio.</Description>
|
||||||
<EnableApiCheck>false</EnableApiCheck>
|
<EnableApiCheck>false</EnableApiCheck>
|
||||||
<RulesDirectory>..\Microsoft.NET.Sdk.Razor\build\netstandard2.0\Rules\</RulesDirectory>
|
<RulesDirectory>..\Microsoft.NET.Sdk.Razor\build\netstandard2.0\Rules\</RulesDirectory>
|
||||||
|
<DefineConstants>$(DefineConstants);WORKSPACE_PROJECT_CONTEXT_FACTORY</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,22 @@ using System.Collections.Immutable;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks.Dataflow;
|
using System.Threading.Tasks.Dataflow;
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.VisualStudio;
|
||||||
using Microsoft.VisualStudio.LanguageServices;
|
using Microsoft.VisualStudio.LanguageServices;
|
||||||
using Microsoft.VisualStudio.ProjectSystem;
|
using Microsoft.VisualStudio.ProjectSystem;
|
||||||
using Microsoft.VisualStudio.ProjectSystem.Properties;
|
using Microsoft.VisualStudio.ProjectSystem.Properties;
|
||||||
|
using Microsoft.VisualStudio.TextManager.Interop;
|
||||||
using Item = System.Collections.Generic.KeyValuePair<string, System.Collections.Immutable.IImmutableDictionary<string, string>>;
|
using Item = System.Collections.Generic.KeyValuePair<string, System.Collections.Immutable.IImmutableDictionary<string, string>>;
|
||||||
|
|
||||||
|
#if WORKSPACE_PROJECT_CONTEXT_FACTORY
|
||||||
|
using IWorkspaceProjectContextFactory = Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory2;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
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
|
// 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
|
// This class is responsible for intializing the Razor ProjectSnapshotManager for cases where
|
||||||
// MSBuild provides configuration support (>= 2.1).
|
// MSBuild provides configuration support (>= 2.1).
|
||||||
[AppliesTo("DotNetCoreRazor & DotNetCoreRazorConfiguration")]
|
[AppliesTo("DotNetCoreRazor & DotNetCoreRazorConfiguration")]
|
||||||
|
[ExportVsProfferedProjectService(typeof(IVsContainedLanguageProjectNameProvider))]
|
||||||
[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
|
[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
|
||||||
internal class DefaultRazorProjectHost : RazorProjectHostBase
|
internal class DefaultRazorProjectHost : RazorProjectHostBase, IVsContainedLanguageProjectNameProvider
|
||||||
{
|
{
|
||||||
private IDisposable _subscription;
|
private IDisposable _subscription;
|
||||||
|
|
||||||
[ImportingConstructor]
|
[ImportingConstructor]
|
||||||
public DefaultRazorProjectHost(
|
public DefaultRazorProjectHost(
|
||||||
IUnconfiguredProjectCommonServices commonServices,
|
IUnconfiguredProjectCommonServices commonServices,
|
||||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
[Import(typeof(VisualStudioWorkspace))] Workspace workspace,
|
||||||
: base(commonServices, workspace)
|
Lazy<IWorkspaceProjectContextFactory> projectContextFactory)
|
||||||
|
: base(commonServices, workspace, projectContextFactory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +77,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
Rules.RazorConfiguration.SchemaName,
|
Rules.RazorConfiguration.SchemaName,
|
||||||
Rules.RazorExtension.SchemaName,
|
Rules.RazorExtension.SchemaName,
|
||||||
Rules.RazorGenerateWithTargetPath.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 documents = GetCurrentDocuments(update.Value);
|
||||||
var changedDocuments = GetChangedAndRemovedDocuments(update.Value);
|
var changedDocuments = GetChangedAndRemovedDocuments(update.Value);
|
||||||
|
|
||||||
|
var references = GetReferences(update.Value);
|
||||||
|
TryGetCommandLineOptions(update.Value.CurrentState, out var commandLineOptions);
|
||||||
|
|
||||||
await UpdateAsync(() =>
|
await UpdateAsync(() =>
|
||||||
{
|
{
|
||||||
UpdateProjectUnsafe(hostProject);
|
UpdateProjectUnsafe(hostProject);
|
||||||
|
UpdateWorkspaceProjectOptionsUnsafe(commandLineOptions);
|
||||||
|
UpdateWorkspaceProjectReferencesUnsafe(references);
|
||||||
|
|
||||||
for (var i = 0; i < changedDocuments.Length; i++)
|
for (var i = 0; i < changedDocuments.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -302,6 +319,77 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
return true;
|
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<string, IProjectRuleSnapshot> 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<string>();
|
||||||
|
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<string, IProjectRuleSnapshot> 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<string, IProjectRuleSnapshot> 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)
|
private HostDocument[] GetCurrentDocuments(IProjectSubscriptionUpdate update)
|
||||||
{
|
{
|
||||||
if (!update.CurrentState.TryGetValue(Rules.RazorGenerateWithTargetPath.SchemaName, out var rule))
|
if (!update.CurrentState.TryGetValue(Rules.RazorGenerateWithTargetPath.SchemaName, out var rule))
|
||||||
|
|
@ -348,5 +436,37 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
|
||||||
return documents.ToArray();
|
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<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<string> folderNames, SourceCodeKind sourceCodeKind)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable<string> folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IDocumentServiceFactory documentServiceFactory = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSourceFile(string filePath, SourceTextContainer container, bool isInCurrentContext = true, IEnumerable<string> 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
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
public FallbackRazorProjectHost(
|
public FallbackRazorProjectHost(
|
||||||
IUnconfiguredProjectCommonServices commonServices,
|
IUnconfiguredProjectCommonServices commonServices,
|
||||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||||
: base(commonServices, workspace)
|
: base(commonServices, workspace, projectContextFactory: null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<string> folderNames, SourceCodeKind sourceCodeKind); // This overload just for binary compat with existing code
|
||||||
|
void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable<string> folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular, IDocumentServiceFactory documentServiceFactory = null);
|
||||||
|
void AddSourceFile(string filePath, SourceTextContainer container, bool isInCurrentContext = true, IEnumerable<string> 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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -6,6 +6,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
// Well-Known Schema and property names defined by the ManagedProjectSystem
|
// Well-Known Schema and property names defined by the ManagedProjectSystem
|
||||||
internal static class ManagedProjectSystemSchema
|
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 class ResolvedCompilationReference
|
||||||
{
|
{
|
||||||
public static readonly string SchemaName = "ResolvedCompilationReference";
|
public static readonly string SchemaName = "ResolvedCompilationReference";
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -5,27 +5,40 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.Composition;
|
||||||
using Microsoft.VisualStudio.LanguageServices;
|
using Microsoft.VisualStudio.LanguageServices;
|
||||||
|
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
|
||||||
using Microsoft.VisualStudio.ProjectSystem;
|
using Microsoft.VisualStudio.ProjectSystem;
|
||||||
using Microsoft.VisualStudio.Threading;
|
using Microsoft.VisualStudio.Threading;
|
||||||
|
|
||||||
|
#if WORKSPACE_PROJECT_CONTEXT_FACTORY
|
||||||
|
using IWorkspaceProjectContextFactory = Microsoft.VisualStudio.LanguageServices.ProjectSystem.IWorkspaceProjectContextFactory2;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
{
|
{
|
||||||
internal abstract class RazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent
|
internal abstract class RazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent
|
||||||
{
|
{
|
||||||
private readonly Workspace _workspace;
|
private readonly Workspace _workspace;
|
||||||
|
private readonly Lazy<IWorkspaceProjectContextFactory> _projectContextFactory;
|
||||||
private readonly AsyncSemaphore _lock;
|
private readonly AsyncSemaphore _lock;
|
||||||
|
|
||||||
private ProjectSnapshotManagerBase _projectManager;
|
private ProjectSnapshotManagerBase _projectManager;
|
||||||
private HostProject _current;
|
private HostProject _current;
|
||||||
|
private IWorkspaceProjectContext _projectContext;
|
||||||
private Dictionary<string, HostDocument> _currentDocuments;
|
private Dictionary<string, HostDocument> _currentDocuments;
|
||||||
|
private HashSet<string> _references;
|
||||||
|
private string _commandLineOptions;
|
||||||
|
|
||||||
public RazorProjectHostBase(
|
public RazorProjectHostBase(
|
||||||
IUnconfiguredProjectCommonServices commonServices,
|
IUnconfiguredProjectCommonServices commonServices,
|
||||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
[Import(typeof(VisualStudioWorkspace))] Workspace workspace,
|
||||||
|
Lazy<IWorkspaceProjectContextFactory> projectContextFactory)
|
||||||
: base(commonServices.ThreadingService.JoinableTaskContext)
|
: base(commonServices.ThreadingService.JoinableTaskContext)
|
||||||
{
|
{
|
||||||
if (commonServices == null)
|
if (commonServices == null)
|
||||||
|
|
@ -40,9 +53,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
|
||||||
CommonServices = commonServices;
|
CommonServices = commonServices;
|
||||||
_workspace = workspace;
|
_workspace = workspace;
|
||||||
|
_projectContextFactory = projectContextFactory;
|
||||||
|
|
||||||
_lock = new AsyncSemaphore(initialCount: 1);
|
_lock = new AsyncSemaphore(initialCount: 1);
|
||||||
_currentDocuments = new Dictionary<string, HostDocument>(FilePathComparer.Instance);
|
_currentDocuments = new Dictionary<string, HostDocument>(FilePathComparer.Instance);
|
||||||
|
_references = new HashSet<string>(FilePathComparer.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
|
|
@ -73,6 +88,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
|
||||||
_lock = new AsyncSemaphore(initialCount: 1);
|
_lock = new AsyncSemaphore(initialCount: 1);
|
||||||
_currentDocuments = new Dictionary<string, HostDocument>(FilePathComparer.Instance);
|
_currentDocuments = new Dictionary<string, HostDocument>(FilePathComparer.Instance);
|
||||||
|
_references = new HashSet<string>(FilePathComparer.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HostProject Current => _current;
|
protected HostProject Current => _current;
|
||||||
|
|
@ -129,6 +145,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
{
|
{
|
||||||
var filePath = CommonServices.UnconfiguredProject.FullPath;
|
var filePath = CommonServices.UnconfiguredProject.FullPath;
|
||||||
UpdateProjectUnsafe(new HostProject(filePath, old.Configuration));
|
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.
|
// This should no-op in the common case, just putting it here for insurance.
|
||||||
for (var i = 0; i < oldDocuments.Length; i++)
|
for (var i = 0; i < oldDocuments.Length; i++)
|
||||||
|
|
@ -153,6 +171,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
return _projectManager;
|
return _projectManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IWorkspaceProjectContextFactory GetProjectContextFactory()
|
||||||
|
{
|
||||||
|
CommonServices.ThreadingService.VerifyOnUIThread();
|
||||||
|
return _projectContextFactory?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
protected async Task UpdateAsync(Action action)
|
protected async Task UpdateAsync(Action action)
|
||||||
{
|
{
|
||||||
await CommonServices.ThreadingService.SwitchToUIThread();
|
await CommonServices.ThreadingService.SwitchToUIThread();
|
||||||
|
|
@ -174,12 +198,40 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
}
|
}
|
||||||
else if (_current == null && project != null)
|
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<Type>()).MakeGenericMethod(type);
|
||||||
|
var export = method.Invoke(CommonServices.UnconfiguredProject.Services.ExportProvider, Array.Empty<object>());
|
||||||
|
var host = new IProjectHostProvider(export);
|
||||||
|
|
||||||
|
var displayName = Path.GetFileNameWithoutExtension(CommonServices.UnconfiguredProject.FullPath) + " (Razor)";
|
||||||
|
_projectContext = projectContextFactory.CreateProjectContext(
|
||||||
|
LanguageNames.CSharp,
|
||||||
|
displayName,
|
||||||
|
CommonServices.UnconfiguredProject.FullPath,
|
||||||
|
Guid.NewGuid(),
|
||||||
|
host.UnconfiguredProjectHostObject.ActiveIntellisenseProjectHostObject,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// END temporary code
|
||||||
|
|
||||||
projectManager.HostProjectAdded(project);
|
projectManager.HostProjectAdded(project);
|
||||||
}
|
}
|
||||||
else if (_current != null && project == null)
|
else if (_current != null && project == null)
|
||||||
{
|
{
|
||||||
Debug.Assert(_currentDocuments.Count == 0);
|
Debug.Assert(_currentDocuments.Count == 0);
|
||||||
projectManager.HostProjectRemoved(_current);
|
projectManager.HostProjectRemoved(_current);
|
||||||
|
_projectContext?.Dispose();
|
||||||
|
_projectContext = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -189,6 +241,56 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
_current = project;
|
_current = project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void UpdateWorkspaceProjectOptionsUnsafe(string commandLineOptions)
|
||||||
|
{
|
||||||
|
if (_projectContext == null)
|
||||||
|
{
|
||||||
|
_commandLineOptions = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(_commandLineOptions, commandLineOptions))
|
||||||
|
{
|
||||||
|
_projectContext.SetOptions(commandLineOptions);
|
||||||
|
_commandLineOptions = commandLineOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateWorkspaceProjectReferencesUnsafe(string[] references)
|
||||||
|
{
|
||||||
|
if (_projectContext == null)
|
||||||
|
{
|
||||||
|
_references.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newer = new HashSet<string>(references, FilePathComparer.Instance);
|
||||||
|
var older = new HashSet<string>(_references, FilePathComparer.Instance);
|
||||||
|
|
||||||
|
if (older.SetEquals(newer))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remove = new HashSet<string>(older, FilePathComparer.Instance);
|
||||||
|
remove.ExceptWith(newer);
|
||||||
|
|
||||||
|
var add = new HashSet<string>(newer, FilePathComparer.Instance);
|
||||||
|
add.ExceptWith(older);
|
||||||
|
|
||||||
|
foreach (var reference in remove)
|
||||||
|
{
|
||||||
|
_references.Remove(reference);
|
||||||
|
_projectContext.RemoveMetadataReference(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var reference in add)
|
||||||
|
{
|
||||||
|
_references.Add(reference);
|
||||||
|
_projectContext.AddMetadataReference(reference, new MetadataReferenceProperties());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void AddDocumentUnsafe(HostDocument document)
|
protected void AddDocumentUnsafe(HostDocument document)
|
||||||
{
|
{
|
||||||
var projectManager = GetProjectManager();
|
var projectManager = GetProjectManager();
|
||||||
|
|
@ -200,6 +302,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
projectManager.DocumentAdded(_current, document, new FileTextLoader(document.FilePath, null));
|
projectManager.DocumentAdded(_current, document, new FileTextLoader(document.FilePath, null));
|
||||||
|
_projectContext?.AddSourceFile(document.FilePath, document.GeneratedCodeContainer.SourceTextContainer, true, GetFolders(document), SourceCodeKind.Regular, document.GeneratedCodeContainer);
|
||||||
_currentDocuments.Add(document.FilePath, document);
|
_currentDocuments.Add(document.FilePath, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,6 +310,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
{
|
{
|
||||||
var projectManager = GetProjectManager();
|
var projectManager = GetProjectManager();
|
||||||
|
|
||||||
|
_projectContext?.RemoveSourceFile(document.FilePath);
|
||||||
projectManager.DocumentRemoved(_current, document);
|
projectManager.DocumentRemoved(_current, document);
|
||||||
_currentDocuments.Remove(document.FilePath);
|
_currentDocuments.Remove(document.FilePath);
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +321,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
|
||||||
foreach (var kvp in _currentDocuments)
|
foreach (var kvp in _currentDocuments)
|
||||||
{
|
{
|
||||||
|
_projectContext?.RemoveSourceFile(kvp.Value.FilePath);
|
||||||
_projectManager.DocumentRemoved(_current, kvp.Value);
|
_projectManager.DocumentRemoved(_current, kvp.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,5 +354,48 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
{
|
{
|
||||||
await OnProjectRenamingAsync().ConfigureAwait(false);
|
await OnProjectRenamingAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetFolders(HostDocument document)
|
||||||
|
{
|
||||||
|
var split = document.TargetPath.Split('/');
|
||||||
|
return split.Take(split.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class IUnconfiguredProjectHostObject
|
||||||
|
{
|
||||||
|
private readonly object _inner;
|
||||||
|
|
||||||
|
public IUnconfiguredProjectHostObject(object inner)
|
||||||
|
{
|
||||||
|
_inner = inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ActiveIntellisenseProjectHostObject
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _inner.GetType().GetProperty(nameof(ActiveIntellisenseProjectHostObject)).GetValue(_inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class IProjectHostProvider
|
||||||
|
{
|
||||||
|
private readonly object _inner;
|
||||||
|
|
||||||
|
public IProjectHostProvider(object inner)
|
||||||
|
{
|
||||||
|
_inner = inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IUnconfiguredProjectHostObject UnconfiguredProjectHostObject
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var inner = _inner.GetType().GetProperty(nameof(UnconfiguredProjectHostObject)).GetValue(_inner);
|
||||||
|
return new IUnconfiguredProjectHostObject(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// 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;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||||
|
{
|
||||||
|
public class GeneratedCodeContainerTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TryGetLinePositionSpan_SpanWithinSourceMapping_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = @"
|
||||||
|
@{
|
||||||
|
var x = SomeClass.SomeProperty;
|
||||||
|
}
|
||||||
|
";
|
||||||
|
var sourceText = SourceText.From(content);
|
||||||
|
var codeDocument = GetCodeDocument(content);
|
||||||
|
var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode;
|
||||||
|
|
||||||
|
var container = new GeneratedCodeContainer();
|
||||||
|
container.SetOutput(sourceText, codeDocument);
|
||||||
|
|
||||||
|
// TODO: Make writing these tests a little less manual.
|
||||||
|
// Position of `SomeProperty` in the generated code.
|
||||||
|
var symbol = "SomeProperty";
|
||||||
|
var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);
|
||||||
|
|
||||||
|
// Position of `SomeProperty` in the source code.
|
||||||
|
var expectedLineSpan = new LinePositionSpan(new LinePosition(2, 22), new LinePosition(2, 34));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = container.TryGetLinePositionSpan(span, out var lineSpan);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Equal(expectedLineSpan, lineSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGetLinePositionSpan_SpanOutsideSourceMapping_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = @"
|
||||||
|
@{
|
||||||
|
var x = SomeClass.SomeProperty;
|
||||||
|
}
|
||||||
|
";
|
||||||
|
var sourceText = SourceText.From(content);
|
||||||
|
var codeDocument = GetCodeDocument(content);
|
||||||
|
var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode;
|
||||||
|
|
||||||
|
var container = new GeneratedCodeContainer();
|
||||||
|
container.SetOutput(sourceText, codeDocument);
|
||||||
|
|
||||||
|
// Position of `ExecuteAsync` in the generated code.
|
||||||
|
var symbol = "ExecuteAsync";
|
||||||
|
var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = container.TryGetLinePositionSpan(span, out var lineSpan);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RazorCodeDocument GetCodeDocument(string content)
|
||||||
|
{
|
||||||
|
var sourceProjectItem = new TestRazorProjectItem("test.cshtml")
|
||||||
|
{
|
||||||
|
Content = content,
|
||||||
|
};
|
||||||
|
|
||||||
|
var engine = RazorProjectEngine.Create();
|
||||||
|
var codeDocument = engine.ProcessDesignTime(sourceProjectItem);
|
||||||
|
return codeDocument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue