зеркало из https://github.com/dotnet/razor.git
Merge branch 'nimullen/13494.prereq' into release/3.1
This commit is contained in:
Коммит
e980416506
|
@ -119,6 +119,7 @@ Session.vim
|
|||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
.vscode-test/
|
||||
|
||||
# Private test configuration and binaries.
|
||||
config.ps1
|
||||
|
@ -132,4 +133,8 @@ node_modules/
|
|||
*.pyc
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
.idea/
|
||||
|
||||
# VSCode extension outputs
|
||||
dist/
|
||||
out/
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"ms-vscode.csharp",
|
||||
"EditorConfig.EditorConfig"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/src/Razor/test/testapps/",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode.Extension"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode.Extension/dist/**/*.js",
|
||||
"${workspaceFolder}/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/dist/**/*.js",
|
||||
],
|
||||
"preLaunchTask": "CompileExtension"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run Unit Tests",
|
||||
"runtimeExecutable": "yarn",
|
||||
"cwd": "${workspaceFolder}/src/Razor/test/Microsoft.AspNetCore.Razor.VSCode.Test",
|
||||
"runtimeArgs": [
|
||||
"test:debug"
|
||||
],
|
||||
"port": 9229,
|
||||
"sourceMaps": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"preLaunchTask": "CompileUnitTests"
|
||||
},
|
||||
{
|
||||
"name": "Run Functional Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/src/Razor/test/testapps/",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/src/Razor/src/Microsoft.AspNetCore.Razor.VSCode.Extension",
|
||||
"--extensionTestsPath=${workspaceFolder}/src/Razor/test/Microsoft.AspNetCore.Razor.VSCode.FunctionalTest/dist/index",
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/src/Razor/test/Microsoft.AspNetCore.Razor.VSCode.FunctionalTest/dist/**/*.js",
|
||||
],
|
||||
"preLaunchTask": "CompileFunctionalTest"
|
||||
},
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.associations": {
|
||||
"*.*proj": "xml",
|
||||
"*.props": "xml",
|
||||
"*.targets": "xml",
|
||||
"*.tasks": "xml"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "CompilePackage",
|
||||
"command": "dotnet",
|
||||
"args": [ "build" ],
|
||||
"options": {
|
||||
"cwd": "src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/"
|
||||
},
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "CompileExtension",
|
||||
"command": "dotnet",
|
||||
"args": [ "build" ],
|
||||
"options": {
|
||||
"cwd": "src/Razor/src/Microsoft.AspNetCore.Razor.VSCode.Extension/"
|
||||
},
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "CompileUnitTests",
|
||||
"command": "dotnet",
|
||||
"args": [ "build" ],
|
||||
"options": {
|
||||
"cwd": "src/Razor/test/Microsoft.AspNetCore.Razor.VSCode.Test/"
|
||||
},
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "CompileFunctionalTest",
|
||||
"command": "dotnet",
|
||||
"args": [ "build" ],
|
||||
"options": {
|
||||
"cwd": "src/Razor/test/Microsoft.AspNetCore.Razor.VSCode.FunctionalTest/"
|
||||
},
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
|
||||
<Import Project="eng\MPack.props" />
|
||||
<Import Project="eng\targets\Npm.Common.props" Condition="'$(MSBuildProjectExtension)' == '.npmproj'" />
|
||||
|
||||
<Import
|
||||
Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageVersion Condition=" '$(PackageVersion)' == '' ">$(Version)</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="eng\MPack.targets" />
|
||||
<Import Project="eng\targets\Npm.Common.targets" Condition="'$(MSBuildProjectExtension)' == '.npmproj'" />
|
||||
|
||||
<!-- Workaround https://github.com/dotnet/cli/issues/10528 -->
|
||||
<PropertyGroup>
|
||||
|
|
|
@ -48,6 +48,10 @@ stages:
|
|||
name: NetCoreInternal-Pool
|
||||
queue: BuildPool.Windows.10.Amd64.VS2019
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Install Node 10.x
|
||||
inputs:
|
||||
versionSpec: 10.x
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Clear NuGet caches'
|
||||
condition: succeeded()
|
||||
|
@ -77,7 +81,7 @@ stages:
|
|||
chmod +x $HOME/bin/jq
|
||||
echo "##vso[task.prependpath]$HOME/bin"
|
||||
displayName: Install jq
|
||||
- script: ./eng/scripts/ci-source-build.sh --ci --configuration $(_BuildConfig)
|
||||
- script: ./eng/scripts/ci-source-build.sh --ci --configuration $(_BuildConfig) /p:BuildNodeJs=false
|
||||
displayName: Run ci-source-build.sh
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
|
@ -158,6 +162,10 @@ stages:
|
|||
- group: DotNet-Blob-Feed
|
||||
- group: DotNet-Symbol-Server-Pats
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Install Node 10.x
|
||||
inputs:
|
||||
versionSpec: 10.x
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Clear NuGet caches'
|
||||
condition: succeeded()
|
||||
|
@ -226,6 +234,10 @@ stages:
|
|||
release:
|
||||
_BuildConfig: Release
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Install Node 10.x
|
||||
inputs:
|
||||
versionSpec: 10.x
|
||||
- script: eng/common/cibuild.sh
|
||||
--configuration $(_BuildConfig)
|
||||
--prepareMachine
|
||||
|
@ -255,6 +267,10 @@ stages:
|
|||
release:
|
||||
_BuildConfig: Release
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Install Node 10.x
|
||||
inputs:
|
||||
versionSpec: 10.x
|
||||
- script: eng/common/cibuild.sh
|
||||
--configuration $(_BuildConfig)
|
||||
--prepareMachine
|
||||
|
|
|
@ -8,4 +8,24 @@
|
|||
Text="$(RazorExtensionVSIXName) was not generated."
|
||||
Condition="!Exists('$(VSSetupDir)$(Configuration)\$(RazorExtensionVSIXName)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_ZipLanguageServerBinaries" AfterTargets="Pack" Condition=" '$(OS)' == 'Windows_NT' ">
|
||||
<!-- Build a .zip file from each platform's directory and write it to 'artifacts' -->
|
||||
<PropertyGroup>
|
||||
<RidsPublishDir>$(ArtifactsDir)LanguageServer\$(Configuration)\</RidsPublishDir>
|
||||
<ZipOutputDir>$(RidsPublishDir)</ZipOutputDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<LanguageServiceBinaryDir Include="$([System.IO.Directory]::GetDirectories("$(RidsPublishDir)"))" />
|
||||
<LanguageServiceBinary Include="@(LanguageServiceBinaryDir)">
|
||||
<SourceDir>%(LanguageServiceBinaryDir.Identity)</SourceDir>
|
||||
<ZipFile>$(ZipOutputDir)RazorLanguageServer-%(LanguageServiceBinaryDir.Filename)-$(PackageVersion).zip</ZipFile>
|
||||
</LanguageServiceBinary>
|
||||
</ItemGroup>
|
||||
|
||||
<MakeDir Directories="$(ZipOutputDir)" />
|
||||
<Delete Files="%(LanguageServiceBinary.ZipFile)" />
|
||||
<Exec Command="powershell.exe -NonInteractive -command "&{ Write-Host "Writing %(LanguageServiceBinary.ZipFile)..." ; Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory('%(LanguageServiceBinary.SourceDir)', '%(LanguageServiceBinary.ZipFile)') }"" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<Project>
|
||||
|
||||
<Target Name="_PublishLanguageServerRids" AfterTargets="Pack" Condition=" '$(OS)' == 'Windows_NT' ">
|
||||
<PropertyGroup>
|
||||
<LanguageServerProject>$(MSBuildThisFileDirectory)..\src\Razor\src\Microsoft.AspNetCore.Razor.LanguageServer\Microsoft.AspNetCore.Razor.LanguageServer.csproj</LanguageServerProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<MSBuild Projects="$(LanguageServerProject)"
|
||||
Targets="PublishAllRids" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
|
@ -1,9 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<PropertyGroup>
|
||||
<BuildNodeJs Condition="'$(BuildNodeJs)' == ''">true</BuildNodeJs>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectToBuild Condition="'$(OS)'=='WINDOWS_NT' and '$(SdkTaskProjects)'==''" Include="$(MSBuildThisFileDirectory)..\src\Razor\Razor.sln" />
|
||||
|
||||
<!-- Exclude VSIX projects on non-Windows -->
|
||||
<ProjectToBuild Condition="'$(OS)'!='WINDOWS_NT' and '$(SdkTaskProjects)'==''" Include="$(MSBuildThisFileDirectory)..\src\Razor\Razor.Slim.sln" />
|
||||
|
||||
<NodeJsProjects Include="$(RepoRoot)src\Razor\src\**\*.npmproj" RestoreInParallel="false"/>
|
||||
<NodeJsProjects Include="$(RepoRoot)src\Razor\test\**\*.npmproj" RestoreInParallel="false" />
|
||||
|
||||
<ProjectToBuild Include="@(NodeJsProjects)" Condition="'$(BuildNodeJs)' == 'true'" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,14 @@
|
|||
<Project>
|
||||
<ItemGroup>
|
||||
<!--
|
||||
This is here to workaround flakiness in the NuGet SDK resolver in MSBuild.
|
||||
Arcade will run a pre-restore for these packages. This works more consistently than the SDK resolution which uses global.json.
|
||||
Without this here, we see regular failures with 'error MSB4236: The SDK 'Yarn.MSBuild' specified could not be found.'
|
||||
Since this project is evaluated before .npmproj files are loaded, this should cause the package to end
|
||||
up in the NuGet cache ahead of time.
|
||||
|
||||
This is not needed in source build.
|
||||
-->
|
||||
<PackageReference Condition="'$(DotNetBuildFromSource)' != 'true'" Include="Yarn.MSBuild" Version="1.15.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -77,6 +77,7 @@
|
|||
<PropertyGroup Label="Manual">
|
||||
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
|
||||
<MicrosoftBuildFrameworkPackageVersion>15.8.166</MicrosoftBuildFrameworkPackageVersion>
|
||||
<MicrosoftBuildLocatorPackageVersion>1.2.6</MicrosoftBuildLocatorPackageVersion>
|
||||
<MicrosoftBuildPackageVersion>15.8.166</MicrosoftBuildPackageVersion>
|
||||
<MicrosoftBuildUtilitiesCorePackageVersion>15.8.166</MicrosoftBuildUtilitiesCorePackageVersion>
|
||||
<MicrosoftCodeAnalysisCommonPackageVersion>3.3.0</MicrosoftCodeAnalysisCommonPackageVersion>
|
||||
|
@ -110,6 +111,8 @@
|
|||
<MoqPackageVersion>4.10.0</MoqPackageVersion>
|
||||
<!-- STOP!!! We need to reference the version of JSON that our HOSTS supprt. -->
|
||||
<NewtonsoftJsonPackageVersion>9.0.1</NewtonsoftJsonPackageVersion>
|
||||
<OmniSharpExtensionsLanguageServerPackageVersion>0.13.1</OmniSharpExtensionsLanguageServerPackageVersion>
|
||||
<OmniSharpMSBuildPackageVersion>1.33.0</OmniSharpMSBuildPackageVersion>
|
||||
<VS_NewtonsoftJsonPackageVersion>12.0.1</VS_NewtonsoftJsonPackageVersion>
|
||||
<VSMAC_NewtonsoftJsonPackageVersion>10.0.3</VSMAC_NewtonsoftJsonPackageVersion>
|
||||
<TEST_NewtonsoftJsonPackageVersion>12.0.1</TEST_NewtonsoftJsonPackageVersion>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const packageJson = args[0];
|
||||
const packageVersion = args[1];
|
||||
|
||||
const fileContent = fs.readFileSync(packageJson);
|
||||
const updatedContent = fileContent.toString().replace(/\"link:.*\"/g, `">=${packageVersion}"`);
|
||||
if (fileContent != updatedContent) {
|
||||
fs.writeFileSync(packageJson, updatedContent);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<NpmTestArgs>test</NpmTestArgs>
|
||||
<Configuration Condition="'$(Configuration)' == '' AND '$(ContinuousIntegrationBuild)' == 'true'">Release</Configuration>
|
||||
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
|
||||
<PackOnBuild>false</PackOnBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,126 @@
|
|||
<Project DefaultTargets="Build" InitialTargets="_CheckForInvalidConfiguration">
|
||||
|
||||
<!-- Version of this SDK is set in global.json -->
|
||||
<Sdk Name="Yarn.MSBuild" />
|
||||
|
||||
<PropertyGroup>
|
||||
<NormalizedPackageId>$(PackageId.Replace('@','').Replace('/','-'))</NormalizedPackageId>
|
||||
<PackageFileName>$(NormalizedPackageId)-$(PackageVersion).tgz</PackageFileName>
|
||||
<PackageJson>$(MSBuildProjectDirectory)\package.json</PackageJson>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)' == ''">$(MSBuildProjectDirectory)\obj\</BaseIntermediateOutputPath>
|
||||
<IntermediateOutputPath>$([MSBuild]::NormalizeDirectory('$(BaseIntermediateOutputPath)'))$(Configuration)\</IntermediateOutputPath>
|
||||
<InstallArgs Condition="'$(RestoreLockedMode)' == 'true'">$(InstallArgs) --frozen-lockfile</InstallArgs>
|
||||
<InstallArgs>$(InstallArgs) --ignore-engines</InstallArgs>
|
||||
<_BackupPackageJson>$(IntermediateOutputPath)$(MSBuildProjectName).package.json.bak</_BackupPackageJson>
|
||||
<BuildDependsOn>
|
||||
PrepareForBuild;
|
||||
ResolveProjectReferences;
|
||||
_Build;
|
||||
</BuildDependsOn>
|
||||
<NpmBuildArgs Condition="'$(NpmBuildArgs)' == ''">run build</NpmBuildArgs>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TSFiles Include="src\**\*.ts" />
|
||||
<TSFiles Include="test\**\*.ts" />
|
||||
<TSFiles Include="package.json" />
|
||||
<TSFiles Include="*.npmproj" />
|
||||
|
||||
<BuildOutputFiles Include="$(BaseIntermediateOutputPath)\build-sentinel" />
|
||||
<BuildOutputFiles Include="dist\**\*.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="_CheckForInvalidConfiguration">
|
||||
<Error Text="Missing expected property: PackageId" Condition="'$(IsPackable)' != 'false' and '$(PackageId)' == ''" />
|
||||
|
||||
<Exec ContinueOnError="true" Command="node -v" StandardOutputImportance="Low">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
|
||||
</Exec>
|
||||
|
||||
<Error Text="Building *.npmproj but NodeJS was not detected on path. Ensure NodeJS is on path or disable building NodeJS projects with /p:BuildNodeJs=false. Skipping NodeJS projects will also skip managed projects depending on them, including Components, Mvc and Analysers." Condition="'$(ErrorCode)' != '0'"/>
|
||||
</Target>
|
||||
|
||||
<Target Name="Restore">
|
||||
<Message Importance="High" Text="Running yarn install on $(MSBuildProjectFullPath)" />
|
||||
<Yarn Command="install --mutex network $(InstallArgs)" StandardOutputImportance="High" StandardErrorImportance="High" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PrepareForBuild">
|
||||
<MakeDir Directories="$(IntermediateOutputPath);$(PackageOutputPath)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="ResolveProjectReferences">
|
||||
<MSBuild Projects="@(ProjectReference)"
|
||||
BuildInParallel="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="GetBuildInputCacheFile">
|
||||
<Hash ItemsToHash="@(TSFiles)">
|
||||
<Output TaskParameter="HashResult" PropertyName="_TSFileHash" />
|
||||
</Hash>
|
||||
|
||||
<WriteLinesToFile
|
||||
Lines="$(_TSFileHash)"
|
||||
File="$(BaseIntermediateOutputPath)tsfiles.cache"
|
||||
Overwrite="True"
|
||||
WriteOnlyWhenDifferent="True" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)">
|
||||
<CallTarget Targets="_Pack" Condition="'$(PackOnBuild)' == 'true'" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_Build"
|
||||
Condition="'$(IsBuildable)' != 'false'"
|
||||
DependsOnTargets="GetBuildInputCacheFile"
|
||||
Inputs="@(TSFiles);$(BaseIntermediateOutputPath)tsfiles.cache"
|
||||
Outputs="@(BuildOutputFiles)">
|
||||
<Yarn Command="$(NpmBuildArgs)" StandardOutputImportance="High" StandardErrorImportance="High" />
|
||||
<WriteLinesToFile Overwrite="true" File="$(BaseIntermediateOutputPath)build-sentinel" />
|
||||
</Target>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackDependsOn>
|
||||
$(PackDependsOn);
|
||||
PrepareForBuild
|
||||
</PackDependsOn>
|
||||
<PackDependsOn Condition="'$(NoBuild)' != 'true'">
|
||||
$(PackDependsOn);
|
||||
Build;
|
||||
_Pack
|
||||
</PackDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_Pack" Condition="'$(IsPackable)' == 'true'"
|
||||
Inputs="@(TSFiles)"
|
||||
Outputs="$(PackageOutputPath)\$(PackageFileName)">
|
||||
|
||||
<PropertyGroup>
|
||||
<_PackageTargetPath>$(MSBuildProjectDirectory)\$(PackageFileName)</_PackageTargetPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Copy SourceFiles="$(PackageJson)" DestinationFiles="$(_BackupPackageJson)" />
|
||||
|
||||
<Yarn Command="version --no-git-tag-version --new-version $(PackageVersion)" />
|
||||
<Exec Command="node $(MSBuildThisFileDirectory)..\scripts\update-packagejson-links.js $(PackageJson) $(PackageVersion)" />
|
||||
<Yarn Command="pack --filename $(PackageFileName)" />
|
||||
|
||||
<Move SourceFiles="$(_PackageTargetPath)" DestinationFolder="$(PackageOutputPath)" />
|
||||
<Message Importance="High" Text="$(MSBuildProjectName) -> $(_PackageTargetPath)" />
|
||||
|
||||
<CallTarget Targets="_RestoreBackupPackageJsonFile" />
|
||||
<OnError ExecuteTargets="_RestoreBackupPackageJsonFile" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Pack" Condition="'$(IsPackable)' == 'true'" DependsOnTargets="$(PackDependsOn)" />
|
||||
|
||||
<Target Name="_RestoreBackupPackageJsonFile">
|
||||
<Move SourceFiles="$(_BackupPackageJson)" DestinationFiles="$(PackageJson)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Test" Condition="'$(IsTestProject)' == 'true'">
|
||||
<Message Importance="High" Text="Running npm tests for $(MSBuildProjectName)" />
|
||||
<Yarn Command="$(NpmTestArgs)" StandardOutputImportance="High" StandardErrorImportance="High" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
|
@ -18,6 +18,7 @@
|
|||
"version": "3.1.100-preview1-014024"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19461.7"
|
||||
"Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19461.7",
|
||||
"Yarn.MSBuild": "1.15.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,9 +86,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test.ComponentShim", "test\Microsoft.AspNetCore.Razor.Test.ComponentShim\Microsoft.AspNetCore.Razor.Test.ComponentShim.csproj", "{5B232E77-F0D3-4298-9A5D-D965788D7A79}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LiveShare.Razor", "src\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.csproj", "{20193C6A-8981-447F-99B3-120DD3B06279}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.LiveShare.Razor", "src\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.csproj", "{20193C6A-8981-447F-99B3-120DD3B06279}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LiveShare.Razor.Test", "test\Microsoft.VisualStudio.LiveShare.Razor.Test\Microsoft.VisualStudio.LiveShare.Razor.Test.csproj", "{9A27DD55-E8CD-4C03-A89B-A7348B787660}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.LiveShare.Razor.Test", "test\Microsoft.VisualStudio.LiveShare.Razor.Test\Microsoft.VisualStudio.LiveShare.Razor.Test.csproj", "{9A27DD55-E8CD-4C03-A89B-A7348B787660}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.LanguageServer.Common", "src\Microsoft.AspNetCore.Razor.LanguageServer.Common\Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj", "{F2B59848-345E-4ECB-ADDB-277F3C937B9C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.LanguageServer", "src\Microsoft.AspNetCore.Razor.LanguageServer\Microsoft.AspNetCore.Razor.LanguageServer.csproj", "{1D15867E-E50F-4107-92A4-BBC2EE6B088C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.LanguageServer.Common.Test", "test\Microsoft.AspNetCore.Razor.LanguageServer.Common.Test\Microsoft.AspNetCore.Razor.LanguageServer.Common.Test.csproj", "{6C8A42B5-B41C-4334-959F-684E647A24E1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.LanguageServer.Test", "test\Microsoft.AspNetCore.Razor.LanguageServer.Test\Microsoft.AspNetCore.Razor.LanguageServer.Test.csproj", "{FBAE9975-77BE-411B-A1A3-4790C8A367EF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.LanguageServer.Test.Common", "test\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj", "{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.OmniSharpPlugin.StrongNamed", "src\Microsoft.AspNetCore.Razor.OmniSharpPlugin.StrongNamed\Microsoft.AspNetCore.Razor.OmniSharpPlugin.StrongNamed.csproj", "{525CCD97-7E6E-404C-8CD3-736E5649C858}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.OmniSharpPlugin", "src\Microsoft.AspNetCore.Razor.OmniSharpPlugin\Microsoft.AspNetCore.Razor.OmniSharpPlugin.csproj", "{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.OmniSharpPlugin.Test", "test\Microsoft.AspNetCore.Razor.OmniSharpPlugin.Test\Microsoft.AspNetCore.Razor.OmniSharpPlugin.Test.csproj", "{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -410,6 +426,70 @@ Global
|
|||
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.ReleaseNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.ReleaseNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -454,6 +534,14 @@ Global
|
|||
{5B232E77-F0D3-4298-9A5D-D965788D7A79} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{20193C6A-8981-447F-99B3-120DD3B06279} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{9A27DD55-E8CD-4C03-A89B-A7348B787660} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{F2B59848-345E-4ECB-ADDB-277F3C937B9C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{1D15867E-E50F-4107-92A4-BBC2EE6B088C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{6C8A42B5-B41C-4334-959F-684E647A24E1} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{FBAE9975-77BE-411B-A1A3-4790C8A367EF} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{9D300F9A-1F78-45C9-B4BB-476EF12E40F8} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{525CCD97-7E6E-404C-8CD3-736E5649C858} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{305354FD-5ED7-4E89-8B1D-58FCCA3E08AD} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{4ED6CC87-11C4-4ECD-B9A1-AFC5C2DACABE} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}
|
||||
|
|
|
@ -1099,8 +1099,6 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
if (node.Source != null)
|
||||
{
|
||||
Debug.Assert(node.Source.Value.FilePath != null);
|
||||
|
||||
node.Source = new SourceSpan(
|
||||
node.Source.Value.FilePath,
|
||||
node.Source.Value.AbsoluteIndex,
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
public class AdhocLanguageServices : HostLanguageServices
|
||||
{
|
||||
private readonly HostWorkspaceServices _workspaceServices;
|
||||
private readonly IEnumerable<ILanguageService> _languageServices;
|
||||
|
||||
public AdhocLanguageServices(HostWorkspaceServices workspaceServices, IEnumerable<ILanguageService> languageServices)
|
||||
{
|
||||
if (workspaceServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspaceServices));
|
||||
}
|
||||
|
||||
if (languageServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(languageServices));
|
||||
}
|
||||
|
||||
_workspaceServices = workspaceServices;
|
||||
_languageServices = languageServices;
|
||||
}
|
||||
|
||||
public override HostWorkspaceServices WorkspaceServices => _workspaceServices;
|
||||
|
||||
public override string Language => RazorLanguage.Name;
|
||||
|
||||
public override TLanguageService GetService<TLanguageService>()
|
||||
{
|
||||
var service = _languageServices.OfType<TLanguageService>().FirstOrDefault();
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Razor language services not configured properly, missing language service '{typeof(TLanguageService).FullName}'.");
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
public class AdhocServices : HostServices
|
||||
{
|
||||
private readonly IEnumerable<IWorkspaceService> _workspaceServices;
|
||||
private readonly IEnumerable<ILanguageService> _razorLanguageServices;
|
||||
|
||||
private AdhocServices(IEnumerable<IWorkspaceService> workspaceServices, IEnumerable<ILanguageService> razorLanguageServices)
|
||||
{
|
||||
if (workspaceServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspaceServices));
|
||||
}
|
||||
|
||||
if (razorLanguageServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(razorLanguageServices));
|
||||
}
|
||||
|
||||
_workspaceServices = workspaceServices;
|
||||
_razorLanguageServices = razorLanguageServices;
|
||||
}
|
||||
|
||||
protected override HostWorkspaceServices CreateWorkspaceServices(Workspace workspace)
|
||||
{
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
return new AdhocWorkspaceServices(this, _workspaceServices, _razorLanguageServices, workspace);
|
||||
}
|
||||
|
||||
public static HostServices Create(IEnumerable<ILanguageService> razorLanguageServices)
|
||||
=> Create(Enumerable.Empty<IWorkspaceService>(), razorLanguageServices);
|
||||
|
||||
public static HostServices Create(IEnumerable<IWorkspaceService> workspaceServices, IEnumerable<ILanguageService> razorLanguageServices)
|
||||
=> new AdhocServices(workspaceServices, razorLanguageServices);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
public class AdhocWorkspaceServices : HostWorkspaceServices
|
||||
{
|
||||
private static readonly Workspace DefaultWorkspace = new AdhocWorkspace();
|
||||
|
||||
private readonly HostServices _hostServices;
|
||||
private readonly HostLanguageServices _razorLanguageServices;
|
||||
private readonly IEnumerable<IWorkspaceService> _workspaceServices;
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
public AdhocWorkspaceServices(
|
||||
HostServices hostServices,
|
||||
IEnumerable<IWorkspaceService> workspaceServices,
|
||||
IEnumerable<ILanguageService> languageServices,
|
||||
Workspace workspace)
|
||||
{
|
||||
if (hostServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostServices));
|
||||
}
|
||||
|
||||
if (workspaceServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspaceServices));
|
||||
}
|
||||
|
||||
if (languageServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(languageServices));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_hostServices = hostServices;
|
||||
_workspaceServices = workspaceServices;
|
||||
_workspace = workspace;
|
||||
|
||||
_razorLanguageServices = new AdhocLanguageServices(this, languageServices);
|
||||
}
|
||||
|
||||
public override HostServices HostServices => _hostServices;
|
||||
|
||||
public override Workspace Workspace => _workspace;
|
||||
|
||||
public override TWorkspaceService GetService<TWorkspaceService>()
|
||||
{
|
||||
var service = _workspaceServices.OfType<TWorkspaceService>().FirstOrDefault();
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
// Fallback to default host services to resolve roslyn specific features.
|
||||
service = DefaultWorkspace.Services.GetService<TWorkspaceService>();
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
public override HostLanguageServices GetLanguageServices(string languageName)
|
||||
{
|
||||
if (languageName == RazorLanguage.Name)
|
||||
{
|
||||
return _razorLanguageServices;
|
||||
}
|
||||
|
||||
// Fallback to default host services to resolve roslyn specific features.
|
||||
return DefaultWorkspace.Services.GetLanguageServices(languageName);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedLanguages => new[] { RazorLanguage.Name };
|
||||
|
||||
public override bool IsSupported(string languageName) => languageName == RazorLanguage.Name;
|
||||
|
||||
public override IEnumerable<TLanguageService> FindLanguageServices<TLanguageService>(MetadataFilter filter) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// 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 System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Components;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
|
||||
[ExportCustomProjectEngineFactory("Default", SupportsSerialization = true)]
|
||||
internal class DefaultProjectEngineFactory : IProjectEngineFactory
|
||||
{
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
CompilerFeatures.Register(b);
|
||||
|
||||
configure?.Invoke(b);
|
||||
|
||||
// See comments on MangleClassNames
|
||||
var componentDocumentClassifier = b.Features.OfType<ComponentDocumentClassifierPass>().FirstOrDefault();
|
||||
if (componentDocumentClassifier != null)
|
||||
{
|
||||
componentDocumentClassifier.MangleClassNames = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// 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 System.Net;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
public class FilePathNormalizer
|
||||
{
|
||||
public string NormalizeDirectory(string directoryFilePath)
|
||||
{
|
||||
var normalized = Normalize(directoryFilePath);
|
||||
|
||||
if (!normalized.EndsWith("/"))
|
||||
{
|
||||
normalized += '/';
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public string Normalize(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
return "/";
|
||||
}
|
||||
|
||||
var decodedPath = WebUtility.UrlDecode(filePath);
|
||||
var normalized = decodedPath.Replace('\\', '/');
|
||||
|
||||
if (normalized[0] != '/')
|
||||
{
|
||||
normalized = '/' + normalized;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public string GetDirectory(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new InvalidOperationException(filePath);
|
||||
}
|
||||
|
||||
var normalizedPath = Normalize(filePath);
|
||||
var lastSeparatorIndex = normalizedPath.LastIndexOf('/');
|
||||
|
||||
var directory = normalizedPath.Substring(0, lastSeparatorIndex + 1);
|
||||
return directory;
|
||||
}
|
||||
|
||||
public bool FilePathsEquivalent(string filePath1, string filePath2)
|
||||
{
|
||||
var normalizedFilePath1 = Normalize(filePath1);
|
||||
var normalizedFilePath2 = Normalize(filePath2);
|
||||
|
||||
return FilePathComparer.Instance.Equals(normalizedFilePath1, normalizedFilePath2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class HostDocumentComparer : IEqualityComparer<HostDocument>
|
||||
{
|
||||
public static readonly HostDocumentComparer Instance = new HostDocumentComparer();
|
||||
|
||||
private HostDocumentComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Equals(HostDocument x, HostDocument y)
|
||||
{
|
||||
if (x.FileKind != y.FileKind)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FilePathComparer.Instance.Equals(x.FilePath, y.FilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FilePathComparer.Instance.Equals(x.TargetPath, y.TargetPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetHashCode(HostDocument hostDocument)
|
||||
{
|
||||
var combiner = HashCodeCombiner.Start();
|
||||
combiner.Add(hostDocument.FilePath, FilePathComparer.Instance);
|
||||
combiner.Add(hostDocument.TargetPath, FilePathComparer.Instance);
|
||||
combiner.Add(hostDocument.FileKind);
|
||||
|
||||
return combiner.CombinedHash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains common assets that are used in the Razor language server and other assemblies.</Description>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,40 @@
|
|||
// 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.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
public class ProjectEngineFactories
|
||||
{
|
||||
public static readonly Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] Factories =
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[]
|
||||
{
|
||||
// Razor based configurations
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new DefaultProjectEngineFactory(),
|
||||
new ExportCustomProjectEngineFactoryAttribute("Default") { SupportsSerialization = true }),
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new ProjectEngineFactory_1_0(),
|
||||
new ExportCustomProjectEngineFactoryAttribute("MVC-1.0") { SupportsSerialization = true }),
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new ProjectEngineFactory_1_1(),
|
||||
new ExportCustomProjectEngineFactoryAttribute("MVC-1.1") { SupportsSerialization = true }),
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new ProjectEngineFactory_2_0(),
|
||||
new ExportCustomProjectEngineFactoryAttribute("MVC-2.0") { SupportsSerialization = true }),
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new ProjectEngineFactory_2_1(),
|
||||
new ExportCustomProjectEngineFactoryAttribute("MVC-2.1") { SupportsSerialization = true }),
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new ProjectEngineFactory_3_0(),
|
||||
new ExportCustomProjectEngineFactoryAttribute("MVC-3.0") { SupportsSerialization = true }),
|
||||
|
||||
// Unsupported (Legacy/System.Web.Razor)
|
||||
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
|
||||
() => new ProjectEngineFactory_Unsupported(),
|
||||
new ExportCustomProjectEngineFactoryAttribute(UnsupportedRazorConfiguration.Instance.ConfigurationName) { SupportsSerialization = true }),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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 System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class ProjectEngineFactory_1_0 : IProjectEngineFactory
|
||||
{
|
||||
private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X";
|
||||
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
// Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
|
||||
var assemblyName = new AssemblyName(typeof(RazorProjectEngine).Assembly.FullName);
|
||||
assemblyName.Name = AssemblyName;
|
||||
|
||||
var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
|
||||
var initializer = extension.CreateInitializer();
|
||||
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
initializer.Initialize(b);
|
||||
configure?.Invoke(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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 System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class ProjectEngineFactory_1_1 : IProjectEngineFactory
|
||||
{
|
||||
private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X";
|
||||
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
// Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
|
||||
var assemblyName = new AssemblyName(typeof(RazorProjectEngine).Assembly.FullName);
|
||||
assemblyName.Name = AssemblyName;
|
||||
|
||||
var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
|
||||
var initializer = extension.CreateInitializer();
|
||||
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
initializer.Initialize(b);
|
||||
configure?.Invoke(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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 System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class ProjectEngineFactory_2_0 : IProjectEngineFactory
|
||||
{
|
||||
private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X";
|
||||
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
// Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
|
||||
var assemblyName = new AssemblyName(typeof(RazorProjectEngine).Assembly.FullName);
|
||||
assemblyName.Name = AssemblyName;
|
||||
|
||||
var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
|
||||
var initializer = extension.CreateInitializer();
|
||||
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
initializer.Initialize(b);
|
||||
configure?.Invoke(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// 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 System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class ProjectEngineFactory_2_1 : IProjectEngineFactory
|
||||
{
|
||||
private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X";
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
// Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
|
||||
var assemblyName = new AssemblyName(typeof(RazorProjectEngine).Assembly.FullName);
|
||||
assemblyName.Name = AssemblyName;
|
||||
|
||||
var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
|
||||
var initializer = extension.CreateInitializer();
|
||||
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
initializer.Initialize(b);
|
||||
configure?.Invoke(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// 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 System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Components;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class ProjectEngineFactory_3_0 : IProjectEngineFactory
|
||||
{
|
||||
private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions";
|
||||
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
// Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
|
||||
var assemblyName = new AssemblyName(typeof(RazorProjectEngine).Assembly.FullName);
|
||||
assemblyName.Name = AssemblyName;
|
||||
|
||||
var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
|
||||
var initializer = extension.CreateInitializer();
|
||||
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, b =>
|
||||
{
|
||||
CompilerFeatures.Register(b);
|
||||
|
||||
initializer.Initialize(b);
|
||||
configure?.Invoke(b);
|
||||
|
||||
var componentDocumentClassifier = b.Features.OfType<ComponentDocumentClassifierPass>().FirstOrDefault();
|
||||
if (componentDocumentClassifier != null)
|
||||
{
|
||||
componentDocumentClassifier.MangleClassNames = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// 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 System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class ProjectEngineFactory_Unsupported : IProjectEngineFactory
|
||||
{
|
||||
public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
|
||||
{
|
||||
return RazorProjectEngine.Create(configuration, fileSystem, builder =>
|
||||
{
|
||||
var csharpLoweringIndex = builder.Phases.IndexOf(builder.Phases.OfType<IRazorCSharpLoweringPhase>().Single());
|
||||
builder.Phases[csharpLoweringIndex] = new UnsupportedCSharpLoweringPhase();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal static class ProjectSerializationFormat
|
||||
{
|
||||
public static string Version => "0.2";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: InternalsVisibleTo("rzls, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.OmniSharpPlugin.StrongNamed, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.LanguageServer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.LanguageServer.Common.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
@ -0,0 +1,39 @@
|
|||
// 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.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal static class RazorCodeDocumentExtensions
|
||||
{
|
||||
private static readonly object UnsupportedKey = new object();
|
||||
|
||||
public static bool IsUnsupported(this RazorCodeDocument document)
|
||||
{
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(document));
|
||||
}
|
||||
|
||||
var unsupportedObj = document.Items[UnsupportedKey];
|
||||
if (unsupportedObj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool)unsupportedObj;
|
||||
}
|
||||
|
||||
public static void SetUnsupported(this RazorCodeDocument document)
|
||||
{
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(document));
|
||||
}
|
||||
|
||||
document.Items[UnsupportedKey] = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal sealed class DocumentSnapshotHandle
|
||||
{
|
||||
public DocumentSnapshotHandle(
|
||||
string filePath,
|
||||
string targetPath,
|
||||
string fileKind)
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
if (targetPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetPath));
|
||||
}
|
||||
|
||||
if (fileKind == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fileKind));
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
TargetPath = targetPath;
|
||||
FileKind = fileKind;
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public string TargetPath { get; }
|
||||
|
||||
public string FileKind { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
// FullProjectSnapshotHandle exists in order to allow ProjectSnapshots to be serialized and then deserialized.
|
||||
// It has named "Full" because there's a similar concept in core Razor of a ProjectSnapshotHandle. In Razor's
|
||||
// case that handle doesn't contain ProjectWorkspaceState information
|
||||
internal sealed class FullProjectSnapshotHandle
|
||||
{
|
||||
public FullProjectSnapshotHandle(
|
||||
string filePath,
|
||||
RazorConfiguration configuration,
|
||||
string rootNamespace,
|
||||
ProjectWorkspaceState projectWorkspaceState,
|
||||
IReadOnlyList<DocumentSnapshotHandle> documents)
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
if (documents == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documents));
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
Configuration = configuration;
|
||||
RootNamespace = rootNamespace;
|
||||
ProjectWorkspaceState = projectWorkspaceState;
|
||||
Documents = documents;
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public RazorConfiguration Configuration { get; }
|
||||
|
||||
public string RootNamespace { get; }
|
||||
|
||||
public ProjectWorkspaceState ProjectWorkspaceState { get; }
|
||||
|
||||
public IReadOnlyList<DocumentSnapshotHandle> Documents { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class FullProjectSnapshotHandleJsonConverter : JsonConverter
|
||||
{
|
||||
public static readonly FullProjectSnapshotHandleJsonConverter Instance = new FullProjectSnapshotHandleJsonConverter();
|
||||
private const string SerializationFormatPropertyName = "SerializationFormat";
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(FullProjectSnapshotHandle).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = JObject.Load(reader);
|
||||
|
||||
// We need to add a serialization format to the project response to indicate that this version of the code is compatible with what's being serialized.
|
||||
// This scenario typically happens when a user has an incompatible serialized project snapshot but is using the latest Razor bits.
|
||||
|
||||
if (!obj.TryGetValue(SerializationFormatPropertyName, out var serializationFormatToken))
|
||||
{
|
||||
// Pre-serialization format release.
|
||||
return null;
|
||||
}
|
||||
|
||||
var serializationFormat = serializationFormatToken.Value<string>();
|
||||
if (serializationFormat != ProjectSerializationFormat.Version)
|
||||
{
|
||||
// Unknown serialization format.
|
||||
return null;
|
||||
}
|
||||
|
||||
var filePath = obj[nameof(FullProjectSnapshotHandle.FilePath)].Value<string>();
|
||||
var configuration = obj[nameof(FullProjectSnapshotHandle.Configuration)].ToObject<RazorConfiguration>(serializer);
|
||||
var rootNamespace = obj[nameof(FullProjectSnapshotHandle.RootNamespace)].ToObject<string>(serializer);
|
||||
var projectWorkspaceState = obj[nameof(FullProjectSnapshotHandle.ProjectWorkspaceState)].ToObject<ProjectWorkspaceState>(serializer);
|
||||
var documents = obj[nameof(FullProjectSnapshotHandle.Documents)].ToObject<DocumentSnapshotHandle[]>(serializer);
|
||||
|
||||
return new FullProjectSnapshotHandle(filePath, configuration, rootNamespace, projectWorkspaceState, documents);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var handle = (FullProjectSnapshotHandle)value;
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.FilePath));
|
||||
writer.WriteValue(handle.FilePath);
|
||||
|
||||
if (handle.Configuration == null)
|
||||
{
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.Configuration));
|
||||
writer.WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.Configuration));
|
||||
serializer.Serialize(writer, handle.Configuration);
|
||||
}
|
||||
|
||||
if (handle.ProjectWorkspaceState == null)
|
||||
{
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.ProjectWorkspaceState));
|
||||
writer.WriteNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.ProjectWorkspaceState));
|
||||
serializer.Serialize(writer, handle.ProjectWorkspaceState);
|
||||
}
|
||||
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.RootNamespace));
|
||||
writer.WriteValue(handle.RootNamespace);
|
||||
|
||||
writer.WritePropertyName(nameof(FullProjectSnapshotHandle.Documents));
|
||||
serializer.Serialize(writer, handle.Documents);
|
||||
|
||||
writer.WritePropertyName(SerializationFormatPropertyName);
|
||||
writer.WriteValue(ProjectSerializationFormat.Version);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal static class JsonConverterCollectionExtensions
|
||||
{
|
||||
public static readonly IReadOnlyList<JsonConverter> RazorConverters = new List<JsonConverter>()
|
||||
{
|
||||
TagHelperDescriptorJsonConverter.Instance,
|
||||
RazorDiagnosticJsonConverter.Instance,
|
||||
RazorExtensionJsonConverter.Instance,
|
||||
RazorConfigurationJsonConverter.Instance,
|
||||
FullProjectSnapshotHandleJsonConverter.Instance,
|
||||
ProjectSnapshotJsonConverter.Instance,
|
||||
};
|
||||
|
||||
public static void RegisterRazorConverters(this JsonConverterCollection collection)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
}
|
||||
|
||||
for (var i = 0; i < RazorConverters.Count; i++)
|
||||
{
|
||||
collection.Add(RazorConverters[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class ProjectSnapshotJsonConverter : JsonConverter
|
||||
{
|
||||
public static readonly ProjectSnapshotJsonConverter Instance = new ProjectSnapshotJsonConverter();
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(ProjectSnapshot).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var project = (ProjectSnapshot)value;
|
||||
|
||||
var documents = new List<DocumentSnapshotHandle>();
|
||||
foreach (var documentFilePath in project.DocumentFilePaths)
|
||||
{
|
||||
var document = project.GetDocument(documentFilePath);
|
||||
var documentHandle = new DocumentSnapshotHandle(document.FilePath, document.TargetPath, document.FileKind);
|
||||
documents.Add(documentHandle);
|
||||
}
|
||||
|
||||
var handle = new FullProjectSnapshotHandle(project.FilePath, project.Configuration, project.RootNamespace, project.ProjectWorkspaceState, documents);
|
||||
|
||||
FullProjectSnapshotHandleJsonConverter.Instance.WriteJson(writer, handle, serializer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// 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.
|
||||
|
||||
// This class is a copy from the Razor repo.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class RazorConfigurationJsonConverter : JsonConverter
|
||||
{
|
||||
public static readonly RazorConfigurationJsonConverter Instance = new RazorConfigurationJsonConverter();
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(RazorConfiguration).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = JObject.Load(reader);
|
||||
var configurationName = obj[nameof(RazorConfiguration.ConfigurationName)].Value<string>();
|
||||
var languageVersionValue = obj[nameof(RazorConfiguration.LanguageVersion)].Value<string>();
|
||||
var extensions = obj[nameof(RazorConfiguration.Extensions)].ToObject<RazorExtension[]>(serializer);
|
||||
|
||||
if (!RazorLanguageVersion.TryParse(languageVersionValue, out var languageVersion))
|
||||
{
|
||||
languageVersion = RazorLanguageVersion.Version_2_1;
|
||||
}
|
||||
|
||||
return RazorConfiguration.Create(languageVersion, configurationName, extensions);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var configuration = (RazorConfiguration)value;
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(RazorConfiguration.ConfigurationName));
|
||||
writer.WriteValue(configuration.ConfigurationName);
|
||||
|
||||
writer.WritePropertyName(nameof(RazorConfiguration.LanguageVersion));
|
||||
if (configuration.LanguageVersion == RazorLanguageVersion.Experimental)
|
||||
{
|
||||
writer.WriteValue("Experimental");
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(configuration.LanguageVersion.ToString());
|
||||
}
|
||||
|
||||
writer.WritePropertyName(nameof(RazorConfiguration.Extensions));
|
||||
serializer.Serialize(writer, configuration.Extensions);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// 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.
|
||||
|
||||
// This class is a copy from the Razor repo.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class RazorDiagnosticJsonConverter : JsonConverter
|
||||
{
|
||||
public static readonly RazorDiagnosticJsonConverter Instance = new RazorDiagnosticJsonConverter();
|
||||
private const string RazorDiagnosticMessageKey = "Message";
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(RazorDiagnostic).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var diagnostic = JObject.Load(reader);
|
||||
var id = diagnostic[nameof(RazorDiagnostic.Id)].Value<string>();
|
||||
var severity = diagnostic[nameof(RazorDiagnostic.Severity)].Value<int>();
|
||||
var message = diagnostic[RazorDiagnosticMessageKey].Value<string>();
|
||||
|
||||
var span = diagnostic[nameof(RazorDiagnostic.Span)].Value<JObject>();
|
||||
var filePath = span[nameof(SourceSpan.FilePath)].Value<string>();
|
||||
var absoluteIndex = span[nameof(SourceSpan.AbsoluteIndex)].Value<int>();
|
||||
var lineIndex = span[nameof(SourceSpan.LineIndex)].Value<int>();
|
||||
var characterIndex = span[nameof(SourceSpan.CharacterIndex)].Value<int>();
|
||||
var length = span[nameof(SourceSpan.Length)].Value<int>();
|
||||
|
||||
var descriptor = new RazorDiagnosticDescriptor(id, () => message, (RazorDiagnosticSeverity)severity);
|
||||
var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length);
|
||||
|
||||
return RazorDiagnostic.Create(descriptor, sourceSpan);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var diagnostic = (RazorDiagnostic)value;
|
||||
|
||||
writer.WriteStartObject();
|
||||
WriteProperty(writer, nameof(RazorDiagnostic.Id), diagnostic.Id);
|
||||
WriteProperty(writer, nameof(RazorDiagnostic.Severity), (int)diagnostic.Severity);
|
||||
WriteProperty(writer, RazorDiagnosticMessageKey, diagnostic.GetMessage(CultureInfo.CurrentCulture));
|
||||
|
||||
writer.WritePropertyName(nameof(RazorDiagnostic.Span));
|
||||
writer.WriteStartObject();
|
||||
WriteProperty(writer, nameof(SourceSpan.FilePath), diagnostic.Span.FilePath);
|
||||
WriteProperty(writer, nameof(SourceSpan.AbsoluteIndex), diagnostic.Span.AbsoluteIndex);
|
||||
WriteProperty(writer, nameof(SourceSpan.LineIndex), diagnostic.Span.LineIndex);
|
||||
WriteProperty(writer, nameof(SourceSpan.CharacterIndex), diagnostic.Span.CharacterIndex);
|
||||
WriteProperty(writer, nameof(SourceSpan.Length), diagnostic.Span.Length);
|
||||
writer.WriteEndObject();
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private void WriteProperty<T>(JsonWriter writer, string key, T value)
|
||||
{
|
||||
writer.WritePropertyName(key);
|
||||
writer.WriteValue(value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
// This class is a copy from the Razor repo.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class RazorExtensionJsonConverter : JsonConverter
|
||||
{
|
||||
public static readonly RazorExtensionJsonConverter Instance = new RazorExtensionJsonConverter();
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(RazorExtension).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = JObject.Load(reader);
|
||||
var extensionName = obj[nameof(RazorExtension.ExtensionName)].Value<string>();
|
||||
|
||||
return new SerializedRazorExtension(extensionName);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var extension = (RazorExtension)value;
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(RazorExtension.ExtensionName));
|
||||
writer.WriteValue(extension.ExtensionName);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
// This class is a copy from the Razor repo.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class SerializedRazorExtension : RazorExtension
|
||||
{
|
||||
public SerializedRazorExtension(string extensionName)
|
||||
{
|
||||
if (extensionName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(extensionName));
|
||||
}
|
||||
|
||||
ExtensionName = extensionName;
|
||||
}
|
||||
|
||||
public override string ExtensionName { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,444 @@
|
|||
// 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.
|
||||
|
||||
// This class is a copy from the Razor repo.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization
|
||||
{
|
||||
internal class TagHelperDescriptorJsonConverter : JsonConverter
|
||||
{
|
||||
public static readonly TagHelperDescriptorJsonConverter Instance = new TagHelperDescriptorJsonConverter();
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(TagHelperDescriptor).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var descriptor = JObject.Load(reader);
|
||||
var descriptorKind = descriptor[nameof(TagHelperDescriptor.Kind)].Value<string>();
|
||||
var typeName = descriptor[nameof(TagHelperDescriptor.Name)].Value<string>();
|
||||
var assemblyName = descriptor[nameof(TagHelperDescriptor.AssemblyName)].Value<string>();
|
||||
var tagMatchingRules = descriptor[nameof(TagHelperDescriptor.TagMatchingRules)].Value<JArray>();
|
||||
var boundAttributes = descriptor[nameof(TagHelperDescriptor.BoundAttributes)].Value<JArray>();
|
||||
var childTags = descriptor[nameof(TagHelperDescriptor.AllowedChildTags)].Value<JArray>();
|
||||
var documentation = descriptor[nameof(TagHelperDescriptor.Documentation)].Value<string>();
|
||||
var tagOutputHint = descriptor[nameof(TagHelperDescriptor.TagOutputHint)].Value<string>();
|
||||
var caseSensitive = descriptor[nameof(TagHelperDescriptor.CaseSensitive)].Value<bool>();
|
||||
var diagnostics = descriptor[nameof(TagHelperDescriptor.Diagnostics)].Value<JArray>();
|
||||
var metadata = descriptor[nameof(TagHelperDescriptor.Metadata)].Value<JObject>();
|
||||
|
||||
var builder = TagHelperDescriptorBuilder.Create(descriptorKind, typeName, assemblyName);
|
||||
|
||||
builder.Documentation = documentation;
|
||||
builder.TagOutputHint = tagOutputHint;
|
||||
builder.CaseSensitive = caseSensitive;
|
||||
|
||||
foreach (var tagMatchingRule in tagMatchingRules)
|
||||
{
|
||||
var rule = tagMatchingRule.Value<JObject>();
|
||||
builder.TagMatchingRule(b => ReadTagMatchingRule(b, rule, serializer));
|
||||
}
|
||||
|
||||
foreach (var boundAttribute in boundAttributes)
|
||||
{
|
||||
var attribute = boundAttribute.Value<JObject>();
|
||||
builder.BindAttribute(b => ReadBoundAttribute(b, attribute, serializer));
|
||||
}
|
||||
|
||||
foreach (var childTag in childTags)
|
||||
{
|
||||
var tag = childTag.Value<JObject>();
|
||||
builder.AllowChildTag(childTagBuilder => ReadAllowedChildTag(childTagBuilder, tag, serializer));
|
||||
}
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
var diagnosticReader = diagnostic.CreateReader();
|
||||
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
|
||||
builder.Diagnostics.Add(diagnosticObject);
|
||||
}
|
||||
|
||||
var metadataReader = metadata.CreateReader();
|
||||
var metadataValue = serializer.Deserialize<Dictionary<string, string>>(metadataReader);
|
||||
foreach (var item in metadataValue)
|
||||
{
|
||||
builder.Metadata[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var tagHelper = (TagHelperDescriptor)value;
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.Kind));
|
||||
writer.WriteValue(tagHelper.Kind);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.Name));
|
||||
writer.WriteValue(tagHelper.Name);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.AssemblyName));
|
||||
writer.WriteValue(tagHelper.AssemblyName);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.Documentation));
|
||||
writer.WriteValue(tagHelper.Documentation);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.TagOutputHint));
|
||||
writer.WriteValue(tagHelper.TagOutputHint);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.CaseSensitive));
|
||||
writer.WriteValue(tagHelper.CaseSensitive);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.TagMatchingRules));
|
||||
writer.WriteStartArray();
|
||||
foreach (var ruleDescriptor in tagHelper.TagMatchingRules)
|
||||
{
|
||||
WriteTagMatchingRule(writer, ruleDescriptor, serializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.BoundAttributes));
|
||||
writer.WriteStartArray();
|
||||
foreach (var boundAttribute in tagHelper.BoundAttributes)
|
||||
{
|
||||
WriteBoundAttribute(writer, boundAttribute, serializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.AllowedChildTags));
|
||||
writer.WriteStartArray();
|
||||
foreach (var allowedChildTag in tagHelper.AllowedChildTags)
|
||||
{
|
||||
WriteAllowedChildTags(writer, allowedChildTag, serializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.Diagnostics));
|
||||
serializer.Serialize(writer, tagHelper.Diagnostics);
|
||||
|
||||
writer.WritePropertyName(nameof(TagHelperDescriptor.Metadata));
|
||||
WriteMetadata(writer, tagHelper.Metadata);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteAllowedChildTags(JsonWriter writer, AllowedChildTagDescriptor allowedChildTag, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Name));
|
||||
writer.WriteValue(allowedChildTag.Name);
|
||||
|
||||
writer.WritePropertyName(nameof(AllowedChildTagDescriptor.DisplayName));
|
||||
writer.WriteValue(allowedChildTag.DisplayName);
|
||||
|
||||
writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Diagnostics));
|
||||
serializer.Serialize(writer, allowedChildTag.Diagnostics);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteBoundAttribute(JsonWriter writer, BoundAttributeDescriptor boundAttribute, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.Kind));
|
||||
writer.WriteValue(boundAttribute.Kind);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.Name));
|
||||
writer.WriteValue(boundAttribute.Name);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.TypeName));
|
||||
writer.WriteValue(boundAttribute.TypeName);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum));
|
||||
writer.WriteValue(boundAttribute.IsEnum);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix));
|
||||
writer.WriteValue(boundAttribute.IndexerNamePrefix);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName));
|
||||
writer.WriteValue(boundAttribute.IndexerTypeName);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation));
|
||||
writer.WriteValue(boundAttribute.Documentation);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics));
|
||||
serializer.Serialize(writer, boundAttribute.Diagnostics);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.Metadata));
|
||||
WriteMetadata(writer, boundAttribute.Metadata);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeDescriptor.BoundAttributeParameters));
|
||||
writer.WriteStartArray();
|
||||
foreach (var boundAttributeParameter in boundAttribute.BoundAttributeParameters)
|
||||
{
|
||||
WriteBoundAttributeParameter(writer, boundAttributeParameter, serializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteBoundAttributeParameter(JsonWriter writer, BoundAttributeParameterDescriptor boundAttributeParameter, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Kind));
|
||||
writer.WriteValue(boundAttributeParameter.Kind);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Name));
|
||||
writer.WriteValue(boundAttributeParameter.Name);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.TypeName));
|
||||
writer.WriteValue(boundAttributeParameter.TypeName);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.IsEnum));
|
||||
writer.WriteValue(boundAttributeParameter.IsEnum);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Documentation));
|
||||
writer.WriteValue(boundAttributeParameter.Documentation);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Diagnostics));
|
||||
serializer.Serialize(writer, boundAttributeParameter.Diagnostics);
|
||||
|
||||
writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Metadata));
|
||||
WriteMetadata(writer, boundAttributeParameter.Metadata);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteMetadata(JsonWriter writer, IReadOnlyDictionary<string, string> metadata)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
foreach (var kvp in metadata)
|
||||
{
|
||||
writer.WritePropertyName(kvp.Key);
|
||||
writer.WriteValue(kvp.Value);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteTagMatchingRule(JsonWriter writer, TagMatchingRuleDescriptor ruleDescriptor, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagName));
|
||||
writer.WriteValue(ruleDescriptor.TagName);
|
||||
|
||||
writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.ParentTag));
|
||||
writer.WriteValue(ruleDescriptor.ParentTag);
|
||||
|
||||
writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagStructure));
|
||||
writer.WriteValue(ruleDescriptor.TagStructure);
|
||||
|
||||
writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Attributes));
|
||||
writer.WriteStartArray();
|
||||
foreach (var requiredAttribute in ruleDescriptor.Attributes)
|
||||
{
|
||||
WriteRequiredAttribute(writer, requiredAttribute, serializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Diagnostics));
|
||||
serializer.Serialize(writer, ruleDescriptor.Diagnostics);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void WriteRequiredAttribute(JsonWriter writer, RequiredAttributeDescriptor requiredAttribute, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Name));
|
||||
writer.WriteValue(requiredAttribute.Name);
|
||||
|
||||
writer.WritePropertyName(nameof(RequiredAttributeDescriptor.NameComparison));
|
||||
writer.WriteValue(requiredAttribute.NameComparison);
|
||||
|
||||
writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Value));
|
||||
writer.WriteValue(requiredAttribute.Value);
|
||||
|
||||
writer.WritePropertyName(nameof(RequiredAttributeDescriptor.ValueComparison));
|
||||
writer.WriteValue(requiredAttribute.ValueComparison);
|
||||
|
||||
writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Diagnostics));
|
||||
serializer.Serialize(writer, requiredAttribute.Diagnostics);
|
||||
|
||||
writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Metadata));
|
||||
WriteMetadata(writer, requiredAttribute.Metadata);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static void ReadTagMatchingRule(TagMatchingRuleDescriptorBuilder builder, JObject rule, JsonSerializer serializer)
|
||||
{
|
||||
var tagName = rule[nameof(TagMatchingRuleDescriptor.TagName)].Value<string>();
|
||||
var attributes = rule[nameof(TagMatchingRuleDescriptor.Attributes)].Value<JArray>();
|
||||
var parentTag = rule[nameof(TagMatchingRuleDescriptor.ParentTag)].Value<string>();
|
||||
var tagStructure = rule[nameof(TagMatchingRuleDescriptor.TagStructure)].Value<int>();
|
||||
var diagnostics = rule[nameof(TagMatchingRuleDescriptor.Diagnostics)].Value<JArray>();
|
||||
|
||||
builder.TagName = tagName;
|
||||
builder.ParentTag = parentTag;
|
||||
builder.TagStructure = (TagStructure)tagStructure;
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
var attibuteValue = attribute.Value<JObject>();
|
||||
builder.Attribute(b => ReadRequiredAttribute(b, attibuteValue, serializer));
|
||||
}
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
var diagnosticReader = diagnostic.CreateReader();
|
||||
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
|
||||
builder.Diagnostics.Add(diagnosticObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadRequiredAttribute(RequiredAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer)
|
||||
{
|
||||
var name = attribute[nameof(RequiredAttributeDescriptor.Name)].Value<string>();
|
||||
var nameComparison = attribute[nameof(RequiredAttributeDescriptor.NameComparison)].Value<int>();
|
||||
var value = attribute[nameof(RequiredAttributeDescriptor.Value)].Value<string>();
|
||||
var valueComparison = attribute[nameof(RequiredAttributeDescriptor.ValueComparison)].Value<int>();
|
||||
var diagnostics = attribute[nameof(RequiredAttributeDescriptor.Diagnostics)].Value<JArray>();
|
||||
var metadata = attribute[nameof(RequiredAttributeDescriptor.Metadata)].Value<JObject>();
|
||||
|
||||
builder.Name = name;
|
||||
builder.NameComparisonMode = (RequiredAttributeDescriptor.NameComparisonMode)nameComparison;
|
||||
builder.Value = value;
|
||||
builder.ValueComparisonMode = (RequiredAttributeDescriptor.ValueComparisonMode)valueComparison;
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
var diagnosticReader = diagnostic.CreateReader();
|
||||
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
|
||||
builder.Diagnostics.Add(diagnosticObject);
|
||||
}
|
||||
|
||||
var metadataReader = metadata.CreateReader();
|
||||
var metadataValue = serializer.Deserialize<Dictionary<string, string>>(metadataReader);
|
||||
foreach (var item in metadataValue)
|
||||
{
|
||||
builder.Metadata[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadAllowedChildTag(AllowedChildTagDescriptorBuilder builder, JObject childTag, JsonSerializer serializer)
|
||||
{
|
||||
var name = childTag[nameof(AllowedChildTagDescriptor.Name)].Value<string>();
|
||||
var displayName = childTag[nameof(AllowedChildTagDescriptor.DisplayName)].Value<string>();
|
||||
var diagnostics = childTag[nameof(AllowedChildTagDescriptor.Diagnostics)].Value<JArray>();
|
||||
|
||||
builder.Name = name;
|
||||
builder.DisplayName = displayName;
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
var diagnosticReader = diagnostic.CreateReader();
|
||||
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
|
||||
builder.Diagnostics.Add(diagnosticObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadBoundAttribute(BoundAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer)
|
||||
{
|
||||
var descriptorKind = attribute[nameof(BoundAttributeDescriptor.Kind)].Value<string>();
|
||||
var name = attribute[nameof(BoundAttributeDescriptor.Name)].Value<string>();
|
||||
var typeName = attribute[nameof(BoundAttributeDescriptor.TypeName)].Value<string>();
|
||||
var isEnum = attribute[nameof(BoundAttributeDescriptor.IsEnum)].Value<bool>();
|
||||
var indexerNamePrefix = attribute[nameof(BoundAttributeDescriptor.IndexerNamePrefix)].Value<string>();
|
||||
var indexerTypeName = attribute[nameof(BoundAttributeDescriptor.IndexerTypeName)].Value<string>();
|
||||
var documentation = attribute[nameof(BoundAttributeDescriptor.Documentation)].Value<string>();
|
||||
var diagnostics = attribute[nameof(BoundAttributeDescriptor.Diagnostics)].Value<JArray>();
|
||||
var metadata = attribute[nameof(BoundAttributeDescriptor.Metadata)].Value<JObject>();
|
||||
var boundAttributeParameters = attribute[nameof(BoundAttributeDescriptor.BoundAttributeParameters)].Value<JArray>();
|
||||
|
||||
builder.Name = name;
|
||||
builder.TypeName = typeName;
|
||||
builder.Documentation = documentation;
|
||||
|
||||
if (indexerNamePrefix != null)
|
||||
{
|
||||
builder.AsDictionary(indexerNamePrefix, indexerTypeName);
|
||||
}
|
||||
|
||||
if (isEnum)
|
||||
{
|
||||
builder.IsEnum = true;
|
||||
}
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
var diagnosticReader = diagnostic.CreateReader();
|
||||
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
|
||||
builder.Diagnostics.Add(diagnosticObject);
|
||||
}
|
||||
|
||||
var metadataReader = metadata.CreateReader();
|
||||
var metadataValue = serializer.Deserialize<Dictionary<string, string>>(metadataReader);
|
||||
foreach (var item in metadataValue)
|
||||
{
|
||||
builder.Metadata[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
foreach (var boundAttributeParameter in boundAttributeParameters)
|
||||
{
|
||||
var parameter = boundAttributeParameter.Value<JObject>();
|
||||
builder.BindAttributeParameter(b => ReadBoundAttributeParameter(b, parameter, serializer));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadBoundAttributeParameter(BoundAttributeParameterDescriptorBuilder builder, JObject parameter, JsonSerializer serializer)
|
||||
{
|
||||
var descriptorKind = parameter[nameof(BoundAttributeParameterDescriptor.Kind)].Value<string>();
|
||||
var name = parameter[nameof(BoundAttributeParameterDescriptor.Name)].Value<string>();
|
||||
var typeName = parameter[nameof(BoundAttributeParameterDescriptor.TypeName)].Value<string>();
|
||||
var isEnum = parameter[nameof(BoundAttributeParameterDescriptor.IsEnum)].Value<bool>();
|
||||
var documentation = parameter[nameof(BoundAttributeParameterDescriptor.Documentation)].Value<string>();
|
||||
var diagnostics = parameter[nameof(BoundAttributeParameterDescriptor.Diagnostics)].Value<JArray>();
|
||||
var metadata = parameter[nameof(BoundAttributeParameterDescriptor.Metadata)].Value<JObject>();
|
||||
|
||||
builder.Name = name;
|
||||
builder.TypeName = typeName;
|
||||
builder.Documentation = documentation;
|
||||
|
||||
if (isEnum)
|
||||
{
|
||||
builder.IsEnum = true;
|
||||
}
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
var diagnosticReader = diagnostic.CreateReader();
|
||||
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
|
||||
builder.Diagnostics.Add(diagnosticObject);
|
||||
}
|
||||
|
||||
var metadataReader = metadata.CreateReader();
|
||||
var metadataValue = serializer.Deserialize<Dictionary<string, string>>(metadataReader);
|
||||
foreach (var item in metadataValue)
|
||||
{
|
||||
builder.Metadata[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class UnsupportedCSharpLoweringPhase : RazorEnginePhaseBase, IRazorCSharpLoweringPhase
|
||||
{
|
||||
internal const string UnsupportedDisclaimer = "// Razor CSharp output is not supported for this project's version of Razor.";
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var documentNode = codeDocument.GetDocumentIntermediateNode();
|
||||
ThrowForMissingDocumentDependency(documentNode);
|
||||
|
||||
var cSharpDocument = RazorCSharpDocument.Create(
|
||||
UnsupportedDisclaimer,
|
||||
documentNode.Options,
|
||||
Enumerable.Empty<RazorDiagnostic>());
|
||||
codeDocument.SetCSharpDocument(cSharpDocument);
|
||||
codeDocument.SetUnsupported();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
public static class UnsupportedRazorConfiguration
|
||||
{
|
||||
public static readonly RazorConfiguration Instance = RazorConfiguration.Create(
|
||||
RazorLanguageVersion.Version_1_0,
|
||||
"UnsupportedRazor",
|
||||
new[] { new UnsupportedRazorExtension("UnsupportedRazorExtension"), });
|
||||
|
||||
private class UnsupportedRazorExtension : RazorExtension
|
||||
{
|
||||
public UnsupportedRazorExtension(string extensionName)
|
||||
{
|
||||
if (extensionName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(extensionName));
|
||||
}
|
||||
|
||||
ExtensionName = extensionName;
|
||||
}
|
||||
|
||||
public override string ExtensionName { get; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// 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.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Common
|
||||
{
|
||||
internal class VSCodeForegroundDispatcher : ForegroundDispatcher
|
||||
{
|
||||
public override bool IsForegroundThread => Thread.CurrentThread.ManagedThreadId == ForegroundTaskScheduler.Instance.ForegroundThreadId;
|
||||
|
||||
public override TaskScheduler ForegroundScheduler { get; } = ForegroundTaskScheduler.Instance;
|
||||
|
||||
public override TaskScheduler BackgroundScheduler { get; } = TaskScheduler.Default;
|
||||
|
||||
internal class ForegroundTaskScheduler : TaskScheduler
|
||||
{
|
||||
public static ForegroundTaskScheduler Instance = new ForegroundTaskScheduler();
|
||||
|
||||
private readonly Thread _thread;
|
||||
private readonly BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
|
||||
|
||||
private ForegroundTaskScheduler()
|
||||
{
|
||||
_thread = new Thread(ThreadStart)
|
||||
{
|
||||
IsBackground = true,
|
||||
};
|
||||
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public int ForegroundThreadId => _thread.ManagedThreadId;
|
||||
|
||||
public override int MaximumConcurrencyLevel => 1;
|
||||
|
||||
protected override void QueueTask(Task task) => _tasks.Add(task);
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
// If the task was previously queued it means that we're ensuring it's running on our single threaded scheduler.
|
||||
// Otherwise, we can't enforce that behavior and therefore need it to be re-queued before execution.
|
||||
if (taskWasPreviouslyQueued)
|
||||
{
|
||||
return TryExecuteTask(task);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Task> GetScheduledTasks() => _tasks.ToArray();
|
||||
|
||||
private void ThreadStart()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = _tasks.Take();
|
||||
TryExecuteTask(task);
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
// Fires when things shut down or in tests. Swallow thread abort exceptions and bail out.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class BackgroundDocumentGenerator : ProjectSnapshotChangeTrigger
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly DocumentVersionCache _documentVersionCache;
|
||||
private readonly IEnumerable<DocumentProcessedListener> _documentProcessedListeners;
|
||||
private readonly ILanguageServer _router;
|
||||
private readonly ILogger _logger;
|
||||
private readonly Dictionary<string, DocumentSnapshot> _work;
|
||||
private ProjectSnapshotManagerBase _projectManager;
|
||||
private Timer _timer;
|
||||
|
||||
public BackgroundDocumentGenerator(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
DocumentVersionCache documentVersionCache,
|
||||
IEnumerable<DocumentProcessedListener> documentProcessedListeners,
|
||||
ILanguageServer router,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (documentVersionCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentVersionCache));
|
||||
}
|
||||
|
||||
if (documentProcessedListeners == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentProcessedListeners));
|
||||
}
|
||||
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_documentVersionCache = documentVersionCache;
|
||||
_documentProcessedListeners = documentProcessedListeners;
|
||||
_router = router;
|
||||
_logger = loggerFactory.CreateLogger<BackgroundDocumentGenerator>();
|
||||
_work = new Dictionary<string, DocumentSnapshot>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
// For testing only
|
||||
protected BackgroundDocumentGenerator(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_logger = loggerFactory.CreateLogger<BackgroundDocumentGenerator>();
|
||||
_work = new Dictionary<string, DocumentSnapshot>(StringComparer.Ordinal);
|
||||
_documentProcessedListeners = Enumerable.Empty<DocumentProcessedListener>();
|
||||
}
|
||||
|
||||
public bool HasPendingNotifications
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_work)
|
||||
{
|
||||
return _work.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan Delay { get; set; } = TimeSpan.Zero;
|
||||
|
||||
public bool IsScheduledOrRunning => _timer != null;
|
||||
|
||||
// Used in tests to ensure we can control when background work starts.
|
||||
public ManualResetEventSlim BlockBackgroundWorkStart { get; set; }
|
||||
|
||||
// Used in tests to ensure we can know when background work finishes.
|
||||
public ManualResetEventSlim NotifyBackgroundWorkStarting { get; set; }
|
||||
|
||||
// Used in unit tests to ensure we can know when background has captured its current workload.
|
||||
public ManualResetEventSlim NotifyBackgroundCapturedWorkload { get; set; }
|
||||
|
||||
// Used in tests to ensure we can control when background work completes.
|
||||
public ManualResetEventSlim BlockBackgroundWorkCompleting { get; set; }
|
||||
|
||||
// Used in tests to ensure we can know when background work finishes.
|
||||
public ManualResetEventSlim NotifyBackgroundWorkCompleted { get; set; }
|
||||
|
||||
public override void Initialize(ProjectSnapshotManagerBase projectManager)
|
||||
{
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
}
|
||||
|
||||
_projectManager = projectManager;
|
||||
|
||||
_projectManager.Changed += ProjectSnapshotManager_Changed;
|
||||
|
||||
foreach (var documentProcessedListener in _documentProcessedListeners)
|
||||
{
|
||||
documentProcessedListener.Initialize(_projectManager);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartingBackgroundWork()
|
||||
{
|
||||
if (BlockBackgroundWorkStart != null)
|
||||
{
|
||||
BlockBackgroundWorkStart.Wait();
|
||||
BlockBackgroundWorkStart.Reset();
|
||||
}
|
||||
|
||||
if (NotifyBackgroundWorkStarting != null)
|
||||
{
|
||||
NotifyBackgroundWorkStarting.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCompletingBackgroundWork()
|
||||
{
|
||||
if (BlockBackgroundWorkCompleting != null)
|
||||
{
|
||||
BlockBackgroundWorkCompleting.Wait();
|
||||
BlockBackgroundWorkCompleting.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCompletedBackgroundWork()
|
||||
{
|
||||
if (NotifyBackgroundWorkCompleted != null)
|
||||
{
|
||||
NotifyBackgroundWorkCompleted.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBackgroundCapturedWorkload()
|
||||
{
|
||||
if (NotifyBackgroundCapturedWorkload != null)
|
||||
{
|
||||
NotifyBackgroundCapturedWorkload.Set();
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void Enqueue(DocumentSnapshot document)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
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.
|
||||
_work[document.FilePath] = document;
|
||||
|
||||
StartWorker();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartWorker()
|
||||
{
|
||||
// Access to the timer is protected by the lock in Synchronize and in Timer_Tick
|
||||
if (_timer == null)
|
||||
{
|
||||
// Timer will fire after a fixed delay, but only once.
|
||||
_timer = new Timer(Timer_Tick, null, Delay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Timer_Tick(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
OnStartingBackgroundWork();
|
||||
|
||||
KeyValuePair<string, DocumentSnapshot>[] work;
|
||||
lock (_work)
|
||||
{
|
||||
work = _work.ToArray();
|
||||
_work.Clear();
|
||||
}
|
||||
|
||||
OnBackgroundCapturedWorkload();
|
||||
|
||||
for (var i = 0; i < work.Length; i++)
|
||||
{
|
||||
var document = work[i].Value;
|
||||
try
|
||||
{
|
||||
await document.GetGeneratedOutputAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReportError(ex);
|
||||
_logger.LogError("Error when processing document: " + document.FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
OnCompletingBackgroundWork();
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
ReportUnsynchronizableContent(work);
|
||||
NotifyDocumentsProcessed(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 (_work.Count > 0)
|
||||
{
|
||||
StartWorker();
|
||||
}
|
||||
}
|
||||
|
||||
OnCompletedBackgroundWork();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// This is something totally unexpected, let's just send it over to the workspace.
|
||||
_logger.LogError("Unexpected error processing document: " + ex.Message);
|
||||
ReportError(ex);
|
||||
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyDocumentsProcessed(KeyValuePair<string, DocumentSnapshot>[] work)
|
||||
{
|
||||
for (var i = 0; i < work.Length; i++)
|
||||
{
|
||||
foreach (var documentProcessedTrigger in _documentProcessedListeners)
|
||||
{
|
||||
documentProcessedTrigger.DocumentProcessed(work[i].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProjectSnapshotManager_Changed(object sender, ProjectChangeEventArgs args)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
switch (args.Kind)
|
||||
{
|
||||
case ProjectChangeKind.ProjectAdded:
|
||||
{
|
||||
var projectSnapshot = args.Newer;
|
||||
foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
|
||||
{
|
||||
if (_projectManager.IsDocumentOpen(documentFilePath))
|
||||
{
|
||||
var document = projectSnapshot.GetDocument(documentFilePath);
|
||||
Enqueue(document);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ProjectChangeKind.ProjectChanged:
|
||||
{
|
||||
var projectSnapshot = args.Newer;
|
||||
foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
|
||||
{
|
||||
if (_projectManager.IsDocumentOpen(documentFilePath))
|
||||
{
|
||||
var document = projectSnapshot.GetDocument(documentFilePath);
|
||||
Enqueue(document);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ProjectChangeKind.DocumentAdded:
|
||||
{
|
||||
var projectSnapshot = args.Newer;
|
||||
var document = projectSnapshot.GetDocument(args.DocumentFilePath);
|
||||
if (_projectManager.IsDocumentOpen(args.DocumentFilePath))
|
||||
{
|
||||
Enqueue(document);
|
||||
}
|
||||
|
||||
foreach (var relatedDocument in projectSnapshot.GetRelatedDocuments(document))
|
||||
{
|
||||
if (_projectManager.IsDocumentOpen(relatedDocument.FilePath))
|
||||
{
|
||||
Enqueue(relatedDocument);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ProjectChangeKind.DocumentChanged:
|
||||
{
|
||||
var projectSnapshot = args.Newer;
|
||||
var document = projectSnapshot.GetDocument(args.DocumentFilePath);
|
||||
if (_projectManager.IsDocumentOpen(args.DocumentFilePath))
|
||||
{
|
||||
Enqueue(document);
|
||||
}
|
||||
|
||||
foreach (var relatedDocument in projectSnapshot.GetRelatedDocuments(document))
|
||||
{
|
||||
if (_projectManager.IsDocumentOpen(relatedDocument.FilePath))
|
||||
{
|
||||
Enqueue(relatedDocument);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ProjectChangeKind.DocumentRemoved:
|
||||
{
|
||||
var olderProject = args.Older;
|
||||
var document = olderProject.GetDocument(args.DocumentFilePath);
|
||||
|
||||
foreach (var relatedDocument in olderProject.GetRelatedDocuments(document))
|
||||
{
|
||||
var newerRelatedDocument = args.Newer.GetDocument(relatedDocument.FilePath);
|
||||
if (_projectManager.IsDocumentOpen(newerRelatedDocument.FilePath))
|
||||
{
|
||||
Enqueue(newerRelatedDocument);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProjectChangeKind.ProjectRemoved:
|
||||
{
|
||||
// ignore
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown ProjectChangeKind {args.Kind}");
|
||||
}
|
||||
}
|
||||
|
||||
// Internal virtual for testing
|
||||
internal virtual void ReportUnsynchronizableContent(KeyValuePair<string, DocumentSnapshot>[] work)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
// This method deals with reporting unsynchronized content. At this point we've force evaluation of each document
|
||||
// in the work queue; however, some documents may be identical versions of the last synchronized document if
|
||||
// one's content does not differ. In this case, the output of the two generated documents is the same but we still
|
||||
// need to let the client know that we've processed its latest text change. This allows the client to understand
|
||||
// when it's operating on out-of-date output.
|
||||
|
||||
for (var i = 0; i < work.Length; i++)
|
||||
{
|
||||
var document = work[i].Value;
|
||||
|
||||
if (!(document is DefaultDocumentSnapshot defaultDocument))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_documentVersionCache.TryGetDocumentVersion(document, out var syncVersion))
|
||||
{
|
||||
// Document is no longer important.
|
||||
continue;
|
||||
}
|
||||
|
||||
var latestSynchronizedDocument = defaultDocument.State.HostDocument.GeneratedCodeContainer.LatestDocument;
|
||||
if (latestSynchronizedDocument == null ||
|
||||
latestSynchronizedDocument == document)
|
||||
{
|
||||
// Already up-to-date
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IdenticalOutputAfterParse(document, latestSynchronizedDocument, syncVersion))
|
||||
{
|
||||
// Documents are identical but we didn't synchronize them because they didn't need to be re-evaluated.
|
||||
|
||||
var request = new UpdateCSharpBufferRequest()
|
||||
{
|
||||
HostDocumentFilePath = document.FilePath,
|
||||
Changes = Array.Empty<TextChange>(),
|
||||
HostDocumentVersion = syncVersion
|
||||
};
|
||||
|
||||
_router.Client.SendRequest("updateCSharpBuffer", request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IdenticalOutputAfterParse(DocumentSnapshot document, DocumentSnapshot latestSynchronizedDocument, long syncVersion)
|
||||
{
|
||||
return latestSynchronizedDocument.TryGetTextVersion(out var latestSourceVersion) &&
|
||||
document.TryGetTextVersion(out var documentSourceVersion) &&
|
||||
_documentVersionCache.TryGetDocumentVersion(latestSynchronizedDocument, out var lastSynchronizedVersion) &&
|
||||
syncVersion > lastSynchronizedVersion &&
|
||||
latestSourceVersion == documentSourceVersion;
|
||||
}
|
||||
|
||||
private void ReportError(Exception ex)
|
||||
{
|
||||
GC.KeepAlive(Task.Factory.StartNew(
|
||||
() => _projectManager.ReportError(ex),
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
_foregroundDispatcher.ForegroundScheduler));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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 System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class AttributeDescriptionInfo
|
||||
{
|
||||
public static readonly AttributeDescriptionInfo Default = new AttributeDescriptionInfo(Array.Empty<TagHelperAttributeDescriptionInfo>());
|
||||
|
||||
public AttributeDescriptionInfo(IReadOnlyList<TagHelperAttributeDescriptionInfo> associatedAttributeDescriptions)
|
||||
{
|
||||
if (associatedAttributeDescriptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(associatedAttributeDescriptions));
|
||||
}
|
||||
|
||||
AssociatedAttributeDescriptions = associatedAttributeDescriptions;
|
||||
}
|
||||
|
||||
public IReadOnlyList<TagHelperAttributeDescriptionInfo> AssociatedAttributeDescriptions { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// 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.Razor.Completion;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal static class CompletionItemExtensions
|
||||
{
|
||||
private const string TagHelperElementDataKey = "_TagHelperElementData_";
|
||||
private const string TagHelperAttributeDataKey = "_TagHelperAttributes_";
|
||||
private const string AttributeCompletionDataKey = "_AttributeCompletion_";
|
||||
private const string RazorCompletionItemKind = "_CompletionItemKind_";
|
||||
|
||||
public static void SetRazorCompletionKind(this CompletionItem completion, RazorCompletionItemKind completionItemKind)
|
||||
{
|
||||
if (completion is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completion));
|
||||
}
|
||||
|
||||
var data = completion.Data ?? new JObject();
|
||||
data[RazorCompletionItemKind] = JToken.FromObject(completionItemKind);
|
||||
completion.Data = data;
|
||||
}
|
||||
|
||||
public static bool TryGetRazorCompletionKind(this CompletionItem completion, out RazorCompletionItemKind completionItemKind)
|
||||
{
|
||||
if (completion is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completion));
|
||||
}
|
||||
|
||||
if (completion.Data is JObject data && data.ContainsKey(RazorCompletionItemKind))
|
||||
{
|
||||
completionItemKind = data[RazorCompletionItemKind].ToObject<RazorCompletionItemKind>();
|
||||
return true;
|
||||
}
|
||||
|
||||
completionItemKind = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsTagHelperElementCompletion(this CompletionItem completion)
|
||||
{
|
||||
if (completion.Data is JObject data && data.ContainsKey(TagHelperElementDataKey))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsTagHelperAttributeCompletion(this CompletionItem completion)
|
||||
{
|
||||
if (completion.Data is JObject data && data.ContainsKey(TagHelperAttributeDataKey))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SetDescriptionInfo(this CompletionItem completion, ElementDescriptionInfo elementDescriptionInfo)
|
||||
{
|
||||
var data = completion.Data ?? new JObject();
|
||||
data[TagHelperElementDataKey] = JObject.FromObject(elementDescriptionInfo);
|
||||
completion.Data = data;
|
||||
}
|
||||
|
||||
public static void SetDescriptionInfo(this CompletionItem completion, AttributeDescriptionInfo attributeDescriptionInfo)
|
||||
{
|
||||
var data = completion.Data ?? new JObject();
|
||||
data[TagHelperAttributeDataKey] = JObject.FromObject(attributeDescriptionInfo);
|
||||
completion.Data = data;
|
||||
}
|
||||
|
||||
public static void SetDescriptionInfo(this CompletionItem completion, AttributeCompletionDescription attributeDescriptionInfo)
|
||||
{
|
||||
if (completion is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completion));
|
||||
}
|
||||
|
||||
if (attributeDescriptionInfo is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributeDescriptionInfo));
|
||||
}
|
||||
|
||||
var data = completion.Data ?? new JObject();
|
||||
data[AttributeCompletionDataKey] = JObject.FromObject(attributeDescriptionInfo);
|
||||
completion.Data = data;
|
||||
}
|
||||
|
||||
public static ElementDescriptionInfo GetElementDescriptionInfo(this CompletionItem completion)
|
||||
{
|
||||
if (completion.Data is JObject data && data.ContainsKey(TagHelperElementDataKey))
|
||||
{
|
||||
var descriptionInfo = data[TagHelperElementDataKey].ToObject<ElementDescriptionInfo>();
|
||||
return descriptionInfo;
|
||||
}
|
||||
|
||||
return ElementDescriptionInfo.Default;
|
||||
}
|
||||
|
||||
public static AttributeDescriptionInfo GetTagHelperAttributeDescriptionInfo(this CompletionItem completion)
|
||||
{
|
||||
if (completion.Data is JObject data && data.ContainsKey(TagHelperAttributeDataKey))
|
||||
{
|
||||
var descriptionInfo = data[TagHelperAttributeDataKey].ToObject<AttributeDescriptionInfo>();
|
||||
return descriptionInfo;
|
||||
}
|
||||
|
||||
return AttributeDescriptionInfo.Default;
|
||||
}
|
||||
|
||||
public static AttributeCompletionDescription GetAttributeDescriptionInfo(this CompletionItem completion)
|
||||
{
|
||||
if (completion is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completion));
|
||||
}
|
||||
|
||||
if (completion.Data is JObject data && data.ContainsKey(AttributeCompletionDataKey))
|
||||
{
|
||||
var descriptionInfo = data[AttributeCompletionDataKey].ToObject<AttributeCompletionDescription>();
|
||||
return descriptionInfo;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using RazorTagHelperCompletionService = Microsoft.VisualStudio.Editor.Razor.TagHelperCompletionService;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class DefaultTagHelperCompletionService : TagHelperCompletionService
|
||||
{
|
||||
private static readonly Container<string> AttributeCommitCharacters = new Container<string>(" ");
|
||||
private static readonly Container<string> ElementCommitCharacters = new Container<string>(" ", ">");
|
||||
private static readonly HashSet<string> HtmlSchemaTagNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"DOCTYPE",
|
||||
"a",
|
||||
"abbr",
|
||||
"acronym",
|
||||
"address",
|
||||
"applet",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"basefont",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"center",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"dir",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"font",
|
||||
"footer",
|
||||
"form",
|
||||
"frame",
|
||||
"frameset",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noframes",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strike",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"svg",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"template",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"tt",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
};
|
||||
private readonly RazorTagHelperCompletionService _razorTagHelperCompletionService;
|
||||
|
||||
public DefaultTagHelperCompletionService(RazorTagHelperCompletionService razorCompletionService)
|
||||
{
|
||||
if (razorCompletionService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(razorCompletionService));
|
||||
}
|
||||
|
||||
_razorTagHelperCompletionService = razorCompletionService;
|
||||
}
|
||||
|
||||
public override IReadOnlyList<CompletionItem> GetCompletionsAt(SourceSpan location, RazorCodeDocument codeDocument)
|
||||
{
|
||||
if (codeDocument == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeDocument));
|
||||
}
|
||||
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
var change = new SourceChange(location, "");
|
||||
var owner = syntaxTree.Root.LocateOwner(change);
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
Debug.Fail("Owner should never be null.");
|
||||
return Array.Empty<CompletionItem>();
|
||||
}
|
||||
|
||||
var parent = owner.Parent;
|
||||
if (TryGetElementInfo(parent, out var containingTagNameToken, out var attributes) &&
|
||||
containingTagNameToken.Span.IntersectsWith(location.AbsoluteIndex))
|
||||
{
|
||||
var stringifiedAttributes = StringifyAttributes(attributes);
|
||||
var elementCompletions = GetElementCompletions(parent, containingTagNameToken.Content, stringifiedAttributes, codeDocument);
|
||||
return elementCompletions;
|
||||
}
|
||||
|
||||
if (TryGetAttributeInfo(parent, out containingTagNameToken, out var selectedAttributeName, out attributes) &&
|
||||
attributes.Span.IntersectsWith(location.AbsoluteIndex))
|
||||
{
|
||||
var stringifiedAttributes = StringifyAttributes(attributes);
|
||||
var attributeCompletions = GetAttributeCompletions(parent, containingTagNameToken.Content, selectedAttributeName, stringifiedAttributes, codeDocument);
|
||||
return attributeCompletions;
|
||||
}
|
||||
|
||||
// Invalid location for TagHelper completions.
|
||||
return Array.Empty<CompletionItem>();
|
||||
}
|
||||
|
||||
private static bool TryGetAttributeInfo(SyntaxNode attribute, out SyntaxToken containingTagNameToken, out string selectedAttributeName, out SyntaxList<RazorSyntaxNode> attributeNodes)
|
||||
{
|
||||
if ((attribute is MarkupMiscAttributeContentSyntax ||
|
||||
attribute is MarkupMinimizedAttributeBlockSyntax ||
|
||||
attribute is MarkupAttributeBlockSyntax ||
|
||||
attribute is MarkupTagHelperAttributeSyntax ||
|
||||
attribute is MarkupMinimizedTagHelperAttributeSyntax ||
|
||||
attribute is MarkupTagHelperDirectiveAttributeSyntax ||
|
||||
attribute is MarkupMinimizedTagHelperDirectiveAttributeSyntax) &&
|
||||
TryGetElementInfo(attribute.Parent, out containingTagNameToken, out attributeNodes))
|
||||
{
|
||||
selectedAttributeName = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
containingTagNameToken = null;
|
||||
selectedAttributeName = null;
|
||||
attributeNodes = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private IReadOnlyList<CompletionItem> GetAttributeCompletions(
|
||||
SyntaxNode containingAttribute,
|
||||
string containingTagName,
|
||||
string selectedAttributeName,
|
||||
IEnumerable<KeyValuePair<string, string>> attributes,
|
||||
RazorCodeDocument codeDocument)
|
||||
{
|
||||
var ancestors = containingAttribute.Parent.Ancestors();
|
||||
var tagHelperDocumentContext = codeDocument.GetTagHelperContext();
|
||||
var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(tagHelper => !tagHelper.BoundAttributes.Any(attribute => attribute.IsDirectiveAttribute()));
|
||||
var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers);
|
||||
var (ancestorTagName, ancestorIsTagHelper) = GetNearestAncestorTagInfo(ancestors);
|
||||
var attributeCompletionContext = new AttributeCompletionContext(
|
||||
filteredContext,
|
||||
existingCompletions: Enumerable.Empty<string>(),
|
||||
containingTagName,
|
||||
selectedAttributeName,
|
||||
attributes,
|
||||
ancestorTagName,
|
||||
ancestorIsTagHelper,
|
||||
HtmlSchemaTagNames.Contains);
|
||||
|
||||
var completionItems = new List<CompletionItem>();
|
||||
var completionResult = _razorTagHelperCompletionService.GetAttributeCompletions(attributeCompletionContext);
|
||||
foreach (var completion in completionResult.Completions)
|
||||
{
|
||||
var filterText = completion.Key;
|
||||
|
||||
// This is a little bit of a hack because the information returned by _razorTagHelperCompletionService.GetAttributeCompletions
|
||||
// does not have enough information for us to determine if a completion is an indexer completion or not. Therefore we have to
|
||||
// jump through a few hoops below to:
|
||||
// 1. Determine if this specific completion is an indexer based completion
|
||||
// 2. Resolve an appropriate snippet if it is. This is more troublesome because we need to remove the ... suffix to accurately
|
||||
// build a snippet that makes sense for the user to type.
|
||||
var indexerCompletion = filterText.EndsWith("...");
|
||||
if (indexerCompletion)
|
||||
{
|
||||
filterText = filterText.Substring(0, filterText.Length - 3);
|
||||
}
|
||||
|
||||
var insertTextFormat = InsertTextFormat.Snippet;
|
||||
if (!TryResolveAttributeInsertionSnippet(filterText, completion.Value, indexerCompletion, out var insertText))
|
||||
{
|
||||
insertTextFormat = InsertTextFormat.PlainText;
|
||||
insertText = filterText;
|
||||
}
|
||||
|
||||
var razorCompletionItem = new CompletionItem()
|
||||
{
|
||||
Label = completion.Key,
|
||||
InsertText = insertText,
|
||||
InsertTextFormat = insertTextFormat,
|
||||
FilterText = filterText,
|
||||
SortText = filterText,
|
||||
Kind = CompletionItemKind.TypeParameter,
|
||||
CommitCharacters = AttributeCommitCharacters,
|
||||
};
|
||||
var attributeDescriptions = completion.Value.Select(boundAttribute => new TagHelperAttributeDescriptionInfo(
|
||||
boundAttribute.DisplayName,
|
||||
boundAttribute.GetPropertyName(),
|
||||
indexerCompletion ? boundAttribute.IndexerTypeName : boundAttribute.TypeName,
|
||||
boundAttribute.Documentation));
|
||||
var attributeDescriptionInfo = new AttributeDescriptionInfo(attributeDescriptions.ToList());
|
||||
razorCompletionItem.SetDescriptionInfo(attributeDescriptionInfo);
|
||||
|
||||
completionItems.Add(razorCompletionItem);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
private IReadOnlyList<CompletionItem> GetElementCompletions(
|
||||
SyntaxNode containingTag,
|
||||
string containingTagName,
|
||||
IEnumerable<KeyValuePair<string, string>> attributes,
|
||||
RazorCodeDocument codeDocument)
|
||||
{
|
||||
var ancestors = containingTag.Ancestors();
|
||||
var tagHelperDocumentContext = codeDocument.GetTagHelperContext();
|
||||
var (ancestorTagName, ancestorIsTagHelper) = GetNearestAncestorTagInfo(ancestors);
|
||||
var elementCompletionContext = new ElementCompletionContext(
|
||||
tagHelperDocumentContext,
|
||||
existingCompletions: Enumerable.Empty<string>(),
|
||||
containingTagName,
|
||||
attributes,
|
||||
ancestorTagName,
|
||||
ancestorIsTagHelper,
|
||||
HtmlSchemaTagNames.Contains);
|
||||
|
||||
var completionItems = new List<CompletionItem>();
|
||||
var completionResult = _razorTagHelperCompletionService.GetElementCompletions(elementCompletionContext);
|
||||
foreach (var completion in completionResult.Completions)
|
||||
{
|
||||
var razorCompletionItem = new CompletionItem()
|
||||
{
|
||||
Label = completion.Key,
|
||||
InsertText = completion.Key,
|
||||
FilterText = completion.Key,
|
||||
SortText = completion.Key,
|
||||
Kind = CompletionItemKind.TypeParameter,
|
||||
CommitCharacters = ElementCommitCharacters,
|
||||
};
|
||||
var tagHelperDescriptions = completion.Value.Select(tagHelper => new TagHelperDescriptionInfo(tagHelper.GetTypeName(), tagHelper.Documentation));
|
||||
var elementDescription = new ElementDescriptionInfo(tagHelperDescriptions.ToList());
|
||||
razorCompletionItem.SetDescriptionInfo(elementDescription);
|
||||
|
||||
completionItems.Add(razorCompletionItem);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static IEnumerable<KeyValuePair<string, string>> StringifyAttributes(SyntaxList<RazorSyntaxNode> attributes)
|
||||
{
|
||||
var stringifiedAttributes = new List<KeyValuePair<string, string>>();
|
||||
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
if (attribute is MarkupTagHelperAttributeSyntax tagHelperAttribute)
|
||||
{
|
||||
var name = tagHelperAttribute.Name.GetContent();
|
||||
var value = tagHelperAttribute.Value?.GetContent() ?? string.Empty;
|
||||
stringifiedAttributes.Add(new KeyValuePair<string, string>(name, value));
|
||||
}
|
||||
else if (attribute is MarkupMinimizedTagHelperAttributeSyntax minimizedTagHelperAttribute)
|
||||
{
|
||||
var name = minimizedTagHelperAttribute.Name.GetContent();
|
||||
stringifiedAttributes.Add(new KeyValuePair<string, string>(name, string.Empty));
|
||||
}
|
||||
else if (attribute is MarkupAttributeBlockSyntax markupAttribute)
|
||||
{
|
||||
var name = markupAttribute.Name.GetContent();
|
||||
var value = markupAttribute.Value?.GetContent() ?? string.Empty;
|
||||
stringifiedAttributes.Add(new KeyValuePair<string, string>(name, value));
|
||||
}
|
||||
else if (attribute is MarkupMinimizedAttributeBlockSyntax minimizedMarkupAttribute)
|
||||
{
|
||||
var name = minimizedMarkupAttribute.Name.GetContent();
|
||||
stringifiedAttributes.Add(new KeyValuePair<string, string>(name, string.Empty));
|
||||
}
|
||||
else if (attribute is MarkupTagHelperDirectiveAttributeSyntax directiveAttribute)
|
||||
{
|
||||
var name = directiveAttribute.FullName;
|
||||
var value = directiveAttribute.Value?.GetContent() ?? string.Empty;
|
||||
stringifiedAttributes.Add(new KeyValuePair<string, string>(name, value));
|
||||
}
|
||||
else if (attribute is MarkupMinimizedTagHelperDirectiveAttributeSyntax minimizedDirectiveAttribute)
|
||||
{
|
||||
var name = minimizedDirectiveAttribute.FullName;
|
||||
stringifiedAttributes.Add(new KeyValuePair<string, string>(name, string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
return stringifiedAttributes;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static (string ancestorTagName, bool ancestorIsTagHelper) GetNearestAncestorTagInfo(IEnumerable<SyntaxNode> ancestors)
|
||||
{
|
||||
foreach (var ancestor in ancestors)
|
||||
{
|
||||
if (ancestor is MarkupElementSyntax element)
|
||||
{
|
||||
// It's possible for start tag to be null in malformed cases.
|
||||
var name = element.StartTag?.Name?.Content ?? string.Empty;
|
||||
return (name, ancestorIsTagHelper: false);
|
||||
}
|
||||
else if (ancestor is MarkupTagHelperElementSyntax tagHelperElement)
|
||||
{
|
||||
// It's possible for start tag to be null in malformed cases.
|
||||
var name = tagHelperElement.StartTag?.Name?.Content ?? string.Empty;
|
||||
return (name, ancestorIsTagHelper: true);
|
||||
}
|
||||
}
|
||||
|
||||
return (ancestorTagName: null, ancestorIsTagHelper: false);
|
||||
}
|
||||
|
||||
private static bool TryGetElementInfo(SyntaxNode element, out SyntaxToken containingTagNameToken, out SyntaxList<RazorSyntaxNode> attributeNodes)
|
||||
{
|
||||
if (element is MarkupStartTagSyntax startTag)
|
||||
{
|
||||
containingTagNameToken = startTag.Name;
|
||||
attributeNodes = startTag.Attributes;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (element is MarkupTagHelperStartTagSyntax startTagHelper)
|
||||
{
|
||||
containingTagNameToken = startTagHelper.Name;
|
||||
attributeNodes = startTagHelper.Attributes;
|
||||
return true;
|
||||
}
|
||||
|
||||
containingTagNameToken = null;
|
||||
attributeNodes = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryResolveAttributeInsertionSnippet(
|
||||
string text,
|
||||
IEnumerable<BoundAttributeDescriptor> boundAttributes,
|
||||
bool indexerCompletion,
|
||||
out string snippetText)
|
||||
{
|
||||
const string BoolTypeName = "System.Boolean";
|
||||
|
||||
// Boolean returning bound attribute, auto-complete to just the attribute name.
|
||||
if (indexerCompletion)
|
||||
{
|
||||
if (boundAttributes.All(boundAttribute => boundAttribute.IndexerTypeName == BoolTypeName))
|
||||
{
|
||||
snippetText = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
snippetText = string.Concat(text, "$1=\"$2\"");
|
||||
return true;
|
||||
}
|
||||
else if (boundAttributes.All(boundAttribute => boundAttribute.TypeName == BoolTypeName))
|
||||
{
|
||||
snippetText = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
snippetText = string.Concat(text, "=\"$1\"");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.CodeAnalysis.Razor.Completion;
|
||||
using RazorAttributeDescriptionInfo = Microsoft.CodeAnalysis.Razor.Completion.AttributeDescriptionInfo;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class DefaultTagHelperDescriptionFactory : TagHelperDescriptionFactory
|
||||
{
|
||||
private static readonly Lazy<Regex> ExtractCrefRegex = new Lazy<Regex>(
|
||||
() => new Regex("<(see|seealso)[\\s]+cref=\"([^\">]+)\"[^>]*>", RegexOptions.Compiled, TimeSpan.FromSeconds(1)));
|
||||
private static readonly IReadOnlyDictionary<string, string> PrimitiveDisplayTypeNameLookups = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
[typeof(byte).FullName] = "byte",
|
||||
[typeof(sbyte).FullName] = "sbyte",
|
||||
[typeof(int).FullName] = "int",
|
||||
[typeof(uint).FullName] = "uint",
|
||||
[typeof(short).FullName] = "short",
|
||||
[typeof(ushort).FullName] = "ushort",
|
||||
[typeof(long).FullName] = "long",
|
||||
[typeof(ulong).FullName] = "ulong",
|
||||
[typeof(float).FullName] = "float",
|
||||
[typeof(double).FullName] = "double",
|
||||
[typeof(char).FullName] = "char",
|
||||
[typeof(bool).FullName] = "bool",
|
||||
[typeof(object).FullName] = "object",
|
||||
[typeof(string).FullName] = "string",
|
||||
[typeof(decimal).FullName] = "decimal",
|
||||
};
|
||||
|
||||
public override bool TryCreateDescription(ElementDescriptionInfo elementDescriptionInfo, out string markdown)
|
||||
{
|
||||
var associatedTagHelperInfos = elementDescriptionInfo.AssociatedTagHelperDescriptions;
|
||||
if (associatedTagHelperInfos.Count == 0)
|
||||
{
|
||||
markdown = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// This generates a markdown description that looks like the following:
|
||||
// **SomeTagHelper**
|
||||
//
|
||||
// The Summary documentation text with `CrefTypeValues` in code.
|
||||
//
|
||||
// Additional description infos result in a triple `---` to separate the markdown entries.
|
||||
|
||||
var descriptionBuilder = new StringBuilder();
|
||||
for (var i = 0; i < associatedTagHelperInfos.Count; i++)
|
||||
{
|
||||
var descriptionInfo = associatedTagHelperInfos[i];
|
||||
|
||||
if (descriptionBuilder.Length > 0)
|
||||
{
|
||||
descriptionBuilder.AppendLine();
|
||||
descriptionBuilder.AppendLine("---");
|
||||
}
|
||||
|
||||
descriptionBuilder.Append("**");
|
||||
var tagHelperType = descriptionInfo.TagHelperTypeName;
|
||||
var reducedTypeName = ReduceTypeName(tagHelperType);
|
||||
descriptionBuilder.Append(reducedTypeName);
|
||||
descriptionBuilder.AppendLine("**");
|
||||
descriptionBuilder.AppendLine();
|
||||
|
||||
var documentation = descriptionInfo.Documentation;
|
||||
if (!TryExtractSummary(documentation, out var summaryContent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var finalSummaryContent = CleanSummaryContent(summaryContent);
|
||||
descriptionBuilder.AppendLine(finalSummaryContent);
|
||||
}
|
||||
|
||||
markdown = descriptionBuilder.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryCreateDescription(AttributeCompletionDescription descriptionInfos, out string markdown)
|
||||
{
|
||||
var associatedAttributeInfos = descriptionInfos.DescriptionInfos;
|
||||
if (associatedAttributeInfos.Count == 0)
|
||||
{
|
||||
markdown = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// This generates a markdown description that looks like the following:
|
||||
// **ReturnTypeName** SomeTypeName.**SomeProperty**
|
||||
//
|
||||
// The Summary documentation text with `CrefTypeValues` in code.
|
||||
//
|
||||
// Additional description infos result in a triple `---` to separate the markdown entries.
|
||||
|
||||
var descriptionBuilder = new StringBuilder();
|
||||
for (var i = 0; i < associatedAttributeInfos.Count; i++)
|
||||
{
|
||||
var descriptionInfo = associatedAttributeInfos[i];
|
||||
|
||||
if (descriptionBuilder.Length > 0)
|
||||
{
|
||||
descriptionBuilder.AppendLine();
|
||||
descriptionBuilder.AppendLine("---");
|
||||
}
|
||||
|
||||
descriptionBuilder.Append("**");
|
||||
var returnTypeName = GetSimpleName(descriptionInfo.ReturnTypeName);
|
||||
var reducedReturnTypeName = ReduceTypeName(returnTypeName);
|
||||
descriptionBuilder.Append(reducedReturnTypeName);
|
||||
descriptionBuilder.Append("** ");
|
||||
var tagHelperTypeName = descriptionInfo.TypeName;
|
||||
var reducedTagHelperTypeName = ReduceTypeName(tagHelperTypeName);
|
||||
descriptionBuilder.Append(reducedTagHelperTypeName);
|
||||
descriptionBuilder.Append(".**");
|
||||
descriptionBuilder.Append(descriptionInfo.PropertyName);
|
||||
descriptionBuilder.AppendLine("**");
|
||||
descriptionBuilder.AppendLine();
|
||||
|
||||
var documentation = descriptionInfo.Documentation;
|
||||
if (!TryExtractSummary(documentation, out var summaryContent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var finalSummaryContent = CleanSummaryContent(summaryContent);
|
||||
descriptionBuilder.AppendLine(finalSummaryContent);
|
||||
}
|
||||
|
||||
markdown = descriptionBuilder.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryCreateDescription(AttributeDescriptionInfo attributeDescriptionInfo, out string markdown)
|
||||
{
|
||||
var convertedDescriptionInfos = new List<RazorAttributeDescriptionInfo>();
|
||||
foreach (var descriptionInfo in attributeDescriptionInfo.AssociatedAttributeDescriptions)
|
||||
{
|
||||
var tagHelperTypeName = ResolveTagHelperTypeName(descriptionInfo);
|
||||
var converted = new RazorAttributeDescriptionInfo(
|
||||
descriptionInfo.ReturnTypeName,
|
||||
tagHelperTypeName,
|
||||
descriptionInfo.PropertyName,
|
||||
descriptionInfo.Documentation);
|
||||
|
||||
convertedDescriptionInfos.Add(converted);
|
||||
}
|
||||
|
||||
var convertedDescriptionInfo = new AttributeCompletionDescription(convertedDescriptionInfos);
|
||||
|
||||
return TryCreateDescription(convertedDescriptionInfo, out markdown);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static string CleanSummaryContent(string summaryContent)
|
||||
{
|
||||
// Cleans out all <see cref="..." /> and <seealso cref="..." /> elements. It's possible to
|
||||
// have additional doc comment types in the summary but none that require cleaning. For instance
|
||||
// if there's a <para> in the summary element when it's shown in the completion description window
|
||||
// it'll be serialized as html (wont show).
|
||||
|
||||
var crefMatches = ExtractCrefRegex.Value.Matches(summaryContent).Reverse();
|
||||
var summaryBuilder = new StringBuilder(summaryContent);
|
||||
|
||||
foreach (var cref in crefMatches)
|
||||
{
|
||||
if (cref.Success)
|
||||
{
|
||||
var value = cref.Groups[2].Value;
|
||||
var reducedValue = ReduceCrefValue(value);
|
||||
reducedValue = reducedValue.Replace("{", "<").Replace("}", ">");
|
||||
summaryBuilder.Remove(cref.Index, cref.Length);
|
||||
summaryBuilder.Insert(cref.Index, $"`{reducedValue}`");
|
||||
}
|
||||
}
|
||||
var lines = summaryBuilder.ToString().Split(new[] { '\n' }, StringSplitOptions.None).Select(line => line.Trim());
|
||||
var finalSummaryContent = string.Join(Environment.NewLine, lines);
|
||||
return finalSummaryContent;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool TryExtractSummary(string documentation, out string summary)
|
||||
{
|
||||
const string summaryStartTag = "<summary>";
|
||||
const string summaryEndTag = "</summary>";
|
||||
|
||||
if (string.IsNullOrEmpty(documentation))
|
||||
{
|
||||
summary = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var summaryTagStart = documentation.IndexOf(summaryStartTag, StringComparison.OrdinalIgnoreCase);
|
||||
if (summaryTagStart == -1)
|
||||
{
|
||||
summary = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var summaryTagEndStart = documentation.IndexOf(summaryEndTag, StringComparison.OrdinalIgnoreCase);
|
||||
if (summaryTagEndStart == -1)
|
||||
{
|
||||
summary = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var summaryContentStart = summaryTagStart + summaryStartTag.Length;
|
||||
var summaryContentLength = summaryTagEndStart - summaryContentStart;
|
||||
|
||||
summary = documentation.Substring(summaryContentStart, summaryContentLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static string ReduceCrefValue(string value)
|
||||
{
|
||||
// cref values come in the following formats:
|
||||
// Type = "T:Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName"
|
||||
// Property = "P:T:Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.AspAction"
|
||||
// Member = "M:T:Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeMethod(System.Collections.Generic.List{System.String})"
|
||||
|
||||
if (value.Length < 2)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var type = value[0];
|
||||
value = value.Substring(2);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 'T':
|
||||
var reducedCrefType = ReduceTypeName(value);
|
||||
return reducedCrefType;
|
||||
case 'P':
|
||||
case 'M':
|
||||
// TypeName.MemberName
|
||||
var reducedCrefProperty = ReduceMemberName(value);
|
||||
return reducedCrefProperty;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static string GetSimpleName(string typeName)
|
||||
{
|
||||
if (PrimitiveDisplayTypeNameLookups.TryGetValue(typeName, out var simpleName))
|
||||
{
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static string ResolveTagHelperTypeName(TagHelperAttributeDescriptionInfo info)
|
||||
{
|
||||
// A BoundAttributeDescriptor does not have a direct reference to its parent TagHelper.
|
||||
// However, when it was constructed the parent TagHelper's type name was embedded into
|
||||
// its DisplayName. In VSCode we can't use the DisplayName verbatim for descriptions
|
||||
// because the DisplayName is typically too long to display properly. Therefore we need
|
||||
// to break it apart and then reconstruct it in a reduced format.
|
||||
// i.e. this is the format the display name comes in:
|
||||
// ReturnTypeName SomeTypeName.SomePropertyName
|
||||
|
||||
// We must simplify the return type name before using it to determine the type name prefix
|
||||
// because that is how the display name was originally built (a little hacky).
|
||||
var simpleReturnType = GetSimpleName(info.ReturnTypeName);
|
||||
|
||||
// "ReturnTypeName "
|
||||
var typeNamePrefixLength = simpleReturnType.Length + 1 /* space */;
|
||||
|
||||
// ".SomePropertyName"
|
||||
var typeNameSuffixLength = /* . */ 1 + info.PropertyName.Length;
|
||||
|
||||
// "SomeTypeName"
|
||||
var typeNameLength = info.DisplayName.Length - typeNamePrefixLength - typeNameSuffixLength;
|
||||
var tagHelperTypeName = info.DisplayName.Substring(typeNamePrefixLength, typeNameLength);
|
||||
return tagHelperTypeName;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static string ReduceTypeName(string content) => ReduceFullName(content, reduceWhenDotCount: 1);
|
||||
|
||||
// Internal for testing
|
||||
internal static string ReduceMemberName(string content) => ReduceFullName(content, reduceWhenDotCount: 2);
|
||||
|
||||
private static string ReduceFullName(string content, int reduceWhenDotCount)
|
||||
{
|
||||
// Starts searching backwards and then substrings everything when it finds enough dots. i.e.
|
||||
// ReduceFullName("Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName", 1) == "SomeTypeName"
|
||||
//
|
||||
// ReduceFullName("Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.AspAction", 2) == "SomeTypeName.AspAction"
|
||||
//
|
||||
// This is also smart enough to ignore nested dots in type generics[<>], methods[()], cref generics[{}].
|
||||
|
||||
if (reduceWhenDotCount <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(reduceWhenDotCount));
|
||||
}
|
||||
|
||||
var dotsSeen = 0;
|
||||
var scope = 0;
|
||||
for (var i = content.Length - 1; i >= 0; i--)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (content[i] == '}')
|
||||
{
|
||||
scope++;
|
||||
}
|
||||
else if (content[i] == '{')
|
||||
{
|
||||
scope--;
|
||||
}
|
||||
|
||||
if (scope > 0)
|
||||
{
|
||||
i--;
|
||||
}
|
||||
} while (scope != 0 && i >= 0);
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
// Could not balance scope
|
||||
return content;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (content[i] == ')')
|
||||
{
|
||||
scope++;
|
||||
}
|
||||
else if (content[i] == '(')
|
||||
{
|
||||
scope--;
|
||||
}
|
||||
|
||||
if (scope > 0)
|
||||
{
|
||||
i--;
|
||||
}
|
||||
} while (scope != 0 && i >= 0);
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
// Could not balance scope
|
||||
return content;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (content[i] == '>')
|
||||
{
|
||||
scope++;
|
||||
}
|
||||
else if (content[i] == '<')
|
||||
{
|
||||
scope--;
|
||||
}
|
||||
|
||||
if (scope > 0)
|
||||
{
|
||||
i--;
|
||||
}
|
||||
} while (scope != 0 && i >= 0);
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
// Could not balance scope
|
||||
return content;
|
||||
}
|
||||
|
||||
if (content[i] == '.')
|
||||
{
|
||||
dotsSeen++;
|
||||
}
|
||||
|
||||
if (dotsSeen == reduceWhenDotCount)
|
||||
{
|
||||
var piece = content.Substring(i + 1);
|
||||
return piece;
|
||||
}
|
||||
}
|
||||
|
||||
// Could not reduce name
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
using Microsoft.CodeAnalysis.Razor.Completion;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class DirectiveAttributeTransitionCompletionItemProvider : DirectiveAttributeCompletionItemProviderBase
|
||||
{
|
||||
private static RazorCompletionItem _transitionCompletionItem;
|
||||
|
||||
public static RazorCompletionItem TransitionCompletionItem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_transitionCompletionItem == null)
|
||||
{
|
||||
_transitionCompletionItem = new RazorCompletionItem("@...", "@", RazorCompletionItemKind.Directive);
|
||||
_transitionCompletionItem.SetDirectiveCompletionDescription(new DirectiveCompletionDescription("Blazor directive attributes"));
|
||||
}
|
||||
|
||||
return _transitionCompletionItem;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyList<RazorCompletionItem> Completions = new[] { TransitionCompletionItem };
|
||||
|
||||
public override IReadOnlyList<RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, TagHelperDocumentContext tagHelperDocumentContext, SourceSpan location)
|
||||
{
|
||||
if (!FileKinds.IsComponent(syntaxTree.Options.FileKind))
|
||||
{
|
||||
// Directive attributes are only supported in components
|
||||
return Array.Empty<RazorCompletionItem>();
|
||||
}
|
||||
|
||||
var change = new SourceChange(location, string.Empty);
|
||||
var owner = syntaxTree.Root.LocateOwner(change);
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
return Array.Empty<RazorCompletionItem>();
|
||||
}
|
||||
|
||||
var attribute = owner.Parent;
|
||||
if (attribute is MarkupMiscAttributeContentSyntax)
|
||||
{
|
||||
// This represents a tag when there's no attribute content <InputText | />.
|
||||
return Completions;
|
||||
}
|
||||
|
||||
if (!TryGetAttributeInfo(attribute, out var name, out var nameLocation, out _, out _))
|
||||
{
|
||||
return Array.Empty<RazorCompletionItem>();
|
||||
}
|
||||
|
||||
if (name.StartsWith("@"))
|
||||
{
|
||||
// The transition is already provided
|
||||
return Array.Empty<RazorCompletionItem>();
|
||||
}
|
||||
|
||||
if (!nameLocation.IntersectsWith(location.AbsoluteIndex))
|
||||
{
|
||||
// Not operating in the name section
|
||||
return Array.Empty<RazorCompletionItem>();
|
||||
}
|
||||
|
||||
// This represents a tag when there's no attribute content <InputText | />.
|
||||
return Completions;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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 System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class ElementDescriptionInfo
|
||||
{
|
||||
public static readonly ElementDescriptionInfo Default = new ElementDescriptionInfo(Array.Empty<TagHelperDescriptionInfo>());
|
||||
|
||||
public ElementDescriptionInfo(IReadOnlyList<TagHelperDescriptionInfo> associatedTagHelperDescriptions)
|
||||
{
|
||||
if (associatedTagHelperDescriptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(associatedTagHelperDescriptions));
|
||||
}
|
||||
|
||||
AssociatedTagHelperDescriptions = associatedTagHelperDescriptions;
|
||||
}
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptionInfo> AssociatedTagHelperDescriptions { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,334 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.Completion;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class RazorCompletionEndpoint : ICompletionHandler, ICompletionResolveHandler
|
||||
{
|
||||
private static readonly Container<string> DirectiveAttributeCommitCharacters = new Container<string>(" ");
|
||||
private CompletionCapability _capability;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly DocumentResolver _documentResolver;
|
||||
private readonly RazorCompletionFactsService _completionFactsService;
|
||||
private readonly TagHelperCompletionService _tagHelperCompletionService;
|
||||
private readonly TagHelperDescriptionFactory _tagHelperDescriptionFactory;
|
||||
private static readonly Command RetriggerCompletionCommand = new Command()
|
||||
{
|
||||
Name = "editor.action.triggerSuggest",
|
||||
Title = "Re-trigger completions...",
|
||||
};
|
||||
|
||||
public RazorCompletionEndpoint(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
DocumentResolver documentResolver,
|
||||
RazorCompletionFactsService completionFactsService,
|
||||
TagHelperCompletionService tagHelperCompletionService,
|
||||
TagHelperDescriptionFactory tagHelperDescriptionFactory,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (documentResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentResolver));
|
||||
}
|
||||
|
||||
if (completionFactsService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionFactsService));
|
||||
}
|
||||
|
||||
if (tagHelperCompletionService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelperCompletionService));
|
||||
}
|
||||
|
||||
if (tagHelperDescriptionFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelperDescriptionFactory));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_documentResolver = documentResolver;
|
||||
_completionFactsService = completionFactsService;
|
||||
_tagHelperCompletionService = tagHelperCompletionService;
|
||||
_tagHelperDescriptionFactory = tagHelperDescriptionFactory;
|
||||
_logger = loggerFactory.CreateLogger<RazorCompletionEndpoint>();
|
||||
}
|
||||
|
||||
public void SetCapability(CompletionCapability capability)
|
||||
{
|
||||
_capability = capability;
|
||||
}
|
||||
|
||||
public async Task<CompletionList> Handle(CompletionParams request, CancellationToken cancellationToken)
|
||||
{
|
||||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
var document = await Task.Factory.StartNew(() =>
|
||||
{
|
||||
_documentResolver.TryResolveDocument(request.TextDocument.Uri.AbsolutePath, out var documentSnapshot);
|
||||
|
||||
return documentSnapshot;
|
||||
}, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
var codeDocument = await document.GetGeneratedOutputAsync();
|
||||
if (codeDocument.IsUnsupported())
|
||||
{
|
||||
return new CompletionList(isIncomplete: false);
|
||||
}
|
||||
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
var tagHelperDocumentContext = codeDocument.GetTagHelperContext();
|
||||
|
||||
var sourceText = await document.GetTextAsync();
|
||||
var linePosition = new LinePosition((int)request.Position.Line, (int)request.Position.Character);
|
||||
var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition);
|
||||
var location = new SourceSpan(hostDocumentIndex, 0);
|
||||
|
||||
var directiveCompletionItems = _completionFactsService.GetCompletionItems(syntaxTree, tagHelperDocumentContext, location);
|
||||
|
||||
_logger.LogTrace($"Found {directiveCompletionItems.Count} directive completion items.");
|
||||
|
||||
var completionItems = new List<CompletionItem>();
|
||||
foreach (var razorCompletionItem in directiveCompletionItems)
|
||||
{
|
||||
if (TryConvert(razorCompletionItem, out var completionItem))
|
||||
{
|
||||
completionItems.Add(completionItem);
|
||||
}
|
||||
}
|
||||
|
||||
var parameterCompletions = completionItems.Where(completionItem => completionItem.TryGetRazorCompletionKind(out var completionKind) && completionKind == RazorCompletionItemKind.DirectiveAttributeParameter);
|
||||
if (parameterCompletions.Any())
|
||||
{
|
||||
// Parameters are present in the completion list, even though TagHelpers are technically valid we shouldn't flood the completion list
|
||||
// with non parameter completions. Filter out the rest.
|
||||
completionItems = parameterCompletions.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var tagHelperCompletionItems = _tagHelperCompletionService.GetCompletionsAt(location, codeDocument);
|
||||
|
||||
_logger.LogTrace($"Found {tagHelperCompletionItems.Count} TagHelper completion items.");
|
||||
|
||||
completionItems.AddRange(tagHelperCompletionItems);
|
||||
}
|
||||
|
||||
var completionList = new CompletionList(completionItems, isIncomplete: false);
|
||||
|
||||
return completionList;
|
||||
}
|
||||
|
||||
public CompletionRegistrationOptions GetRegistrationOptions()
|
||||
{
|
||||
return new CompletionRegistrationOptions()
|
||||
{
|
||||
DocumentSelector = RazorDefaults.Selector,
|
||||
ResolveProvider = true,
|
||||
TriggerCharacters = new Container<string>("@", "<", ":"),
|
||||
};
|
||||
}
|
||||
|
||||
public bool CanResolve(CompletionItem completionItem)
|
||||
{
|
||||
if (completionItem.TryGetRazorCompletionKind(out var completionItemKind))
|
||||
{
|
||||
switch (completionItemKind)
|
||||
{
|
||||
case RazorCompletionItemKind.DirectiveAttribute:
|
||||
case RazorCompletionItemKind.DirectiveAttributeParameter:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (completionItem.IsTagHelperElementCompletion() ||
|
||||
completionItem.IsTagHelperAttributeCompletion())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<CompletionItem> Handle(CompletionItem completionItem, CancellationToken cancellationToken)
|
||||
{
|
||||
string markdown = null;
|
||||
if (completionItem.TryGetRazorCompletionKind(out var completionItemKind))
|
||||
{
|
||||
switch (completionItemKind)
|
||||
{
|
||||
case RazorCompletionItemKind.DirectiveAttribute:
|
||||
case RazorCompletionItemKind.DirectiveAttributeParameter:
|
||||
var descriptionInfo = completionItem.GetAttributeDescriptionInfo();
|
||||
_tagHelperDescriptionFactory.TryCreateDescription(descriptionInfo, out markdown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (completionItem.IsTagHelperElementCompletion())
|
||||
{
|
||||
var descriptionInfo = completionItem.GetElementDescriptionInfo();
|
||||
_tagHelperDescriptionFactory.TryCreateDescription(descriptionInfo, out markdown);
|
||||
}
|
||||
|
||||
if (completionItem.IsTagHelperAttributeCompletion())
|
||||
{
|
||||
var descriptionInfo = completionItem.GetTagHelperAttributeDescriptionInfo();
|
||||
_tagHelperDescriptionFactory.TryCreateDescription(descriptionInfo, out markdown);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (markdown != null)
|
||||
{
|
||||
var documentation = new StringOrMarkupContent(
|
||||
new MarkupContent()
|
||||
{
|
||||
Kind = MarkupKind.Markdown,
|
||||
Value = markdown,
|
||||
});
|
||||
completionItem.Documentation = documentation;
|
||||
}
|
||||
|
||||
return Task.FromResult(completionItem);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool TryConvert(RazorCompletionItem razorCompletionItem, out CompletionItem completionItem)
|
||||
{
|
||||
switch (razorCompletionItem.Kind)
|
||||
{
|
||||
case RazorCompletionItemKind.Directive:
|
||||
{
|
||||
// There's not a lot of calculation needed for Directives, go ahead and store the documentation/detail
|
||||
// on the completion item.
|
||||
var descriptionInfo = razorCompletionItem.GetDirectiveCompletionDescription();
|
||||
var directiveCompletionItem = new CompletionItem()
|
||||
{
|
||||
Label = razorCompletionItem.DisplayText,
|
||||
InsertText = razorCompletionItem.InsertText,
|
||||
FilterText = razorCompletionItem.DisplayText,
|
||||
SortText = razorCompletionItem.DisplayText,
|
||||
Detail = descriptionInfo.Description,
|
||||
Documentation = descriptionInfo.Description,
|
||||
Kind = CompletionItemKind.Struct,
|
||||
};
|
||||
|
||||
if (razorCompletionItem == DirectiveAttributeTransitionCompletionItemProvider.TransitionCompletionItem)
|
||||
{
|
||||
directiveCompletionItem.Command = RetriggerCompletionCommand;
|
||||
directiveCompletionItem.Kind = CompletionItemKind.TypeParameter;
|
||||
directiveCompletionItem.Preselect = true;
|
||||
}
|
||||
|
||||
directiveCompletionItem.SetRazorCompletionKind(razorCompletionItem.Kind);
|
||||
completionItem = directiveCompletionItem;
|
||||
return true;
|
||||
}
|
||||
case RazorCompletionItemKind.DirectiveAttribute:
|
||||
{
|
||||
var descriptionInfo = razorCompletionItem.GetAttributeCompletionDescription();
|
||||
|
||||
var directiveAttributeCompletionItem = new CompletionItem()
|
||||
{
|
||||
Label = razorCompletionItem.DisplayText,
|
||||
InsertText = razorCompletionItem.InsertText,
|
||||
FilterText = razorCompletionItem.InsertText,
|
||||
SortText = razorCompletionItem.InsertText,
|
||||
Kind = CompletionItemKind.TypeParameter,
|
||||
};
|
||||
|
||||
var indexerCompletion = razorCompletionItem.DisplayText.EndsWith("...");
|
||||
if (TryResolveDirectiveAttributeInsertionSnippet(razorCompletionItem.InsertText, indexerCompletion, descriptionInfo, out var snippetText))
|
||||
{
|
||||
directiveAttributeCompletionItem.InsertText = snippetText;
|
||||
directiveAttributeCompletionItem.InsertTextFormat = InsertTextFormat.Snippet;
|
||||
}
|
||||
|
||||
directiveAttributeCompletionItem.SetDescriptionInfo(descriptionInfo);
|
||||
directiveAttributeCompletionItem.SetRazorCompletionKind(razorCompletionItem.Kind);
|
||||
completionItem = directiveAttributeCompletionItem;
|
||||
return true;
|
||||
}
|
||||
case RazorCompletionItemKind.DirectiveAttributeParameter:
|
||||
{
|
||||
var descriptionInfo = razorCompletionItem.GetAttributeCompletionDescription();
|
||||
var parameterCompletionItem = new CompletionItem()
|
||||
{
|
||||
Label = razorCompletionItem.DisplayText,
|
||||
InsertText = razorCompletionItem.InsertText,
|
||||
FilterText = razorCompletionItem.InsertText,
|
||||
SortText = razorCompletionItem.InsertText,
|
||||
Kind = CompletionItemKind.TypeParameter,
|
||||
};
|
||||
|
||||
parameterCompletionItem.SetDescriptionInfo(descriptionInfo);
|
||||
parameterCompletionItem.SetRazorCompletionKind(razorCompletionItem.Kind);
|
||||
completionItem = parameterCompletionItem;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
completionItem = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryResolveDirectiveAttributeInsertionSnippet(
|
||||
string insertText,
|
||||
bool indexerCompletion,
|
||||
AttributeCompletionDescription attributeCompletionDescription,
|
||||
out string snippetText)
|
||||
{
|
||||
const string BoolTypeName = "System.Boolean";
|
||||
var attributeInfos = attributeCompletionDescription.DescriptionInfos;
|
||||
|
||||
// Boolean returning bound attribute, auto-complete to just the attribute name.
|
||||
if (attributeInfos.All(info => info.ReturnTypeName == BoolTypeName))
|
||||
{
|
||||
snippetText = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (indexerCompletion)
|
||||
{
|
||||
// Indexer completion
|
||||
snippetText = string.Concat(insertText, "$1=\"$2\"$0");
|
||||
}
|
||||
else
|
||||
{
|
||||
snippetText = string.Concat(insertText, "=\"$1\"$0");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class TagHelperAttributeDescriptionInfo
|
||||
{
|
||||
public TagHelperAttributeDescriptionInfo(
|
||||
string displayName,
|
||||
string propertyName,
|
||||
string returnTypeName,
|
||||
string documentation)
|
||||
{
|
||||
if (displayName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayName));
|
||||
}
|
||||
|
||||
if (propertyName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyName));
|
||||
}
|
||||
|
||||
if (returnTypeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(returnTypeName));
|
||||
}
|
||||
|
||||
DisplayName = displayName;
|
||||
PropertyName = propertyName;
|
||||
ReturnTypeName = returnTypeName;
|
||||
Documentation = documentation;
|
||||
}
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public string PropertyName { get; }
|
||||
|
||||
public string ReturnTypeName { get; }
|
||||
|
||||
public string Documentation { get; }
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal abstract class TagHelperCompletionService
|
||||
{
|
||||
public abstract IReadOnlyList<CompletionItem> GetCompletionsAt(SourceSpan location, RazorCodeDocument codeDocument);
|
||||
}
|
||||
}
|
|
@ -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.CodeAnalysis.Razor.Completion;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal abstract class TagHelperDescriptionFactory
|
||||
{
|
||||
public abstract bool TryCreateDescription(ElementDescriptionInfo descriptionInfos, out string markdown);
|
||||
|
||||
public abstract bool TryCreateDescription(AttributeDescriptionInfo descriptionInfos, out string markdown);
|
||||
|
||||
public abstract bool TryCreateDescription(AttributeCompletionDescription descriptionInfos, out string markdown);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion
|
||||
{
|
||||
internal class TagHelperDescriptionInfo
|
||||
{
|
||||
public TagHelperDescriptionInfo(string tagHelperTypeName, string documentation)
|
||||
{
|
||||
if (tagHelperTypeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelperTypeName));
|
||||
}
|
||||
|
||||
TagHelperTypeName = tagHelperTypeName;
|
||||
Documentation = documentation;
|
||||
}
|
||||
|
||||
public string TagHelperTypeName { get; }
|
||||
|
||||
public string Documentation { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class DefaultDocumentVersionCache : DocumentVersionCache
|
||||
{
|
||||
internal const int MaxDocumentTrackingCount = 20;
|
||||
|
||||
// Internal for testing
|
||||
internal readonly Dictionary<string, List<DocumentEntry>> _documentLookup;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private ProjectSnapshotManagerBase _projectSnapshotManager;
|
||||
|
||||
public DefaultDocumentVersionCache(ForegroundDispatcher foregroundDispatcher)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_documentLookup = new Dictionary<string, List<DocumentEntry>>(FilePathComparer.Instance);
|
||||
}
|
||||
|
||||
public override void TrackDocumentVersion(DocumentSnapshot documentSnapshot, long version)
|
||||
{
|
||||
if (documentSnapshot == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentSnapshot));
|
||||
}
|
||||
|
||||
if (version < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(version));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
if (!_documentLookup.TryGetValue(documentSnapshot.FilePath, out var documentEntries))
|
||||
{
|
||||
documentEntries = new List<DocumentEntry>();
|
||||
_documentLookup[documentSnapshot.FilePath] = documentEntries;
|
||||
}
|
||||
|
||||
if (documentEntries.Count == MaxDocumentTrackingCount)
|
||||
{
|
||||
// Clear the oldest document entry
|
||||
|
||||
// With this approach we'll slowly leak memory as new documents are added to the system. We don't clear up
|
||||
// document file paths where where all of the corresponding entries are expired.
|
||||
documentEntries.RemoveAt(0);
|
||||
}
|
||||
|
||||
var entry = new DocumentEntry(documentSnapshot, version);
|
||||
documentEntries.Add(entry);
|
||||
}
|
||||
|
||||
public override bool TryGetDocumentVersion(DocumentSnapshot documentSnapshot, out long version)
|
||||
{
|
||||
if (documentSnapshot == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentSnapshot));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
if (!_documentLookup.TryGetValue(documentSnapshot.FilePath, out var documentEntries))
|
||||
{
|
||||
version = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
DocumentEntry entry = null;
|
||||
for (var i = documentEntries.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// We iterate backwards over the entries to prioritize newer entries.
|
||||
if (documentEntries[i].Document.TryGetTarget(out var document) &&
|
||||
document == documentSnapshot)
|
||||
{
|
||||
entry = documentEntries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
version = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
version = entry.Version;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Initialize(ProjectSnapshotManagerBase projectManager)
|
||||
{
|
||||
_projectSnapshotManager = projectManager;
|
||||
_projectSnapshotManager.Changed += ProjectSnapshotManager_Changed;
|
||||
}
|
||||
|
||||
private void ProjectSnapshotManager_Changed(object sender, ProjectChangeEventArgs args)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
switch (args.Kind)
|
||||
{
|
||||
case ProjectChangeKind.DocumentChanged:
|
||||
case ProjectChangeKind.DocumentRemoved:
|
||||
if (_documentLookup.ContainsKey(args.DocumentFilePath) &&
|
||||
!_projectSnapshotManager.IsDocumentOpen(args.DocumentFilePath))
|
||||
{
|
||||
// Document closed or removed, evict entry.
|
||||
_documentLookup.Remove(args.DocumentFilePath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Any event that has a project may have changed the state of the documents
|
||||
// and therefore requires us to mark all existing documents as latest.
|
||||
if (args.ProjectFilePath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var project = _projectSnapshotManager.GetLoadedProject(args.ProjectFilePath);
|
||||
if (project == null)
|
||||
{
|
||||
// Project no longer loaded, wait for document removed event.
|
||||
return;
|
||||
}
|
||||
|
||||
CaptureProjectDocumentsAsLatest(project);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void MarkAsLatestVersion(DocumentSnapshot document)
|
||||
{
|
||||
if (!TryGetLatestVersionFromPath(document.FilePath, out var latestVersion))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update our internal tracking state to track the changed document as the latest document.
|
||||
TrackDocumentVersion(document, latestVersion);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal bool TryGetLatestVersionFromPath(string filePath, out long version)
|
||||
{
|
||||
if (!_documentLookup.TryGetValue(filePath, out var documentEntries))
|
||||
{
|
||||
version = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
var latestEntry = documentEntries[documentEntries.Count - 1];
|
||||
|
||||
version = latestEntry.Version;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CaptureProjectDocumentsAsLatest(ProjectSnapshot projectSnapshot)
|
||||
{
|
||||
foreach (var documentPath in projectSnapshot.DocumentFilePaths)
|
||||
{
|
||||
if (_documentLookup.ContainsKey(documentPath))
|
||||
{
|
||||
var document = projectSnapshot.GetDocument(documentPath);
|
||||
MarkAsLatestVersion(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class DocumentEntry
|
||||
{
|
||||
public DocumentEntry(DocumentSnapshot document, long version)
|
||||
{
|
||||
Document = new WeakReference<DocumentSnapshot>(document);
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public WeakReference<DocumentSnapshot> Document { get; }
|
||||
|
||||
public long Version { get; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
// 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 System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using OmniSharp.Extensions.LanguageServer.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class DefaultGeneratedCodeContainerStore : GeneratedCodeContainerStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, GeneratedCodeContainer> _store;
|
||||
private readonly Lazy<ILanguageServer> _server;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly DocumentVersionCache _documentVersionCache;
|
||||
private ProjectSnapshotManagerBase _projectSnapshotManager;
|
||||
|
||||
public DefaultGeneratedCodeContainerStore(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
DocumentVersionCache documentVersionCache,
|
||||
Lazy<ILanguageServer> server)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (documentVersionCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentVersionCache));
|
||||
}
|
||||
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_documentVersionCache = documentVersionCache;
|
||||
_server = server;
|
||||
_store = new ConcurrentDictionary<string, GeneratedCodeContainer>(FilePathComparer.Instance);
|
||||
}
|
||||
|
||||
public override GeneratedCodeContainer Get(string physicalFilePath)
|
||||
{
|
||||
if (physicalFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(physicalFilePath));
|
||||
}
|
||||
|
||||
lock (_store)
|
||||
{
|
||||
var codeContainer = _store.GetOrAdd(physicalFilePath, Create);
|
||||
return codeContainer;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize(ProjectSnapshotManagerBase projectManager)
|
||||
{
|
||||
_projectSnapshotManager = projectManager;
|
||||
_projectSnapshotManager.Changed += ProjectSnapshotManager_Changed;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void ProjectSnapshotManager_Changed(object sender, ProjectChangeEventArgs args)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
switch (args.Kind)
|
||||
{
|
||||
case ProjectChangeKind.DocumentChanged:
|
||||
case ProjectChangeKind.DocumentRemoved:
|
||||
lock (_store)
|
||||
{
|
||||
if (_store.ContainsKey(args.DocumentFilePath) &&
|
||||
!_projectSnapshotManager.IsDocumentOpen(args.DocumentFilePath))
|
||||
{
|
||||
// Document closed or removed, evict entry.
|
||||
_store.TryRemove(args.DocumentFilePath, out var _);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private GeneratedCodeContainer Create(string filePath)
|
||||
{
|
||||
var codeContainer = new GeneratedCodeContainer();
|
||||
codeContainer.GeneratedCodeChanged += (sender, args) =>
|
||||
{
|
||||
var generatedCodeContainer = (GeneratedCodeContainer)sender;
|
||||
|
||||
IReadOnlyList<TextChange> textChanges;
|
||||
|
||||
if (args.NewText.ContentEquals(args.OldText))
|
||||
{
|
||||
// If the content is equal then no need to update the underlying CSharp buffer.
|
||||
textChanges = Array.Empty<TextChange>();
|
||||
}
|
||||
else
|
||||
{
|
||||
textChanges = args.NewText.GetTextChanges(args.OldText);
|
||||
}
|
||||
|
||||
var latestDocument = generatedCodeContainer.LatestDocument;
|
||||
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
if (!_documentVersionCache.TryGetDocumentVersion(latestDocument, out var hostDocumentVersion))
|
||||
{
|
||||
// Cache entry doesn't exist, document most likely was evicted from the cache/too old.
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new UpdateCSharpBufferRequest()
|
||||
{
|
||||
HostDocumentFilePath = filePath,
|
||||
Changes = textChanges,
|
||||
HostDocumentVersion = hostDocumentVersion,
|
||||
};
|
||||
|
||||
_server.Value.Client.SendRequest("updateCSharpBuffer", request);
|
||||
}, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
|
||||
};
|
||||
|
||||
return codeContainer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// 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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class DefaultHostDocumentFactory : HostDocumentFactory
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly GeneratedCodeContainerStore _generatedCodeContainerStore;
|
||||
|
||||
public DefaultHostDocumentFactory(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
GeneratedCodeContainerStore generatedCodeContainerStore)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (generatedCodeContainerStore == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generatedCodeContainerStore));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_generatedCodeContainerStore = generatedCodeContainerStore;
|
||||
}
|
||||
|
||||
public override HostDocument Create(string filePath, string targetFilePath)
|
||||
=> Create(filePath, targetFilePath, fileKind: null);
|
||||
|
||||
public override HostDocument Create(string filePath, string targetFilePath, string fileKind)
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
if (targetFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetFilePath));
|
||||
}
|
||||
|
||||
var hostDocument = new HostDocument(filePath, targetFilePath, fileKind);
|
||||
hostDocument.GeneratedCodeContainer.GeneratedCodeChanged += (sender, args) =>
|
||||
{
|
||||
var sharedContainer = _generatedCodeContainerStore.Get(filePath);
|
||||
var container = (GeneratedCodeContainer)sender;
|
||||
var latestDocument = (DefaultDocumentSnapshot)container.LatestDocument;
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
var codeDocument = await latestDocument.GetGeneratedOutputAsync();
|
||||
|
||||
sharedContainer.SetOutput(
|
||||
latestDocument,
|
||||
codeDocument.GetCSharpDocument(),
|
||||
container.InputVersion,
|
||||
container.OutputVersion);
|
||||
}, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.BackgroundScheduler);
|
||||
|
||||
};
|
||||
|
||||
return hostDocument;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class DefaultProjectSnapshotManagerAccessor : ProjectSnapshotManagerAccessor
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly IEnumerable<ProjectSnapshotChangeTrigger> _changeTriggers;
|
||||
private readonly FilePathNormalizer _filePathNormalizer;
|
||||
private ProjectSnapshotManagerBase _instance;
|
||||
|
||||
public DefaultProjectSnapshotManagerAccessor(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
IEnumerable<ProjectSnapshotChangeTrigger> changeTriggers,
|
||||
FilePathNormalizer filePathNormalizer)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (changeTriggers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(changeTriggers));
|
||||
}
|
||||
|
||||
if (filePathNormalizer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePathNormalizer));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_changeTriggers = changeTriggers;
|
||||
_filePathNormalizer = filePathNormalizer;
|
||||
}
|
||||
|
||||
public override ProjectSnapshotManagerBase Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
var services = AdhocServices.Create(
|
||||
workspaceServices: new[]
|
||||
{
|
||||
new RemoteProjectSnapshotProjectEngineFactory(_filePathNormalizer)
|
||||
},
|
||||
razorLanguageServices: Enumerable.Empty<ILanguageService>());
|
||||
var workspace = new AdhocWorkspace(services);
|
||||
_instance = new DefaultProjectSnapshotManager(
|
||||
_foregroundDispatcher,
|
||||
new DefaultErrorReporter(),
|
||||
_changeTriggers,
|
||||
workspace);
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// 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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class DefaultRemoteTextLoaderFactory : RemoteTextLoaderFactory
|
||||
{
|
||||
private const string GetTextDocumentMethod = "getTextDocument";
|
||||
private readonly ILanguageServer _router;
|
||||
private readonly FilePathNormalizer _filePathNormalizer;
|
||||
|
||||
public DefaultRemoteTextLoaderFactory(
|
||||
ILanguageServer router,
|
||||
FilePathNormalizer filePathNormalizer)
|
||||
{
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
if (filePathNormalizer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePathNormalizer));
|
||||
}
|
||||
|
||||
_router = router;
|
||||
_filePathNormalizer = filePathNormalizer;
|
||||
}
|
||||
|
||||
public override TextLoader Create(string filePath)
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
var normalizedPath = _filePathNormalizer.Normalize(filePath);
|
||||
return new RemoteTextLoader(normalizedPath, _router);
|
||||
}
|
||||
|
||||
private class RemoteTextLoader : TextLoader
|
||||
{
|
||||
private readonly string _filePath;
|
||||
private readonly ILanguageServer _router;
|
||||
|
||||
public RemoteTextLoader(string filePath, ILanguageServer router)
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
_filePath = filePath;
|
||||
_router = router;
|
||||
}
|
||||
|
||||
public override async Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
|
||||
{
|
||||
var document = await _router.Client.SendRequest<string, TextDocumentItem>(GetTextDocumentMethod, _filePath);
|
||||
var sourceText = SourceText.From(document.Text);
|
||||
var textAndVersion = TextAndVersion.Create(sourceText, VersionStamp.Default);
|
||||
return textAndVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal abstract class DocumentProcessedListener
|
||||
{
|
||||
public abstract void Initialize(ProjectSnapshotManager projectManager);
|
||||
|
||||
public abstract void DocumentProcessed(DocumentSnapshot document);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// 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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class DocumentSnapshotTextLoader : TextLoader
|
||||
{
|
||||
private readonly DocumentSnapshot _documentSnapshot;
|
||||
|
||||
public DocumentSnapshotTextLoader(DocumentSnapshot documentSnapshot)
|
||||
{
|
||||
if (documentSnapshot == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentSnapshot));
|
||||
}
|
||||
|
||||
_documentSnapshot = documentSnapshot;
|
||||
}
|
||||
|
||||
public override async Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
|
||||
{
|
||||
var sourceText = await _documentSnapshot.GetTextAsync();
|
||||
var textAndVersion = TextAndVersion.Create(sourceText, VersionStamp.Default);
|
||||
|
||||
return textAndVersion;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal abstract class DocumentVersionCache : ProjectSnapshotChangeTrigger
|
||||
{
|
||||
public abstract bool TryGetDocumentVersion(DocumentSnapshot documentSnapshot, out long version);
|
||||
|
||||
public abstract void TrackDocumentVersion(DocumentSnapshot documentSnapshot, long version);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal abstract class GeneratedCodeContainerStore : ProjectSnapshotChangeTrigger
|
||||
{
|
||||
public abstract GeneratedCodeContainer Get(string physicalFilePath);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal abstract class HostDocumentFactory
|
||||
{
|
||||
public abstract HostDocument Create(string filePath, string targetFilePath);
|
||||
|
||||
public abstract HostDocument Create(string filePath, string targetFilePath, string fileKind);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
[Serial, Method("razor/languageQuery")]
|
||||
internal interface IRazorLanguageQueryHandler : IJsonRpcRequestHandler<RazorLanguageQueryParams, RazorLanguageQueryResponse>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains a Razor language server.</Description>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;osx-x64;</RuntimeIdentifiers>
|
||||
<AssemblyName>rzls</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OmniSharp.Extensions.LanguageServer" Version="$(OmniSharpExtensionsLanguageServerPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.LanguageServer.Common\Microsoft.AspNetCore.Razor.LanguageServer.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="_IncludeOmniSharpPlugin" Condition="Exists('$(PublishDir)')">
|
||||
<PropertyGroup>
|
||||
<TargetPluginOutputPath>$(PublishDir)\OmniSharpPlugin</TargetPluginOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<MSBuild Projects="..\Microsoft.AspNetCore.Razor.OmniSharpPlugin\Microsoft.AspNetCore.Razor.OmniSharpPlugin.csproj" Properties="PublishDir=$(TargetPluginOutputPath);RuntimeIdentifier=" Targets="Publish" />
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Technique for publishing multiple RIDs from
|
||||
https://github.com/dotnet/cli/issues/9221#issuecomment-387512008
|
||||
Example usage:
|
||||
dotnet msbuild -restore -t:PublishAllRids -p:Configuration=Release
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
|
||||
<!-- Enable roll-forward to latest patch. This allows one restore operation
|
||||
to apply to all of the self-contained publish operations. -->
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<RidsPublishDir>$(ArtifactsDir)LanguageServer\$(Configuration)\</RidsPublishDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PublishAllRids">
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Transform RuntimeIdentifiers property to item -->
|
||||
<RuntimeIdentifierForPublish Include="$(RuntimeIdentifiers)" />
|
||||
|
||||
<!-- Transform RuntimeIdentifierForPublish items to project items to pass to MSBuild task -->
|
||||
<ProjectToPublish Include="@(RuntimeIdentifierForPublish->'$(MSBuildProjectFullPath)')">
|
||||
<AdditionalProperties>RuntimeIdentifier=%(RuntimeIdentifierForPublish.Identity);PublishDir=$(RidsPublishDir)%(RuntimeIdentifierForPublish.Identity)\</AdditionalProperties>
|
||||
</ProjectToPublish>
|
||||
</ItemGroup>
|
||||
|
||||
<MSBuild Projects="@(ProjectToPublish)"
|
||||
Targets="Publish;_IncludeOmniSharpPlugin"
|
||||
BuildInParallel="false" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,129 @@
|
|||
// 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 System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Completion;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.Completion;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
|
||||
using OmniSharp.Extensions.LanguageServer.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
MainAsync(args).Wait();
|
||||
}
|
||||
|
||||
public static async Task MainAsync(string[] args)
|
||||
{
|
||||
var logLevel = LogLevel.Information;
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i].IndexOf("debug", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
while (!Debugger.IsAttached)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
Debugger.Break();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] == "--logLevel" && i + 1 < args.Length)
|
||||
{
|
||||
var logLevelString = args[++i];
|
||||
if (!Enum.TryParse(logLevelString, out logLevel))
|
||||
{
|
||||
logLevel = LogLevel.Information;
|
||||
Console.WriteLine($"Invalid log level '{logLevelString}'. Defaulting to {logLevel.ToString()}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serializer.Instance.JsonSerializer.Converters.RegisterRazorConverters();
|
||||
|
||||
var factory = new LoggerFactory();
|
||||
ILanguageServer server = null;
|
||||
server = await OmniSharp.Extensions.LanguageServer.Server.LanguageServer.From(options =>
|
||||
options
|
||||
.WithInput(Console.OpenStandardInput())
|
||||
.WithOutput(Console.OpenStandardOutput())
|
||||
.WithLoggerFactory(factory)
|
||||
.AddDefaultLoggingProvider()
|
||||
.WithMinimumLogLevel(logLevel)
|
||||
.WithHandler<RazorDocumentSynchronizationEndpoint>()
|
||||
.WithHandler<RazorCompletionEndpoint>()
|
||||
.WithHandler<RazorLanguageEndpoint>()
|
||||
.WithHandler<RazorProjectEndpoint>()
|
||||
.WithServices(services =>
|
||||
{
|
||||
services.AddSingleton<RemoteTextLoaderFactory, DefaultRemoteTextLoaderFactory>();
|
||||
services.AddSingleton<ProjectResolver, DefaultProjectResolver>();
|
||||
services.AddSingleton<DocumentResolver, DefaultDocumentResolver>();
|
||||
services.AddSingleton<FilePathNormalizer>();
|
||||
services.AddSingleton<RazorProjectService, DefaultRazorProjectService>();
|
||||
services.AddSingleton<ProjectSnapshotChangeTrigger, BackgroundDocumentGenerator>();
|
||||
services.AddSingleton<DocumentProcessedListener, RazorDiagnosticsPublisher>();
|
||||
services.AddSingleton<HostDocumentFactory, DefaultHostDocumentFactory>();
|
||||
services.AddSingleton<ProjectSnapshotManagerAccessor, DefaultProjectSnapshotManagerAccessor>();
|
||||
services.AddSingleton<TagHelperFactsService, DefaultTagHelperFactsService>();
|
||||
services.AddSingleton<VisualStudio.Editor.Razor.TagHelperCompletionService, VisualStudio.Editor.Razor.DefaultTagHelperCompletionService>();
|
||||
services.AddSingleton<TagHelperDescriptionFactory, DefaultTagHelperDescriptionFactory>();
|
||||
|
||||
// Completion
|
||||
services.AddSingleton<Completion.TagHelperCompletionService, Completion.DefaultTagHelperCompletionService>();
|
||||
services.AddSingleton<RazorCompletionItemProvider, DirectiveCompletionItemProvider>();
|
||||
services.AddSingleton<RazorCompletionItemProvider, DirectiveAttributeCompletionItemProvider>();
|
||||
services.AddSingleton<RazorCompletionItemProvider, DirectiveAttributeParameterCompletionItemProvider>();
|
||||
services.AddSingleton<RazorCompletionItemProvider, DirectiveAttributeTransitionCompletionItemProvider>();
|
||||
|
||||
var foregroundDispatcher = new VSCodeForegroundDispatcher();
|
||||
services.AddSingleton<ForegroundDispatcher>(foregroundDispatcher);
|
||||
services.AddSingleton<RazorCompletionFactsService, DefaultRazorCompletionFactsService>();
|
||||
var documentVersionCache = new DefaultDocumentVersionCache(foregroundDispatcher);
|
||||
services.AddSingleton<DocumentVersionCache>(documentVersionCache);
|
||||
services.AddSingleton<ProjectSnapshotChangeTrigger>(documentVersionCache);
|
||||
var containerStore = new DefaultGeneratedCodeContainerStore(
|
||||
foregroundDispatcher,
|
||||
documentVersionCache,
|
||||
new Lazy<ILanguageServer>(() => server));
|
||||
services.AddSingleton<GeneratedCodeContainerStore>(containerStore);
|
||||
services.AddSingleton<ProjectSnapshotChangeTrigger>(containerStore);
|
||||
}));
|
||||
|
||||
// Workaround for https://github.com/OmniSharp/csharp-language-server-protocol/issues/106
|
||||
var languageServer = (OmniSharp.Extensions.LanguageServer.Server.LanguageServer)server;
|
||||
languageServer.MinimumLogLevel = logLevel;
|
||||
|
||||
try
|
||||
{
|
||||
var logger = factory.CreateLogger<Program>();
|
||||
var assemblyInformationAttribute = typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
logger.LogInformation("Razor Language Server version " + assemblyInformationAttribute.InformationalVersion);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow exceptions from determining assembly information.
|
||||
}
|
||||
|
||||
await server.WaitForExit;
|
||||
|
||||
TempDirectory.Instance.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal abstract class ProjectSnapshotManagerAccessor
|
||||
{
|
||||
public abstract ProjectSnapshotManagerBase Instance { get; }
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class AddDocumentParams : IRequest
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// 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 System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class DefaultDocumentResolver : DocumentResolver
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly ProjectResolver _projectResolver;
|
||||
private readonly FilePathNormalizer _filePathNormalizer;
|
||||
|
||||
public DefaultDocumentResolver(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ProjectResolver projectResolver,
|
||||
FilePathNormalizer filePathNormalizer)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (projectResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectResolver));
|
||||
}
|
||||
|
||||
if (filePathNormalizer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePathNormalizer));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_projectResolver = projectResolver;
|
||||
_filePathNormalizer = filePathNormalizer;
|
||||
}
|
||||
|
||||
public override bool TryResolveDocument(string documentFilePath, out DocumentSnapshot document)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var normalizedPath = _filePathNormalizer.Normalize(documentFilePath);
|
||||
if (!_projectResolver.TryResolvePotentialProject(normalizedPath, out var project))
|
||||
{
|
||||
project = _projectResolver.GetMiscellaneousProject();
|
||||
}
|
||||
|
||||
if (!project.DocumentFilePaths.Contains(normalizedPath, FilePathComparer.Instance))
|
||||
{
|
||||
// Miscellaneous project and other tracked projects do not contain document.
|
||||
document = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
document = project.GetDocument(normalizedPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// 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 System.IO;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class DefaultProjectResolver : ProjectResolver
|
||||
{
|
||||
// Internal for testing
|
||||
protected internal readonly HostProject _miscellaneousHostProject;
|
||||
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly FilePathNormalizer _filePathNormalizer;
|
||||
private readonly ProjectSnapshotManagerAccessor _projectSnapshotManagerAccessor;
|
||||
|
||||
public DefaultProjectResolver(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
FilePathNormalizer filePathNormalizer,
|
||||
ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (filePathNormalizer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePathNormalizer));
|
||||
}
|
||||
|
||||
if (projectSnapshotManagerAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectSnapshotManagerAccessor));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_filePathNormalizer = filePathNormalizer;
|
||||
_projectSnapshotManagerAccessor = projectSnapshotManagerAccessor;
|
||||
|
||||
var miscellaneousProjectPath = Path.Combine(TempDirectory.Instance.DirectoryPath, "__MISC_RAZOR_PROJECT__");
|
||||
_miscellaneousHostProject = new HostProject(miscellaneousProjectPath, RazorDefaults.Configuration, RazorDefaults.RootNamespace);
|
||||
}
|
||||
|
||||
public override bool TryResolvePotentialProject(string documentFilePath, out ProjectSnapshot projectSnapshot)
|
||||
{
|
||||
if (documentFilePath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentFilePath));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var normalizedDocumentPath = _filePathNormalizer.Normalize(documentFilePath);
|
||||
var projects = _projectSnapshotManagerAccessor.Instance.Projects;
|
||||
for (var i = 0; i < projects.Count; i++)
|
||||
{
|
||||
if (projects[i].FilePath == _miscellaneousHostProject.FilePath)
|
||||
{
|
||||
// We don't resolve documents to belonging to the miscellaneous project.
|
||||
continue;
|
||||
}
|
||||
|
||||
var projectDirectory = _filePathNormalizer.GetDirectory(projects[i].FilePath);
|
||||
if (normalizedDocumentPath.StartsWith(projectDirectory))
|
||||
{
|
||||
projectSnapshot = projects[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
projectSnapshot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override ProjectSnapshot GetMiscellaneousProject()
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var miscellaneousProject = _projectSnapshotManagerAccessor.Instance.GetLoadedProject(_miscellaneousHostProject.FilePath);
|
||||
if (miscellaneousProject == null)
|
||||
{
|
||||
_projectSnapshotManagerAccessor.Instance.ProjectAdded(_miscellaneousHostProject);
|
||||
miscellaneousProject = _projectSnapshotManagerAccessor.Instance.GetLoadedProject(_miscellaneousHostProject.FilePath);
|
||||
}
|
||||
|
||||
return miscellaneousProject;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class DefaultRazorProjectService : RazorProjectService
|
||||
{
|
||||
private readonly ProjectSnapshotManagerAccessor _projectSnapshotManagerAccessor;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly HostDocumentFactory _hostDocumentFactory;
|
||||
private readonly RemoteTextLoaderFactory _remoteTextLoaderFactory;
|
||||
private readonly ProjectResolver _projectResolver;
|
||||
private readonly DocumentVersionCache _documentVersionCache;
|
||||
private readonly FilePathNormalizer _filePathNormalizer;
|
||||
private readonly DocumentResolver _documentResolver;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DefaultRazorProjectService(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
HostDocumentFactory hostDocumentFactory,
|
||||
RemoteTextLoaderFactory remoteTextLoaderFactory,
|
||||
DocumentResolver documentResolver,
|
||||
ProjectResolver projectResolver,
|
||||
DocumentVersionCache documentVersionCache,
|
||||
FilePathNormalizer filePathNormalizer,
|
||||
ProjectSnapshotManagerAccessor projectSnapshotManagerAccessor,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (hostDocumentFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hostDocumentFactory));
|
||||
}
|
||||
|
||||
if (remoteTextLoaderFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(remoteTextLoaderFactory));
|
||||
}
|
||||
|
||||
if (documentResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentResolver));
|
||||
}
|
||||
|
||||
if (projectResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectResolver));
|
||||
}
|
||||
|
||||
if (documentVersionCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentVersionCache));
|
||||
}
|
||||
|
||||
if (filePathNormalizer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filePathNormalizer));
|
||||
}
|
||||
|
||||
if (projectSnapshotManagerAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectSnapshotManagerAccessor));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_hostDocumentFactory = hostDocumentFactory;
|
||||
_remoteTextLoaderFactory = remoteTextLoaderFactory;
|
||||
_documentResolver = documentResolver;
|
||||
_projectResolver = projectResolver;
|
||||
_documentVersionCache = documentVersionCache;
|
||||
_filePathNormalizer = filePathNormalizer;
|
||||
_projectSnapshotManagerAccessor = projectSnapshotManagerAccessor;
|
||||
_logger = loggerFactory.CreateLogger<DefaultRazorProjectService>();
|
||||
}
|
||||
|
||||
public override void AddDocument(string filePath)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var textDocumentPath = _filePathNormalizer.Normalize(filePath);
|
||||
if (_documentResolver.TryResolveDocument(textDocumentPath, out var _))
|
||||
{
|
||||
// Document already added. This usually occurs when VSCode has already pre-initialized
|
||||
// open documents and then we try to manually add all known razor documents.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_projectResolver.TryResolvePotentialProject(textDocumentPath, out var projectSnapshot))
|
||||
{
|
||||
projectSnapshot = _projectResolver.GetMiscellaneousProject();
|
||||
}
|
||||
|
||||
var targetFilePath = textDocumentPath;
|
||||
var projectDirectory = _filePathNormalizer.GetDirectory(projectSnapshot.FilePath);
|
||||
if (targetFilePath.StartsWith(projectDirectory, FilePathComparison.Instance))
|
||||
{
|
||||
// Make relative
|
||||
targetFilePath = textDocumentPath.Substring(projectDirectory.Length);
|
||||
}
|
||||
|
||||
// Representing all of our host documents with a re-normalized target path to workaround GetRelatedDocument limitations.
|
||||
var normalizedTargetFilePath = targetFilePath.Replace('/', '\\').TrimStart('\\');
|
||||
|
||||
var hostDocument = _hostDocumentFactory.Create(textDocumentPath, normalizedTargetFilePath);
|
||||
var defaultProject = (DefaultProjectSnapshot)projectSnapshot;
|
||||
var textLoader = _remoteTextLoaderFactory.Create(textDocumentPath);
|
||||
|
||||
_logger.LogInformation($"Adding document '{filePath}' to project '{projectSnapshot.FilePath}'.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentAdded(defaultProject.HostProject, hostDocument, textLoader);
|
||||
}
|
||||
|
||||
public override void OpenDocument(string filePath, SourceText sourceText, long version)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var textDocumentPath = _filePathNormalizer.Normalize(filePath);
|
||||
if (!_documentResolver.TryResolveDocument(textDocumentPath, out var _))
|
||||
{
|
||||
// Document hasn't been added. This usually occurs when VSCode trumps all other initialization
|
||||
// processes and pre-initializes already open documents.
|
||||
AddDocument(filePath);
|
||||
}
|
||||
|
||||
if (!_projectResolver.TryResolvePotentialProject(textDocumentPath, out var projectSnapshot))
|
||||
{
|
||||
projectSnapshot = _projectResolver.GetMiscellaneousProject();
|
||||
}
|
||||
|
||||
var defaultProject = (DefaultProjectSnapshot)projectSnapshot;
|
||||
|
||||
_logger.LogInformation($"Opening document '{textDocumentPath}' in project '{projectSnapshot.FilePath}'.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentOpened(defaultProject.HostProject.FilePath, textDocumentPath, sourceText);
|
||||
TrackDocumentVersion(textDocumentPath, version);
|
||||
|
||||
}
|
||||
|
||||
public override void CloseDocument(string filePath)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var textDocumentPath = _filePathNormalizer.Normalize(filePath);
|
||||
if (!_projectResolver.TryResolvePotentialProject(textDocumentPath, out var projectSnapshot))
|
||||
{
|
||||
projectSnapshot = _projectResolver.GetMiscellaneousProject();
|
||||
}
|
||||
|
||||
var textLoader = _remoteTextLoaderFactory.Create(filePath);
|
||||
var defaultProject = (DefaultProjectSnapshot)projectSnapshot;
|
||||
_logger.LogInformation($"Closing document '{textDocumentPath}' in project '{projectSnapshot.FilePath}'.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentClosed(defaultProject.HostProject.FilePath, textDocumentPath, textLoader);
|
||||
}
|
||||
|
||||
public override void RemoveDocument(string filePath)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var textDocumentPath = _filePathNormalizer.Normalize(filePath);
|
||||
if (!_projectResolver.TryResolvePotentialProject(textDocumentPath, out var projectSnapshot))
|
||||
{
|
||||
projectSnapshot = _projectResolver.GetMiscellaneousProject();
|
||||
}
|
||||
|
||||
if (!projectSnapshot.DocumentFilePaths.Contains(textDocumentPath, FilePathComparer.Instance))
|
||||
{
|
||||
_logger.LogInformation($"Containing project is not tracking document '{filePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
var document = (DefaultDocumentSnapshot)projectSnapshot.GetDocument(textDocumentPath);
|
||||
var defaultProject = (DefaultProjectSnapshot)projectSnapshot;
|
||||
_logger.LogInformation($"Removing document '{textDocumentPath}' from project '{projectSnapshot.FilePath}'.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentRemoved(defaultProject.HostProject, document.State.HostDocument);
|
||||
}
|
||||
|
||||
public override void UpdateDocument(string filePath, SourceText sourceText, long version)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var textDocumentPath = _filePathNormalizer.Normalize(filePath);
|
||||
if (!_projectResolver.TryResolvePotentialProject(textDocumentPath, out var projectSnapshot))
|
||||
{
|
||||
projectSnapshot = _projectResolver.GetMiscellaneousProject();
|
||||
}
|
||||
|
||||
var defaultProject = (DefaultProjectSnapshot)projectSnapshot;
|
||||
_logger.LogTrace($"Updating document '{textDocumentPath}'.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentChanged(defaultProject.HostProject.FilePath, textDocumentPath, sourceText);
|
||||
|
||||
TrackDocumentVersion(textDocumentPath, version);
|
||||
}
|
||||
|
||||
public override void AddProject(string filePath)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var normalizedPath = _filePathNormalizer.Normalize(filePath);
|
||||
var hostProject = new HostProject(normalizedPath, RazorDefaults.Configuration, RazorDefaults.RootNamespace);
|
||||
_projectSnapshotManagerAccessor.Instance.ProjectAdded(hostProject);
|
||||
_logger.LogInformation($"Added project '{filePath}' to project system.");
|
||||
|
||||
TryMigrateMiscellaneousDocumentsToProject();
|
||||
}
|
||||
|
||||
public override void RemoveProject(string filePath)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var normalizedPath = _filePathNormalizer.Normalize(filePath);
|
||||
var project = (DefaultProjectSnapshot)_projectSnapshotManagerAccessor.Instance.GetLoadedProject(normalizedPath);
|
||||
|
||||
if (project == null)
|
||||
{
|
||||
// Never tracked the project to begin with, noop.
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Removing project '{filePath}' from project system.");
|
||||
_projectSnapshotManagerAccessor.Instance.ProjectRemoved(project.HostProject);
|
||||
|
||||
TryMigrateDocumentsFromRemovedProject(project);
|
||||
}
|
||||
|
||||
public override void UpdateProject(
|
||||
string filePath,
|
||||
RazorConfiguration configuration,
|
||||
string rootNamespace,
|
||||
ProjectWorkspaceState projectWorkspaceState,
|
||||
IReadOnlyList<DocumentSnapshotHandle> documents)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var normalizedPath = _filePathNormalizer.Normalize(filePath);
|
||||
var project = (DefaultProjectSnapshot)_projectSnapshotManagerAccessor.Instance.GetLoadedProject(normalizedPath);
|
||||
|
||||
if (project == null)
|
||||
{
|
||||
// Never tracked the project to begin with, noop.
|
||||
_logger.LogInformation($"Failed to update untracked project '{filePath}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProjectDocuments(documents, project);
|
||||
|
||||
if (!projectWorkspaceState.Equals(ProjectWorkspaceState.Default))
|
||||
{
|
||||
_logger.LogInformation($"Updating project '{filePath}' TagHelpers ({projectWorkspaceState.TagHelpers.Count}) and C# Language Version ({projectWorkspaceState.CSharpLanguageVersion}).");
|
||||
}
|
||||
|
||||
_projectSnapshotManagerAccessor.Instance.ProjectWorkspaceStateChanged(project.FilePath, projectWorkspaceState);
|
||||
|
||||
var currentHostProject = project.HostProject;
|
||||
var currentConfiguration = currentHostProject.Configuration;
|
||||
if (currentConfiguration.ConfigurationName == configuration?.ConfigurationName &&
|
||||
currentHostProject.RootNamespace == rootNamespace)
|
||||
{
|
||||
_logger.LogTrace($"Updating project '{filePath}'. The project is already using configuration '{configuration.ConfigurationName}' and root namespace '{rootNamespace}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (configuration == null)
|
||||
{
|
||||
configuration = RazorDefaults.Configuration;
|
||||
_logger.LogInformation($"Updating project '{filePath}' to use Razor's default configuration ('{configuration.ConfigurationName}')'.");
|
||||
}
|
||||
else if (currentConfiguration.ConfigurationName != configuration.ConfigurationName)
|
||||
{
|
||||
_logger.LogInformation($"Updating project '{filePath}' to Razor configuration '{configuration.ConfigurationName}' with language version '{configuration.LanguageVersion}'.");
|
||||
}
|
||||
|
||||
if (currentHostProject.RootNamespace != rootNamespace)
|
||||
{
|
||||
_logger.LogInformation($"Updating project '{filePath}''s root namespace to '{rootNamespace}'.");
|
||||
}
|
||||
|
||||
var hostProject = new HostProject(project.FilePath, configuration, rootNamespace);
|
||||
_projectSnapshotManagerAccessor.Instance.ProjectConfigurationChanged(hostProject);
|
||||
}
|
||||
|
||||
private void UpdateProjectDocuments(IReadOnlyList<DocumentSnapshotHandle> documents, DefaultProjectSnapshot project)
|
||||
{
|
||||
var currentHostProject = project.HostProject;
|
||||
var projectDirectory = _filePathNormalizer.GetDirectory(project.FilePath);
|
||||
var documentMap = documents.ToDictionary(document => EnsureFullPath(document.FilePath, projectDirectory), FilePathComparer.Instance);
|
||||
|
||||
foreach (var documentFilePath in project.DocumentFilePaths)
|
||||
{
|
||||
if (!documentMap.TryGetValue(documentFilePath, out var documentHandle))
|
||||
{
|
||||
// Document exists in the project but not in the configured documents. Chances are the project configuration is from a fallback
|
||||
// configuration case (< 2.1) or the project isn't fully loaded yet.
|
||||
continue;
|
||||
}
|
||||
|
||||
var documentSnapshot = (DefaultDocumentSnapshot)project.GetDocument(documentFilePath);
|
||||
var currentHostDocument = documentSnapshot.State.HostDocument;
|
||||
var newFilePath = EnsureFullPath(documentHandle.FilePath, projectDirectory);
|
||||
var newHostDocument = _hostDocumentFactory.Create(newFilePath, documentHandle.TargetPath, documentHandle.FileKind);
|
||||
|
||||
if (HostDocumentComparer.Instance.Equals(currentHostDocument, newHostDocument))
|
||||
{
|
||||
// Current and "new" host documents are equivalent
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogTrace($"Updating document '{newHostDocument.FilePath}''s file kind to '{newHostDocument.FileKind}' and target path to '{newHostDocument.TargetPath}'.");
|
||||
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentRemoved(currentHostProject, currentHostDocument);
|
||||
|
||||
var remoteTextLoader = _remoteTextLoaderFactory.Create(newFilePath);
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentAdded(currentHostProject, newHostDocument, remoteTextLoader);
|
||||
}
|
||||
}
|
||||
|
||||
private string EnsureFullPath(string filePath, string projectDirectory)
|
||||
{
|
||||
var normalizedFilePath = _filePathNormalizer.Normalize(filePath);
|
||||
if (!normalizedFilePath.StartsWith(projectDirectory))
|
||||
{
|
||||
// Remove '/' from document path
|
||||
normalizedFilePath = normalizedFilePath.Substring(1);
|
||||
normalizedFilePath = _filePathNormalizer.Normalize(projectDirectory + normalizedFilePath);
|
||||
}
|
||||
|
||||
return normalizedFilePath;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void TryMigrateDocumentsFromRemovedProject(ProjectSnapshot project)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var miscellaneousProject = _projectResolver.GetMiscellaneousProject();
|
||||
|
||||
foreach (var documentFilePath in project.DocumentFilePaths)
|
||||
{
|
||||
var documentSnapshot = (DefaultDocumentSnapshot)project.GetDocument(documentFilePath);
|
||||
|
||||
if (!_projectResolver.TryResolvePotentialProject(documentFilePath, out var toProject))
|
||||
{
|
||||
// This is the common case. It'd be rare for a project to be nested but we need to protect against it anyhow.
|
||||
toProject = miscellaneousProject;
|
||||
}
|
||||
|
||||
var textLoader = new DocumentSnapshotTextLoader(documentSnapshot);
|
||||
var defaultToProject = (DefaultProjectSnapshot)toProject;
|
||||
var newHostDocument = _hostDocumentFactory.Create(documentSnapshot.FilePath, documentSnapshot.TargetPath);
|
||||
|
||||
_logger.LogInformation($"Migrating '{documentFilePath}' from the '{project.FilePath}' project to '{toProject.FilePath}' project.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentAdded(defaultToProject.HostProject, newHostDocument, textLoader);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void TryMigrateMiscellaneousDocumentsToProject()
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var miscellaneousProject = _projectResolver.GetMiscellaneousProject();
|
||||
|
||||
foreach (var documentFilePath in miscellaneousProject.DocumentFilePaths)
|
||||
{
|
||||
if (!_projectResolver.TryResolvePotentialProject(documentFilePath, out var projectSnapshot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var documentSnapshot = (DefaultDocumentSnapshot)miscellaneousProject.GetDocument(documentFilePath);
|
||||
|
||||
// Remove from miscellaneous project
|
||||
var defaultMiscProject = (DefaultProjectSnapshot)miscellaneousProject;
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentRemoved(defaultMiscProject.HostProject, documentSnapshot.State.HostDocument);
|
||||
|
||||
// Add to new project
|
||||
|
||||
var textLoader = new DocumentSnapshotTextLoader(documentSnapshot);
|
||||
var defaultProject = (DefaultProjectSnapshot)projectSnapshot;
|
||||
var newHostDocument = _hostDocumentFactory.Create(documentSnapshot.FilePath, documentSnapshot.TargetPath);
|
||||
_logger.LogInformation($"Migrating '{documentFilePath}' from the '{miscellaneousProject.FilePath}' project to '{projectSnapshot.FilePath}' project.");
|
||||
_projectSnapshotManagerAccessor.Instance.DocumentAdded(defaultProject.HostProject, newHostDocument, textLoader);
|
||||
}
|
||||
}
|
||||
|
||||
private void TrackDocumentVersion(string textDocumentPath, long version)
|
||||
{
|
||||
if (!_documentResolver.TryResolveDocument(textDocumentPath, out var documentSnapshot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_documentVersionCache.TrackDocumentVersion(documentSnapshot, version);
|
||||
}
|
||||
|
||||
private class DelegatingTextLoader : TextLoader
|
||||
{
|
||||
private readonly DocumentSnapshot _fromDocument;
|
||||
public DelegatingTextLoader(DocumentSnapshot fromDocument)
|
||||
{
|
||||
_fromDocument = fromDocument ?? throw new ArgumentNullException(nameof(fromDocument));
|
||||
}
|
||||
public override async Task<TextAndVersion> LoadTextAndVersionAsync(
|
||||
Workspace workspace,
|
||||
DocumentId documentId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var sourceText = await _fromDocument.GetTextAsync();
|
||||
var version = await _fromDocument.GetTextVersionAsync();
|
||||
var textAndVersion = TextAndVersion.Create(sourceText, version.GetNewerVersion());
|
||||
return textAndVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal abstract class DocumentResolver
|
||||
{
|
||||
public abstract bool TryResolveDocument(string documentFilePath, out DocumentSnapshot document);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Threading.Tasks;
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
[Parallel, Method("projects/addDocument")]
|
||||
internal interface IRazorAddDocumentHandler : IJsonRpcRequestHandler<AddDocumentParams>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
[Serial, Method("projects/addProject")]
|
||||
internal interface IRazorAddProjectHandler : IJsonRpcNotificationHandler<RazorAddProjectParams>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Threading.Tasks;
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
[Parallel, Method("projects/removeDocument")]
|
||||
internal interface IRazorRemoveDocumentHandler : IJsonRpcRequestHandler<RemoveDocumentParams>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
[Serial, Method("projects/removeProject")]
|
||||
internal interface IRazorRemoveProjectHandler : IJsonRpcNotificationHandler<RazorRemoveProjectParams>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
[Serial, Method("projects/updateProject")]
|
||||
internal interface IRazorUpdateProjectHandler : IJsonRpcNotificationHandler<RazorUpdateProjectParams>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal abstract class ProjectResolver
|
||||
{
|
||||
public abstract bool TryResolvePotentialProject(string documentFilePath, out ProjectSnapshot projectSnapshot);
|
||||
|
||||
public abstract ProjectSnapshot GetMiscellaneousProject();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class RazorAddProjectParams : IRequest
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common.Serialization;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal abstract class RazorProjectService
|
||||
{
|
||||
public abstract void AddDocument(string filePath);
|
||||
|
||||
public abstract void OpenDocument(string filePath, SourceText sourceText, long version);
|
||||
|
||||
public abstract void CloseDocument(string filePath);
|
||||
|
||||
public abstract void RemoveDocument(string filePath);
|
||||
|
||||
public abstract void UpdateDocument(string filePath, SourceText sourceText, long version);
|
||||
|
||||
public abstract void AddProject(string filePath);
|
||||
|
||||
public abstract void RemoveProject(string filePath);
|
||||
|
||||
public abstract void UpdateProject(
|
||||
string filePath,
|
||||
RazorConfiguration configuration,
|
||||
string rootNamespace,
|
||||
ProjectWorkspaceState projectWorkspaceState,
|
||||
IReadOnlyList<DocumentSnapshotHandle> documents);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class RazorRemoveProjectParams : IRequest
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 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.LanguageServer.Common.Serialization;
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class RazorUpdateProjectParams : IRequest
|
||||
{
|
||||
public FullProjectSnapshotHandle ProjectSnapshotHandle { get; set; }
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
|
||||
{
|
||||
internal class RemoveDocumentParams : IRequest
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.LanguageServer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
@ -0,0 +1,22 @@
|
|||
// 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.Razor.ProjectSystem;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
public static class RazorDefaults
|
||||
{
|
||||
public static DocumentSelector Selector { get; } = new DocumentSelector(
|
||||
new DocumentFilter()
|
||||
{
|
||||
Pattern = "**/*.{cshtml,razor}"
|
||||
});
|
||||
|
||||
public static RazorConfiguration Configuration { get; } = FallbackRazorConfiguration.MVC_2_1;
|
||||
|
||||
public static string RootNamespace { get; } = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// 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.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal static class RazorDiagnosticConverter
|
||||
{
|
||||
public static Diagnostic Convert(RazorDiagnostic razorDiagnostic, SourceText sourceText)
|
||||
{
|
||||
if (razorDiagnostic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(razorDiagnostic));
|
||||
}
|
||||
|
||||
if (sourceText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceText));
|
||||
}
|
||||
|
||||
var diagnostic = new Diagnostic()
|
||||
{
|
||||
Message = razorDiagnostic.GetMessage(),
|
||||
Code = razorDiagnostic.Id,
|
||||
Severity = ConvertSeverity(razorDiagnostic.Severity),
|
||||
Range = ConvertSpanToRange(razorDiagnostic.Span, sourceText),
|
||||
};
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static DiagnosticSeverity ConvertSeverity(RazorDiagnosticSeverity severity)
|
||||
{
|
||||
switch (severity)
|
||||
{
|
||||
case RazorDiagnosticSeverity.Error:
|
||||
return DiagnosticSeverity.Error;
|
||||
default:
|
||||
return DiagnosticSeverity.Information;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static Range ConvertSpanToRange(SourceSpan sourceSpan, SourceText sourceText)
|
||||
{
|
||||
if (sourceSpan == SourceSpan.Undefined)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var spanStartIndex = NormalizeIndex(sourceSpan.AbsoluteIndex);
|
||||
var startPosition = sourceText.Lines.GetLinePosition(spanStartIndex);
|
||||
var start = new Position()
|
||||
{
|
||||
Line = startPosition.Line,
|
||||
Character = startPosition.Character,
|
||||
};
|
||||
|
||||
var spanEndIndex = NormalizeIndex(sourceSpan.AbsoluteIndex + sourceSpan.Length);
|
||||
var endPosition = sourceText.Lines.GetLinePosition(spanEndIndex);
|
||||
var end = new Position()
|
||||
{
|
||||
Line = endPosition.Line,
|
||||
Character = endPosition.Character,
|
||||
};
|
||||
var range = new Range()
|
||||
{
|
||||
Start = start,
|
||||
End = end,
|
||||
};
|
||||
|
||||
return range;
|
||||
|
||||
int NormalizeIndex(int index)
|
||||
{
|
||||
if (index >= sourceText.Length)
|
||||
{
|
||||
// Span start index is past the end of the document. Roslyn and VSCode don't support
|
||||
// virtual positions that don't exist on the document; normalize to the last character.
|
||||
index = sourceText.Length - 1;
|
||||
}
|
||||
|
||||
return Math.Max(index, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class RazorDiagnosticsPublisher : DocumentProcessedListener
|
||||
{
|
||||
// Internal for testing
|
||||
internal readonly Dictionary<string, IReadOnlyList<RazorDiagnostic>> _publishedDiagnostics;
|
||||
internal Timer _workTimer;
|
||||
internal Timer _documentClosedTimer;
|
||||
|
||||
private static readonly TimeSpan PublishDelay = TimeSpan.FromSeconds(2);
|
||||
private static readonly TimeSpan CheckForDocumentClosedDelay = TimeSpan.FromSeconds(5);
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly ILanguageServer _languageServer;
|
||||
private readonly Dictionary<string, DocumentSnapshot> _work;
|
||||
private readonly ILogger<RazorDiagnosticsPublisher> _logger;
|
||||
private ProjectSnapshotManager _projectManager;
|
||||
|
||||
public RazorDiagnosticsPublisher(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ILanguageServer languageServer,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (languageServer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(languageServer));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_languageServer = languageServer;
|
||||
_publishedDiagnostics = new Dictionary<string, IReadOnlyList<RazorDiagnostic>>(FilePathComparer.Instance);
|
||||
_work = new Dictionary<string, DocumentSnapshot>(FilePathComparer.Instance);
|
||||
_logger = loggerFactory.CreateLogger<RazorDiagnosticsPublisher>();
|
||||
}
|
||||
|
||||
public override void Initialize(ProjectSnapshotManager projectManager)
|
||||
{
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
}
|
||||
|
||||
_projectManager = projectManager;
|
||||
}
|
||||
|
||||
public override void DocumentProcessed(DocumentSnapshot document)
|
||||
{
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(document));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
lock (_work)
|
||||
{
|
||||
_work[document.FilePath] = document;
|
||||
StartWorkTimer();
|
||||
StartDocumentClosedCheckTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartWorkTimer()
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
// Access to the timer is protected by the lock in Synchronize and in Timer_Tick
|
||||
if (_workTimer == null)
|
||||
{
|
||||
// Timer will fire after a fixed delay, but only once.
|
||||
_workTimer = new Timer(WorkTimer_Tick, null, PublishDelay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartDocumentClosedCheckTimer()
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
if (_documentClosedTimer == null)
|
||||
{
|
||||
_documentClosedTimer = new Timer(DocumentClosedTimer_Tick, null, CheckForDocumentClosedDelay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DocumentClosedTimer_Tick(object state)
|
||||
{
|
||||
await Task.Factory.StartNew(
|
||||
ClearClosedDocuments,
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
_foregroundDispatcher.ForegroundScheduler);
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void ClearClosedDocuments()
|
||||
{
|
||||
lock (_publishedDiagnostics)
|
||||
{
|
||||
var publishedDiagnostics = new Dictionary<string, IReadOnlyList<RazorDiagnostic>>(_publishedDiagnostics);
|
||||
foreach (var entry in publishedDiagnostics)
|
||||
{
|
||||
if (!_projectManager.IsDocumentOpen(entry.Key))
|
||||
{
|
||||
// Document is now closed, we shouldn't track its diagnostics anymore.
|
||||
_publishedDiagnostics.Remove(entry.Key);
|
||||
|
||||
// If the last published diagnostics for the document were > 0 then we need to clear them out so the user
|
||||
// doesn't have a ton of closed document errors that they can't get rid of.
|
||||
if (entry.Value.Count > 0)
|
||||
{
|
||||
PublishDiagnosticsForFilePath(entry.Key, Array.Empty<Diagnostic>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_documentClosedTimer?.Dispose();
|
||||
_documentClosedTimer = null;
|
||||
|
||||
if (_publishedDiagnostics.Count > 0)
|
||||
{
|
||||
// There's no way for us to know when a document is closed at this layer. Therefore, we need to poll every X seconds
|
||||
// and check if the currently tracked documents are closed. In practice this work is super minimal.
|
||||
StartDocumentClosedCheckTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal async Task PublishDiagnosticsAsync(DocumentSnapshot document)
|
||||
{
|
||||
var result = await document.GetGeneratedOutputAsync();
|
||||
|
||||
var diagnostics = result.GetCSharpDocument().Diagnostics;
|
||||
|
||||
lock (_publishedDiagnostics)
|
||||
{
|
||||
if (_publishedDiagnostics.TryGetValue(document.FilePath, out var previousDiagnostics) &&
|
||||
diagnostics.SequenceEqual(previousDiagnostics))
|
||||
{
|
||||
// Diagnostics are the same as last publish
|
||||
return;
|
||||
}
|
||||
|
||||
_publishedDiagnostics[document.FilePath] = diagnostics;
|
||||
}
|
||||
|
||||
if (!document.TryGetText(out var sourceText))
|
||||
{
|
||||
Debug.Fail("Document source text should already be available.");
|
||||
}
|
||||
var convertedDiagnostics = diagnostics.Select(razorDiagnostic => RazorDiagnosticConverter.Convert(razorDiagnostic, sourceText));
|
||||
|
||||
PublishDiagnosticsForFilePath(document.FilePath, convertedDiagnostics);
|
||||
|
||||
if (_logger.IsEnabled(LogLevel.Trace))
|
||||
{
|
||||
var diagnosticString = string.Join(", ", diagnostics.Select(diagnostic => diagnostic.Id));
|
||||
_logger.LogTrace($"Publishing diagnostics for document '{document.FilePath}': {diagnosticString}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void WorkTimer_Tick(object state)
|
||||
{
|
||||
DocumentSnapshot[] documents;
|
||||
lock (_work)
|
||||
{
|
||||
documents = _work.Values.ToArray();
|
||||
_work.Clear();
|
||||
}
|
||||
|
||||
for (var i = 0; i < documents.Length; i++)
|
||||
{
|
||||
var document = documents[i];
|
||||
await PublishDiagnosticsAsync(document);
|
||||
}
|
||||
|
||||
lock (_work)
|
||||
{
|
||||
// Resetting the timer allows another batch of work to start.
|
||||
_workTimer.Dispose();
|
||||
_workTimer = null;
|
||||
|
||||
// If more work came in while we were running start the timer again.
|
||||
if (_work.Count > 0)
|
||||
{
|
||||
StartWorkTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishDiagnosticsForFilePath(string filePath, IEnumerable<Diagnostic> diagnostics)
|
||||
{
|
||||
var uriBuilder = new UriBuilder()
|
||||
{
|
||||
Scheme = Uri.UriSchemeFile,
|
||||
Path = filePath,
|
||||
Host = string.Empty,
|
||||
};
|
||||
_languageServer.Document.PublishDiagnostics(new PublishDiagnosticsParams()
|
||||
{
|
||||
Uri = uriBuilder.Uri,
|
||||
Diagnostics = new Container<Diagnostic>(diagnostics),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class RazorDocumentSynchronizationEndpoint : ITextDocumentSyncHandler
|
||||
{
|
||||
private SynchronizationCapability _capability;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly DocumentResolver _documentResolver;
|
||||
private readonly RazorProjectService _projectService;
|
||||
|
||||
public RazorDocumentSynchronizationEndpoint(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
DocumentResolver documentResolver,
|
||||
RazorProjectService projectService,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (documentResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentResolver));
|
||||
}
|
||||
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_documentResolver = documentResolver;
|
||||
_projectService = projectService;
|
||||
_logger = loggerFactory.CreateLogger<RazorDocumentSynchronizationEndpoint>();
|
||||
}
|
||||
|
||||
public TextDocumentSyncKind Change { get; } = TextDocumentSyncKind.Incremental;
|
||||
|
||||
public void SetCapability(SynchronizationCapability capability)
|
||||
{
|
||||
_capability = capability;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(DidChangeTextDocumentParams notification, CancellationToken token)
|
||||
{
|
||||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
var document = await Task.Factory.StartNew(() =>
|
||||
{
|
||||
_documentResolver.TryResolveDocument(notification.TextDocument.Uri.AbsolutePath, out var documentSnapshot);
|
||||
|
||||
return documentSnapshot;
|
||||
}, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
var sourceText = await document.GetTextAsync();
|
||||
sourceText = ApplyContentChanges(notification.ContentChanges, sourceText);
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
() => _projectService.UpdateDocument(document.FilePath, sourceText, notification.TextDocument.Version),
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
_foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(DidOpenTextDocumentParams notification, CancellationToken token)
|
||||
{
|
||||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
var sourceText = SourceText.From(notification.TextDocument.Text);
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
() => _projectService.OpenDocument(notification.TextDocument.Uri.AbsolutePath, sourceText, notification.TextDocument.Version),
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
_foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(DidCloseTextDocumentParams notification, CancellationToken token)
|
||||
{
|
||||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
() => _projectService.CloseDocument(notification.TextDocument.Uri.AbsolutePath),
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
_foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
public Task<Unit> Handle(DidSaveTextDocumentParams notification, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation($"Saved Document {notification.TextDocument.Uri.AbsolutePath}");
|
||||
|
||||
return Unit.Task;
|
||||
}
|
||||
|
||||
public TextDocumentAttributes GetTextDocumentAttributes(Uri uri)
|
||||
{
|
||||
return new TextDocumentAttributes(uri, "razor");
|
||||
}
|
||||
|
||||
TextDocumentChangeRegistrationOptions IRegistration<TextDocumentChangeRegistrationOptions>.GetRegistrationOptions()
|
||||
{
|
||||
return new TextDocumentChangeRegistrationOptions()
|
||||
{
|
||||
DocumentSelector = RazorDefaults.Selector,
|
||||
SyncKind = Change
|
||||
};
|
||||
}
|
||||
|
||||
TextDocumentRegistrationOptions IRegistration<TextDocumentRegistrationOptions>.GetRegistrationOptions()
|
||||
{
|
||||
return new TextDocumentRegistrationOptions()
|
||||
{
|
||||
DocumentSelector = RazorDefaults.Selector,
|
||||
};
|
||||
}
|
||||
|
||||
TextDocumentSaveRegistrationOptions IRegistration<TextDocumentSaveRegistrationOptions>.GetRegistrationOptions()
|
||||
{
|
||||
return new TextDocumentSaveRegistrationOptions()
|
||||
{
|
||||
DocumentSelector = RazorDefaults.Selector,
|
||||
IncludeText = true
|
||||
};
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal SourceText ApplyContentChanges(IEnumerable<TextDocumentContentChangeEvent> contentChanges, SourceText sourceText)
|
||||
{
|
||||
foreach (var change in contentChanges)
|
||||
{
|
||||
var linePosition = new LinePosition((int)change.Range.Start.Line, (int)change.Range.Start.Character);
|
||||
var position = sourceText.Lines.GetPosition(linePosition);
|
||||
var textSpan = new TextSpan(position, change.RangeLength);
|
||||
var textChange = new TextChange(textSpan, change.Text);
|
||||
|
||||
_logger.LogTrace("Applying " + textChange);
|
||||
|
||||
// If there happens to be multiple text changes we generate a new source text for each one. Due to the
|
||||
// differences in VSCode and Roslyn's representation we can't pass in all changes simultaneously because
|
||||
// ordering may differ.
|
||||
sourceText = sourceText.WithChanges(textChange);
|
||||
}
|
||||
|
||||
return sourceText;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
// 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 System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class RazorLanguageEndpoint : IRazorLanguageQueryHandler
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly DocumentResolver _documentResolver;
|
||||
private readonly DocumentVersionCache _documentVersionCache;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RazorLanguageEndpoint(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
DocumentResolver documentResolver,
|
||||
DocumentVersionCache documentVersionCache,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (documentResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentResolver));
|
||||
}
|
||||
|
||||
if (documentVersionCache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentVersionCache));
|
||||
}
|
||||
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_documentResolver = documentResolver;
|
||||
_documentVersionCache = documentVersionCache;
|
||||
_logger = loggerFactory.CreateLogger<RazorLanguageEndpoint>();
|
||||
}
|
||||
|
||||
public async Task<RazorLanguageQueryResponse> Handle(RazorLanguageQueryParams request, CancellationToken cancellationToken)
|
||||
{
|
||||
long documentVersion = -1;
|
||||
DocumentSnapshot documentSnapshot = null;
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
_documentResolver.TryResolveDocument(request.Uri.AbsolutePath, out documentSnapshot);
|
||||
if (!_documentVersionCache.TryGetDocumentVersion(documentSnapshot, out documentVersion))
|
||||
{
|
||||
Debug.Fail("Document should always be available here.");
|
||||
}
|
||||
|
||||
return documentSnapshot;
|
||||
}, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
var codeDocument = await documentSnapshot.GetGeneratedOutputAsync();
|
||||
var sourceText = await documentSnapshot.GetTextAsync();
|
||||
var linePosition = new LinePosition((int)request.Position.Line, (int)request.Position.Character);
|
||||
var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition);
|
||||
var responsePosition = request.Position;
|
||||
|
||||
if (codeDocument.IsUnsupported())
|
||||
{
|
||||
// All language queries on unsupported documents return Html. This is equivalent to what pre-VSCode Razor was capable of.
|
||||
return new RazorLanguageQueryResponse()
|
||||
{
|
||||
Kind = RazorLanguageKind.Html,
|
||||
Position = responsePosition,
|
||||
PositionIndex = hostDocumentIndex,
|
||||
HostDocumentVersion = documentVersion,
|
||||
};
|
||||
}
|
||||
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
var classifiedSpans = syntaxTree.GetClassifiedSpans();
|
||||
var tagHelperSpans = syntaxTree.GetTagHelperSpans();
|
||||
var languageKind = GetLanguageKind(classifiedSpans, tagHelperSpans, hostDocumentIndex);
|
||||
|
||||
var responsePositionIndex = hostDocumentIndex;
|
||||
|
||||
if (languageKind == RazorLanguageKind.CSharp)
|
||||
{
|
||||
if (TryGetCSharpProjectedPosition(codeDocument, hostDocumentIndex, out var projectedPosition, out var projectedIndex))
|
||||
{
|
||||
// For C# locations, we attempt to return the corresponding position
|
||||
// within the projected document
|
||||
responsePosition = projectedPosition;
|
||||
responsePositionIndex = projectedIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// It no longer makes sense to think of this location as C#, since it doesn't
|
||||
// correspond to any position in the projected document. This should not happen
|
||||
// since there should be source mappings for all the C# spans.
|
||||
languageKind = RazorLanguageKind.Razor;
|
||||
responsePositionIndex = hostDocumentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogTrace($"Language query request for ({request.Position.Line}, {request.Position.Character}) = {languageKind} at ({responsePosition.Line}, {responsePosition.Character})");
|
||||
|
||||
return new RazorLanguageQueryResponse()
|
||||
{
|
||||
Kind = languageKind,
|
||||
Position = responsePosition,
|
||||
PositionIndex = responsePositionIndex,
|
||||
HostDocumentVersion = documentVersion
|
||||
};
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static RazorLanguageKind GetLanguageKind(
|
||||
IReadOnlyList<ClassifiedSpanInternal> classifiedSpans,
|
||||
IReadOnlyList<TagHelperSpanInternal> tagHelperSpans,
|
||||
int absoluteIndex)
|
||||
{
|
||||
for (var i = 0; i < classifiedSpans.Count; i++)
|
||||
{
|
||||
var classifiedSpan = classifiedSpans[i];
|
||||
var span = classifiedSpan.Span;
|
||||
|
||||
if (span.AbsoluteIndex <= absoluteIndex)
|
||||
{
|
||||
var end = span.AbsoluteIndex + span.Length;
|
||||
if (end >= absoluteIndex)
|
||||
{
|
||||
if (end == absoluteIndex)
|
||||
{
|
||||
// We're at an edge.
|
||||
|
||||
if (span.Length > 0 &&
|
||||
classifiedSpan.AcceptedCharacters == AcceptedCharactersInternal.None)
|
||||
{
|
||||
// Non-marker spans do not own the edges after it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Overlaps with request
|
||||
switch (classifiedSpan.SpanKind)
|
||||
{
|
||||
case SpanKindInternal.Markup:
|
||||
return RazorLanguageKind.Html;
|
||||
case SpanKindInternal.Code:
|
||||
return RazorLanguageKind.CSharp;
|
||||
}
|
||||
|
||||
// Content type was non-C# or Html or we couldn't find a classified span overlapping the request position.
|
||||
// All other classified span kinds default back to Razor
|
||||
return RazorLanguageKind.Razor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tagHelperSpans.Count; i++)
|
||||
{
|
||||
var tagHelperSpan = tagHelperSpans[i];
|
||||
var span = tagHelperSpan.Span;
|
||||
|
||||
if (span.AbsoluteIndex <= absoluteIndex)
|
||||
{
|
||||
var end = span.AbsoluteIndex + span.Length;
|
||||
if (end >= absoluteIndex)
|
||||
{
|
||||
if (end == absoluteIndex)
|
||||
{
|
||||
// We're at an edge. TagHelper spans never own their edge and aren't represented by marker spans
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found intersection
|
||||
return RazorLanguageKind.Html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to Razor
|
||||
return RazorLanguageKind.Razor;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool TryGetCSharpProjectedPosition(RazorCodeDocument codeDocument, int absoluteIndex, out Position projectedPosition, out int projectedIndex)
|
||||
{
|
||||
var csharpDoc = codeDocument.GetCSharpDocument();
|
||||
foreach (var mapping in csharpDoc.SourceMappings)
|
||||
{
|
||||
var originalSpan = mapping.OriginalSpan;
|
||||
var originalAbsoluteIndex = originalSpan.AbsoluteIndex;
|
||||
if (originalAbsoluteIndex <= absoluteIndex)
|
||||
{
|
||||
// Treat the mapping as owning the edge at its end (hence <= originalSpan.Length),
|
||||
// otherwise we wouldn't handle the cursor being right after the final C# char
|
||||
var distanceIntoOriginalSpan = absoluteIndex - originalAbsoluteIndex;
|
||||
if (distanceIntoOriginalSpan <= originalSpan.Length)
|
||||
{
|
||||
var generatedSource = SourceText.From(csharpDoc.GeneratedCode);
|
||||
projectedIndex = mapping.GeneratedSpan.AbsoluteIndex + distanceIntoOriginalSpan;
|
||||
var generatedLinePosition = generatedSource.Lines.GetLinePosition(projectedIndex);
|
||||
projectedPosition = new Position(generatedLinePosition.Line, generatedLinePosition.Character);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
projectedPosition = default;
|
||||
projectedIndex = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
public enum RazorLanguageKind
|
||||
{
|
||||
CSharp = 1,
|
||||
Html = 2,
|
||||
Razor = 3
|
||||
}
|
||||
}
|
|
@ -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 System;
|
||||
using OmniSharp.Extensions.Embedded.MediatR;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class RazorLanguageQueryParams : IRequest<RazorLanguageQueryResponse>
|
||||
{
|
||||
public Uri Uri { get; set; }
|
||||
|
||||
public Position Position { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// 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 OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer
|
||||
{
|
||||
internal class RazorLanguageQueryResponse
|
||||
{
|
||||
public RazorLanguageKind Kind { get; set; }
|
||||
|
||||
public int PositionIndex { get; set; }
|
||||
|
||||
public Position Position { get; set; }
|
||||
|
||||
public long HostDocumentVersion { get; set; }
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче