private (RazorLanguageKind, TextEdit[]) GetFormattedEdits(RazorCodeDocument codeDocument, string expected, int positionBeforeTriggerChar) { var mappingService = new DefaultRazorDocumentMappingService(); var languageKind = mappingService.GetLanguageKind(codeDocument, positionBeforeTriggerChar); var expectedText = SourceText.From(expected); var(expectedCodeDocument, _) = CreateCodeDocumentAndSnapshot(expectedText, codeDocument.Source.FilePath, fileKind: codeDocument.GetFileKind()); var edits = Array.Empty <TextEdit>(); if (languageKind == RazorLanguageKind.CSharp) { var beforeCSharpText = SourceText.From(codeDocument.GetCSharpDocument().GeneratedCode); var afterCSharpText = SourceText.From(expectedCodeDocument.GetCSharpDocument().GeneratedCode); edits = SourceTextDiffer.GetMinimalTextChanges(beforeCSharpText, afterCSharpText, lineDiffOnly: false).Select(c => c.AsTextEdit(beforeCSharpText)).ToArray(); } else if (languageKind == RazorLanguageKind.Html) { var beforeHtmlText = SourceText.From(codeDocument.GetHtmlDocument().GeneratedHtml); var afterHtmlText = SourceText.From(expectedCodeDocument.GetHtmlDocument().GeneratedHtml); edits = SourceTextDiffer.GetMinimalTextChanges(beforeHtmlText, afterHtmlText, lineDiffOnly: false).Select(c => c.AsTextEdit(beforeHtmlText)).ToArray(); } return(languageKind, edits); }
public static SourceText GetHtmlSourceText(this RazorCodeDocument document) { if (document == null) { throw new ArgumentNullException(nameof(document)); } var sourceTextObj = document.Items[HtmlSourceTextKey]; if (sourceTextObj == null) { var htmlDocument = document.GetHtmlDocument(); var sourceText = SourceText.From(htmlDocument.GeneratedHtml); document.Items[HtmlSourceTextKey] = sourceText; return(sourceText); } return((SourceText)sourceTextObj); }
public bool TrySetOutput( DefaultDocumentSnapshot document, RazorCodeDocument codeDocument, VersionStamp inputVersion, VersionStamp outputCSharpVersion, VersionStamp outputHtmlVersion) { lock (_setOutputLock) { if (_inputVersion.HasValue && _inputVersion.Value != inputVersion && _inputVersion == _inputVersion.Value.GetNewerVersion(inputVersion)) { // Latest document is newer than the provided document. return(false); } if (!document.TryGetText(out var source)) { Debug.Fail("The text should have already been evaluated."); return(false); } _source = source; _inputVersion = inputVersion; _outputCSharpVersion = outputCSharpVersion; _outputHtmlVersion = outputHtmlVersion; _outputCSharp = codeDocument.GetCSharpDocument(); _outputHtml = codeDocument.GetHtmlDocument(); _latestDocument = document; var csharpSourceText = codeDocument.GetCSharpSourceText(); _csharpTextContainer.SetText(csharpSourceText); var htmlSourceText = codeDocument.GetHtmlSourceText(); _htmlTextContainer.SetText(htmlSourceText); return(true); } }
private async Task <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionCoreAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { // We only need to produce the generated code if any of our inputs is newer than the // previously cached output. // // First find the versions that are the inputs: // - The project + computed state // - The imports // - This document // // All of these things are cached, so no work is wasted if we do need to generate the code. var configurationVersion = project.State.ConfigurationVersion; var projectWorkspaceStateVersion = project.State.ProjectWorkspaceStateVersion; var documentCollectionVersion = project.State.DocumentCollectionVersion; var imports = await GetImportsAsync(project, document).ConfigureAwait(false); var documentVersion = await document.GetTextVersionAsync().ConfigureAwait(false); // OK now that have the previous output and all of the versions, we can see if anything // has changed that would require regenerating the code. var inputVersion = documentVersion; if (inputVersion.GetNewerVersion(configurationVersion) == configurationVersion) { inputVersion = configurationVersion; } if (inputVersion.GetNewerVersion(projectWorkspaceStateVersion) == projectWorkspaceStateVersion) { inputVersion = projectWorkspaceStateVersion; } if (inputVersion.GetNewerVersion(documentCollectionVersion) == documentCollectionVersion) { inputVersion = documentCollectionVersion; } for (var i = 0; i < imports.Count; i++) { var importVersion = imports[i].Version; if (inputVersion.GetNewerVersion(importVersion) == importVersion) { inputVersion = importVersion; } } RazorCodeDocument olderOutput = null; var olderInputVersion = default(VersionStamp); var olderCSharpOutputVersion = default(VersionStamp); var olderHtmlOutputVersion = default(VersionStamp); if (_older?.TaskUnsafeReference != null && _older.TaskUnsafeReference.TryGetTarget(out var taskUnsafe)) { (olderOutput, olderInputVersion, olderCSharpOutputVersion, olderHtmlOutputVersion) = await taskUnsafe.ConfigureAwait(false); if (inputVersion.GetNewerVersion(olderInputVersion) == olderInputVersion) { // Nothing has changed, we can use the cached result. lock (_lock) { TaskUnsafeReference = _older.TaskUnsafeReference; _older = null; return(olderOutput, olderInputVersion, olderCSharpOutputVersion, olderHtmlOutputVersion); } } } // OK we have to generate the code. var importSources = new List <RazorSourceDocument>(); var projectEngine = project.GetProjectEngine(); foreach (var item in imports) { var importProjectItem = item.FilePath == null ? null : projectEngine.FileSystem.GetItem(item.FilePath, item.FileKind); var sourceDocument = await GetRazorSourceDocumentAsync(item.Document, importProjectItem).ConfigureAwait(false); importSources.Add(sourceDocument); } var projectItem = document.FilePath == null ? null : projectEngine.FileSystem.GetItem(document.FilePath, document.FileKind); var documentSource = await GetRazorSourceDocumentAsync(document, projectItem).ConfigureAwait(false); var codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); var csharpDocument = codeDocument.GetCSharpDocument(); var htmlDocument = codeDocument.GetHtmlDocument(); // OK now we've generated the code. Let's check if the output is actually different. This is // a valuable optimization for our use cases because lots of changes you could make require // us to run code generation, but don't change the result. // // Note that we're talking about the effect on the generated C#/HTML here (not the other artifacts). // This is the reason why we have three versions associated with the document. // // The INPUT version is related the .cshtml files and tag helpers // The CSHARPOUTPUT version is related to the generated C# // The HTMLOUTPUT version is related to the generated HTML // // Examples: // // A change to a tag helper not used by this document - updates the INPUT version, but not // the OUTPUT version. // // // Razor IDE features should always retrieve the output and party on it regardless. Depending // on the use cases we may or may not need to synchronize the output. var outputCSharpVersion = inputVersion; var outputHtmlVersion = inputVersion; if (olderOutput != null) { if (string.Equals( olderOutput.GetCSharpDocument().GeneratedCode, csharpDocument.GeneratedCode, StringComparison.Ordinal)) { outputCSharpVersion = olderCSharpOutputVersion; } if (string.Equals( olderOutput.GetHtmlDocument().GeneratedHtml, htmlDocument.GeneratedHtml, StringComparison.Ordinal)) { outputHtmlVersion = olderHtmlOutputVersion; } } if (document is DefaultDocumentSnapshot defaultDocument) { defaultDocument.State.HostDocument.GeneratedDocumentContainer.SetOutput( defaultDocument, csharpDocument, htmlDocument, inputVersion, outputCSharpVersion, outputHtmlVersion); } return(codeDocument, inputVersion, outputCSharpVersion, outputHtmlVersion); }