public async Task <CommandOrCodeActionContainer> Handle(CodeActionParams request, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } var documentSnapshot = await Task.Factory.StartNew(() => { _documentResolver.TryResolveDocument(request.TextDocument.Uri.GetAbsoluteOrUNCPath(), out var documentSnapshot); return(documentSnapshot); }, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler).ConfigureAwait(false); if (documentSnapshot is null) { return(null); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return(null); } var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); var linePosition = new LinePosition((int)request.Range.Start.Line, (int)request.Range.Start.Character); var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition); var location = new SourceLocation(hostDocumentIndex, (int)request.Range.Start.Line, (int)request.Range.Start.Character); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location); var tasks = new List <Task <CommandOrCodeActionContainer> >(); foreach (var provider in _providers) { var result = provider.ProvideAsync(context, cancellationToken); if (result != null) { tasks.Add(result); } } var results = await Task.WhenAll(tasks).ConfigureAwait(false); var container = new List <CommandOrCodeAction>(); foreach (var result in results) { if (result != null) { foreach (var commandOrCodeAction in result) { container.Add(commandOrCodeAction); } } } return(new CommandOrCodeActionContainer(container)); }
private static RazorCodeActionContext CreateRazorCodeActionContext(CodeActionParams request, SourceLocation location, string filePath, string text, SourceSpan componentSourceSpan, bool supportsFileCreation = true) { var shortComponent = TagHelperDescriptorBuilder.Create(ComponentMetadata.Component.TagHelperKind, "Fully.Qualified.Component", "TestAssembly"); shortComponent.TagMatchingRule(rule => rule.TagName = "Component"); var fullyQualifiedComponent = TagHelperDescriptorBuilder.Create(ComponentMetadata.Component.TagHelperKind, "Fully.Qualified.Component", "TestAssembly"); fullyQualifiedComponent.TagMatchingRule(rule => rule.TagName = "Fully.Qualified.Component"); var tagHelpers = new[] { shortComponent.Build(), fullyQualifiedComponent.Build() }; var sourceDocument = TestRazorSourceDocument.Create(text, filePath: filePath, relativePath: filePath); var projectEngine = RazorProjectEngine.Create(builder => { builder.AddTagHelpers(tagHelpers); }); var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, FileKinds.Component, Array.Empty <RazorSourceDocument>(), tagHelpers); var cSharpDocument = codeDocument.GetCSharpDocument(); var diagnosticDescriptor = new RazorDiagnosticDescriptor("RZ10012", () => "", RazorDiagnosticSeverity.Error); var diagnostic = RazorDiagnostic.Create(diagnosticDescriptor, componentSourceSpan); var cSharpDocumentWithDiagnostic = RazorCSharpDocument.Create(cSharpDocument.GeneratedCode, cSharpDocument.Options, new[] { diagnostic }); codeDocument.SetCSharpDocument(cSharpDocumentWithDiagnostic); var documentSnapshot = Mock.Of <DocumentSnapshot>(document => document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.GetSourceText()) && document.Project.TagHelpers == tagHelpers); var sourceText = SourceText.From(text); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve: true); return(context); }
protected static bool InFunctionsBlock(RazorCodeActionContext context) { var change = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty); var syntaxTree = context.CodeDocument.GetSyntaxTree(); if (syntaxTree?.Root is null) { return(false); } var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { Debug.Fail("Owner should never be null."); return(false); } var node = owner.Ancestors().FirstOrDefault(n => n.Kind == SyntaxKind.RazorDirective); if (node == null || !(node is RazorDirectiveSyntax directiveNode)) { return(false); } return(directiveNode.DirectiveDescriptor == FunctionsDirective.Directive); }
private static RazorCodeActionContext CreateRazorCodeActionContext( CodeActionParams request, SourceLocation location, string filePath, string text, SourceSpan componentSourceSpan, bool supportsFileCreation = true, bool supportsCodeActionResolve = true) { var tagHelpers = Array.Empty <TagHelperDescriptor>(); var sourceDocument = TestRazorSourceDocument.Create(text, filePath: filePath, relativePath: filePath); var projectEngine = RazorProjectEngine.Create(builder => builder.AddTagHelpers(tagHelpers)); var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, FileKinds.Component, Array.Empty <RazorSourceDocument>(), tagHelpers); var cSharpDocument = codeDocument.GetCSharpDocument(); var diagnosticDescriptor = new RazorDiagnosticDescriptor("RZ10012", () => "", RazorDiagnosticSeverity.Error); var diagnostic = RazorDiagnostic.Create(diagnosticDescriptor, componentSourceSpan); var cSharpDocumentWithDiagnostic = RazorCSharpDocument.Create(cSharpDocument.GeneratedCode, cSharpDocument.Options, new[] { diagnostic }); codeDocument.SetCSharpDocument(cSharpDocumentWithDiagnostic); var documentSnapshot = Mock.Of <DocumentSnapshot>(document => document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.GetSourceText()) && document.Project.TagHelpers == tagHelpers, MockBehavior.Strict); var sourceText = SourceText.From(text); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve); return(context); }
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken) { var codeActions = new List <RazorCodeAction>(); // Locate cursor var change = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty); var node = context.CodeDocument.GetSyntaxTree().Root.LocateOwner(change); if (node is null) { return(EmptyResult); } // Find start tag var startTag = (MarkupStartTagSyntax)node.Ancestors().FirstOrDefault(n => n is MarkupStartTagSyntax); if (startTag is null) { return(EmptyResult); } // Ignore if start tag has dots, as we only handle short tags if (startTag.Name.Content.Contains(".")) { return(EmptyResult); } if (IsTagUnknown(startTag, context)) { AddComponentAccessFromTag(context, startTag, codeActions); AddCreateComponentFromTag(context, startTag, codeActions); } return(Task.FromResult(codeActions as IReadOnlyList <RazorCodeAction>)); }
// internal for testing internal async Task <RazorCodeActionContext> GenerateRazorCodeActionContextAsync(RazorCodeActionParams request, CancellationToken cancellationToken) { var documentSnapshot = await Task.Factory.StartNew(() => { _documentResolver.TryResolveDocument(request.TextDocument.Uri.GetAbsoluteOrUNCPath(), out var documentSnapshot); return(documentSnapshot); }, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler).ConfigureAwait(false); if (documentSnapshot is null) { return(null); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return(null); } var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); // VS Provides `CodeActionParams.Context.SelectionRange` in addition to // `CodeActionParams.Range`. The `SelectionRange` is relative to where the // code action was invoked (ex. line 14, char 3) whereas the `Range` is // always at the start of the line (ex. line 14, char 0). We want to utilize // the relative positioning to ensure we provide code actions for the appropriate // context. // // Note: VS Code doesn't provide a `SelectionRange`. if (request.Context.SelectionRange != null) { request.Range = request.Context.SelectionRange; } // We hide `CodeActionParams.CodeActionContext` in order to capture // `RazorCodeActionParams.ExtendedCodeActionContext`, we must // restore this context to access diagnostics. (request as CodeActionParams).Context = request.Context; var linePosition = new LinePosition( request.Range.Start.Line, request.Range.Start.Character); var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition); var location = new SourceLocation( hostDocumentIndex, request.Range.Start.Line, request.Range.Start.Character); var context = new RazorCodeActionContext( request, documentSnapshot, codeDocument, location, sourceText, _languageServerFeatureOptions.SupportsFileManipulation, _supportsCodeActionResolve); return(context); }
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync( RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions, CancellationToken cancellationToken) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (codeActions is null) { throw new ArgumentNullException(nameof(codeActions)); } // Used to identify if this is VSCode which doesn't support // code action resolve. if (!context.SupportsCodeActionResolve) { return(EmptyResult); } var results = codeActions.Where(codeAction => StringMatchCodeActions.Contains(codeAction.Title) || RegexMatchCodeActions.Any(pattern => pattern.Match(codeAction.Title).Success) ); var wrappedResults = results.Select(c => c.WrapResolvableCSharpCodeAction(context)).ToList(); return(Task.FromResult(wrappedResults as IReadOnlyList <RazorCodeAction>)); }
private static WorkspaceEdit CreateRenameTagEdit(RazorCodeActionContext context, MarkupStartTagSyntax startTag, string newTagName) { var changes = new List <TextEdit> { new TextEdit { Range = startTag.Name.GetRange(context.CodeDocument.Source), NewText = newTagName, }, }; var endTag = (startTag.Parent as MarkupElementSyntax).EndTag; if (endTag != null) { changes.Add(new TextEdit { Range = endTag.Name.GetRange(context.CodeDocument.Source), NewText = newTagName, }); } return(new WorkspaceEdit { Changes = new Dictionary <Uri, IEnumerable <TextEdit> > { [context.Request.TextDocument.Uri] = changes, } }); }
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync( RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions, CancellationToken cancellationToken) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (codeActions is null) { throw new ArgumentNullException(nameof(codeActions)); } if (context.Request?.Context?.Diagnostics is null) { return(EmptyResult); } if (codeActions is null || !codeActions.Any()) { return(EmptyResult); } var results = context.SupportsCodeActionResolve ? ProcessCodeActionsVS(context, codeActions) : ProcessCodeActionsVSCode(context, codeActions); var orderedResults = results.OrderBy(codeAction => codeAction.Title).ToArray(); return(Task.FromResult(orderedResults as IReadOnlyList <RazorCodeAction>)); }
private void AddComponentAccessFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <RazorCodeAction> container) { var matching = FindMatchingTagHelpers(context, startTag); // For all the matches, add options for add @using and fully qualify foreach (var tagHelperPair in matching.Values) { if (tagHelperPair._fullyQualified is null) { continue; } var fullyQualifiedName = tagHelperPair._short.Name; // Insert @using if (AddUsingsCodeActionProviderHelper.TryCreateAddUsingResolutionParams(fullyQualifiedName, context.Request.TextDocument.Uri, out var @namespace, out var resolutionParams)) { var addUsingCodeAction = RazorCodeActionFactory.CreateAddComponentUsing(@namespace, resolutionParams); container.Add(addUsingCodeAction); } // Fully qualify var renameTagWorkspaceEdit = CreateRenameTagEdit(context, startTag, fullyQualifiedName); var fullyQualifiedCodeAction = RazorCodeActionFactory.CreateFullyQualifyComponent(fullyQualifiedName, renameTagWorkspaceEdit); container.Add(fullyQualifiedCodeAction); } }
private static RazorCodeActionContext CreateRazorCodeActionContext(CodeActionParams request, SourceLocation location, string filePath, string text, bool supportsFileCreation = true) { var codeDocument = TestRazorCodeDocument.CreateEmpty(); codeDocument.SetFileKind(FileKinds.Component); var sourceDocument = TestRazorSourceDocument.Create(text, filePath: filePath, relativePath: filePath); var options = RazorParserOptions.Create(o => { o.Directives.Add(ComponentCodeDirective.Directive); o.Directives.Add(FunctionsDirective.Directive); }); var syntaxTree = RazorSyntaxTree.Parse(sourceDocument, options); codeDocument.SetSyntaxTree(syntaxTree); var documentSnapshot = Mock.Of <DocumentSnapshot>(document => document.GetGeneratedOutputAsync() == Task.FromResult(codeDocument) && document.GetTextAsync() == Task.FromResult(codeDocument.GetSourceText()), MockBehavior.Strict); var sourceText = SourceText.From(text); var context = new RazorCodeActionContext(request, documentSnapshot, codeDocument, location, sourceText, supportsFileCreation, supportsCodeActionResolve: true); return(context); }
static SyntaxNode FindImplicitOrExplicitExpressionNode(RazorCodeActionContext context) { var change = new SourceChange(context.Location.AbsoluteIndex, length: 0, newText: string.Empty); var syntaxTree = context.CodeDocument.GetSyntaxTree(); if (syntaxTree?.Root is null) { return(null); } var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { Debug.Fail("Owner should never be null."); return(null); } // E.g, (| is position) // // `@|foo` - true // `@(|foo)` - true // return(owner.AncestorsAndSelf().FirstOrDefault(n => n is CSharpImplicitExpressionSyntax || n is CSharpExplicitExpressionSyntax)); }
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions, CancellationToken cancellationToken) { return(Task.FromResult(new List <RazorCodeAction>() { new RazorCodeAction() } as IReadOnlyList <RazorCodeAction>)); }
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync( RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions, CancellationToken cancellationToken) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (codeActions is null) { throw new ArgumentNullException(nameof(codeActions)); } // Used to identify if this is VSCode which doesn't support // code action resolve. if (!context.SupportsCodeActionResolve) { return(EmptyResult); } var results = new List <RazorCodeAction>(); foreach (var codeAction in codeActions) { if (SupportedDefaultCodeActionNames.Contains(codeAction.Name)) { results.Add(codeAction.WrapResolvableCSharpCodeAction(context)); } } return(Task.FromResult(results as IReadOnlyList <RazorCodeAction>)); }
private static RazorCodeAction CreateFQNCodeAction( RazorCodeActionContext context, Diagnostic fqnDiagnostic, RazorCodeAction codeAction, string fullyQualifiedName) { var codeDocumentIdentifier = new VersionedTextDocumentIdentifier() { Uri = context.Request.TextDocument.Uri }; var fqnTextEdit = new TextEdit() { NewText = fullyQualifiedName, Range = fqnDiagnostic.Range }; var fqnWorkspaceEditDocumentChange = new WorkspaceEditDocumentChange(new TextDocumentEdit() { TextDocument = codeDocumentIdentifier, Edits = new[] { fqnTextEdit }, }); var fqnWorkspaceEdit = new WorkspaceEdit() { DocumentChanges = new[] { fqnWorkspaceEditDocumentChange } }; return(new RazorCodeAction() { Title = codeAction.Title, Edit = fqnWorkspaceEdit }); }
private void AddCreateComponentFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <CommandOrCodeAction> container) { var path = context.Request.TextDocument.Uri.GetAbsoluteOrUNCPath(); path = _filePathNormalizer.Normalize(path); var newComponentPath = Path.Combine(Path.GetDirectoryName(path), $"{startTag.Name.Content}.razor"); if (File.Exists(newComponentPath)) { return; } var actionParams = new CreateComponentCodeActionParams { Uri = context.Request.TextDocument.Uri, Path = newComponentPath, }; var data = JObject.FromObject(actionParams); var resolutionParams = new RazorCodeActionResolutionParams { Action = LanguageServerConstants.CodeActions.CreateComponentFromTag, Data = data, }; var serializedParams = JToken.FromObject(resolutionParams); var arguments = new JArray(serializedParams); container.Add(new CommandOrCodeAction(new Command { Title = "Create component from tag", Name = LanguageServerConstants.RazorCodeActionRunnerCommand, Arguments = arguments, })); }
private void AddComponentAccessFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <RazorCodeAction> container) { var matching = FindMatchingTagHelpers(context, startTag); // For all the matches, add options for add @using and fully qualify foreach (var tagHelperPair in matching.Values) { if (tagHelperPair.FullyQualified is null) { continue; } var fullyQualifiedName = tagHelperPair.Short.Name; // Insert @using var addUsingCodeAction = AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction( fullyQualifiedName, context.Request.TextDocument.Uri); if (addUsingCodeAction != null) { container.Add(addUsingCodeAction); } // Fully qualify container.Add(new RazorCodeAction() { Title = $"{fullyQualifiedName}", Edit = CreateRenameTagEdit(context, startTag, fullyQualifiedName), }); } }
private void AddCreateComponentFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <RazorCodeAction> container) { var path = context.Request.TextDocument.Uri.GetAbsoluteOrUNCPath(); path = _filePathNormalizer.Normalize(path); var newComponentPath = Path.Combine(Path.GetDirectoryName(path), $"{startTag.Name.Content}.razor"); if (File.Exists(newComponentPath)) { return; } var actionParams = new CreateComponentCodeActionParams { Uri = context.Request.TextDocument.Uri, Path = newComponentPath, }; var resolutionParams = new RazorCodeActionResolutionParams { Action = LanguageServerConstants.CodeActions.CreateComponentFromTag, Data = actionParams, }; container.Add(new RazorCodeAction() { Title = CreateComponentFromTagTitle, Data = resolutionParams }); }
internal async Task <RazorCodeActionContext> GenerateRazorCodeActionContextAsync(CodeActionParams request, CancellationToken cancellationToken) { var documentSnapshot = await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() => { _documentResolver.TryResolveDocument(request.TextDocument.Uri.GetAbsoluteOrUNCPath(), out var documentSnapshot); return(documentSnapshot); }, cancellationToken).ConfigureAwait(false); if (documentSnapshot is null) { return(null); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return(null); } var sourceText = await documentSnapshot.GetTextAsync().ConfigureAwait(false); // VS Provides `CodeActionParams.Context.SelectionRange` in addition to // `CodeActionParams.Range`. The `SelectionRange` is relative to where the // code action was invoked (ex. line 14, char 3) whereas the `Range` is // always at the start of the line (ex. line 14, char 0). We want to utilize // the relative positioning to ensure we provide code actions for the appropriate // context. // // Note: VS Code doesn't provide a `SelectionRange`. var vsCodeActionContext = (OmniSharpVSCodeActionContext)request.Context; if (vsCodeActionContext.SelectionRange != null) { request = request with { Range = vsCodeActionContext.SelectionRange }; } var linePosition = new LinePosition( request.Range.Start.Line, request.Range.Start.Character); var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition); var location = new SourceLocation( hostDocumentIndex, request.Range.Start.Line, request.Range.Start.Character); var context = new RazorCodeActionContext( request, documentSnapshot, codeDocument, location, sourceText, _languageServerFeatureOptions.SupportsFileManipulation, _supportsCodeActionResolve); return(context); }
private static RazorCodeAction CreateAddUsingCodeAction( RazorCodeActionContext context, string fullyQualifiedName) { return(AddUsingsCodeActionProviderFactory.CreateAddUsingCodeAction( fullyQualifiedName, context.Request.TextDocument.Uri)); }
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync( RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions, CancellationToken cancellationToken) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (codeActions is null) { throw new ArgumentNullException(nameof(codeActions)); } // Used to identify if this is VSCode which doesn't support // code action resolve. if (!context.SupportsCodeActionResolve) { return(EmptyResult); } if (context.Request?.Context?.Diagnostics is null) { return(EmptyResult); } var diagnostics = context.Request.Context.Diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error && diagnostic.Code?.IsString == true) .Select(diagnostic => diagnostic.Code.Value.String) .ToImmutableHashSet(); if (diagnostics is null) { return(null); } var results = new List <RazorCodeAction>(); if (diagnostics.Contains(ImplementAbstractClassDiagnostic)) { var implementAbstractClassCodeAction = codeActions.Where(c => c.Title == ImplementAbstractClassCodeActionTitle); results.AddRange(implementAbstractClassCodeAction); } if (diagnostics.Contains(ImplementInterfaceDiagnostic)) { var implementInterfaceCodeActions = codeActions.Where(c => ImplementInterfaceCodeActionTitle.Contains(c.Title)); results.AddRange(implementInterfaceCodeActions); } var wrappedResults = results.Select(c => c.WrapResolvableCSharpCodeAction(context)).ToList(); return(Task.FromResult(wrappedResults as IReadOnlyList <RazorCodeAction>)); }
private static IEnumerable <RazorCodeAction> ProcessCodeActionsVS( RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions) { var typeAccessibilityCodeActions = new List <RazorCodeAction>(1); foreach (var codeAction in codeActions) { if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.FullyQualify, StringComparison.Ordinal)) { string action; if (!TryGetOwner(context, out var owner)) { // Failed to locate a valid owner for the light bulb continue; } else if (IsSingleLineDirectiveNode(owner)) { // Don't support single line directives continue; } else if (IsExplicitExpressionNode(owner)) { // Don't support explicit expressions continue; } else if (IsImplicitExpressionNode(owner)) { action = LanguageServerConstants.CodeActions.UnformattedRemap; } else { // All other scenarios we support default formatted code action behavior action = LanguageServerConstants.CodeActions.Default; } typeAccessibilityCodeActions.Add(codeAction.WrapResolvableCSharpCodeAction(context, action)); } // For add using suggestions, the code action title is of the form: // `using System.Net;` else if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.AddImport, StringComparison.Ordinal) && AddUsingsCodeActionProviderHelper.TryExtractNamespace(codeAction.Title, out var @namespace)) { var newCodeAction = codeAction with { Title = $"@using {@namespace}" }; typeAccessibilityCodeActions.Add(newCodeAction.WrapResolvableCSharpCodeAction(context, LanguageServerConstants.CodeActions.AddUsing)); } // Not a type accessibility code action else { continue; } } return(typeAccessibilityCodeActions);
public override Task <IReadOnlyList <RazorCodeAction> > ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken) { // O# Code Actions don't have `Data`, but `Commands` do return(Task.FromResult(new List <RazorCodeAction>() { new RazorCodeAction() { Title = "SomeTitle", Data = JToken.FromObject(new AddUsingsCodeActionParams()) } } as IReadOnlyList <RazorCodeAction>)); }
private static IEnumerable <RazorCodeAction> ProcessCodeActionsVS( RazorCodeActionContext context, IEnumerable <RazorCodeAction> codeActions) { var typeAccessibilityCodeActions = new List <RazorCodeAction>(1); foreach (var codeAction in codeActions) { if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.FullyQualify, StringComparison.Ordinal)) { var node = FindImplicitOrExplicitExpressionNode(context); string action; // The formatting pass of our Default code action resolver rejects // implicit/explicit expressions. So if we're in an implicit expression, // we run the remapping resolver responsible for simply remapping // (without formatting) the resolved code action. We do not support // explicit expressions due to issues with the remapping methodology // risking document corruption. if (node is null) { action = LanguageServerConstants.CodeActions.Default; } else if (node is CSharpImplicitExpressionSyntax) { action = LanguageServerConstants.CodeActions.UnformattedRemap; } else { continue; } typeAccessibilityCodeActions.Add(codeAction.WrapResolvableCSharpCodeAction(context, action)); } // For add using suggestions, the code action title is of the form: // `using System.Net;` else if (codeAction.Name.Equals(RazorPredefinedCodeFixProviderNames.AddImport, StringComparison.Ordinal) && AddUsingsCodeActionProviderFactory.TryExtractNamespace(codeAction.Title, out var @namespace)) { var newCodeAction = codeAction with { Title = $"@using {@namespace}" }; typeAccessibilityCodeActions.Add(newCodeAction.WrapResolvableCSharpCodeAction(context, LanguageServerConstants.CodeActions.AddUsing)); } // Not a type accessibility code action else { continue; } } return(typeAccessibilityCodeActions);
private async Task <IEnumerable <CodeAction> > GetCSharpCodeActionsAsync(RazorCodeActionContext context, CancellationToken cancellationToken) { var csharpCodeActions = await GetCSharpCodeActionsFromLanguageServerAsync(context, cancellationToken); if (csharpCodeActions is null || !csharpCodeActions.Any()) { return(null); } var filteredCSharpCodeActions = await FilterCSharpCodeActionsAsync(context, csharpCodeActions, cancellationToken); return(filteredCSharpCodeActions); }
private static bool TryProcessCodeAction( RazorCodeActionContext context, CodeAction codeAction, Diagnostic diagnostic, string associatedValue, out ICollection <CodeAction> typeAccessibilityCodeActions) { // VS & VSCode provide type accessibility code actions in different formats // We must handle them seperately. return(context.SupportsCodeActionResolve ? TryProcessCodeActionVS(context, codeAction, diagnostic, associatedValue, out typeAccessibilityCodeActions) : TryProcessCodeActionVSCode(context, codeAction, diagnostic, associatedValue, out typeAccessibilityCodeActions)); }
private static bool TryProcessCodeAction( RazorCodeActionContext context, RazorCodeAction codeAction, Diagnostic diagnostic, string associatedValue, out ICollection <RazorCodeAction> typeAccessibilityCodeActions) { var fqn = string.Empty; // When there's only one FQN suggestion, code action title is of the form: // `System.Net.Dns` if (!codeAction.Title.Any(c => char.IsWhiteSpace(c)) && codeAction.Title.EndsWith(associatedValue, StringComparison.OrdinalIgnoreCase)) { fqn = codeAction.Title; } else { // When there are multiple FQN suggestions, the code action title is of the form: // `Fully qualify 'Dns' -> System.Net.Dns` var expectedCodeActionPrefix = $"Fully qualify '{associatedValue}' -> "; if (codeAction.Title.StartsWith(expectedCodeActionPrefix, StringComparison.OrdinalIgnoreCase)) { fqn = codeAction.Title.Substring(expectedCodeActionPrefix.Length); } } if (string.IsNullOrEmpty(fqn)) { typeAccessibilityCodeActions = default; return(false); } typeAccessibilityCodeActions = new List <RazorCodeAction>(); var fqnCodeAction = CreateFQNCodeAction(context, diagnostic, codeAction, fqn); typeAccessibilityCodeActions.Add(fqnCodeAction); var addUsingCodeAction = CreateAddUsingCodeAction(context, fqn); if (addUsingCodeAction != null) { typeAccessibilityCodeActions.Add(addUsingCodeAction); } return(true); }
private bool IsTagUnknown(MarkupStartTagSyntax startTag, RazorCodeActionContext context) { foreach (var diagnostic in context.CodeDocument.GetCSharpDocument().Diagnostics) { // Check that the diagnostic is to do with our start tag if (!(diagnostic.Span.AbsoluteIndex > startTag.Span.End || startTag.Span.Start > diagnostic.Span.AbsoluteIndex + diagnostic.Span.Length)) { // Component is not recognized in environment if (diagnostic.Id == ComponentDiagnosticFactory.UnexpectedMarkupElement.Id) { return(true); } } } return(false); }
private void AddComponentAccessFromTag(RazorCodeActionContext context, MarkupStartTagSyntax startTag, List <CommandOrCodeAction> container) { var matching = FindMatchingTagHelpers(context, startTag); // For all the matches, add options for add @using and fully qualify foreach (var tagHelperPair in matching.Values) { if (tagHelperPair.FullyQualified is null) { continue; } var fullyQualifiedComponentName = tagHelperPair.Short.Name; // We assume .Name is the fully qualified component name DefaultRazorTagHelperBinderPhase.ComponentDirectiveVisitor.TrySplitNamespaceAndType(fullyQualifiedComponentName, out var namespaceSpan, out var _); var namespaceName = tagHelperPair.Short.Name.Substring(namespaceSpan.Start, namespaceSpan.Length); var actionParams = new AddUsingsCodeActionParams { Uri = context.Request.TextDocument.Uri, Namespace = namespaceName, }; var data = JObject.FromObject(actionParams); var resolutionParams = new RazorCodeActionResolutionParams { Action = LanguageServerConstants.CodeActions.AddUsing, Data = data, }; var serializedParams = JToken.FromObject(resolutionParams); var arguments = new JArray(serializedParams); // Insert @using container.Add(new CommandOrCodeAction(new Command { Title = $"@using {namespaceName}", Name = LanguageServerConstants.RazorCodeActionRunnerCommand, Arguments = arguments, })); // Fully qualify container.Add(new CommandOrCodeAction(new CodeAction { Title = $"{tagHelperPair.Short.Name}", Edit = CreateRenameTagEdit(context, startTag, tagHelperPair.Short.Name), })); } }
private async Task <IEnumerable <RazorCodeAction> > GetRazorCodeActionsAsync(RazorCodeActionContext context, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var tasks = new List <Task <IReadOnlyList <RazorCodeAction> > >(); foreach (var provider in _razorCodeActionProviders) { var result = provider.ProvideAsync(context, cancellationToken); if (result != null) { tasks.Add(result); } } return(await ConsolidateCodeActionsFromProvidersAsync(tasks, cancellationToken)); }