File: Interactive\CSharpSendToInteractiveSubmissionProvider.cs
Web Access
Project: src\src\EditorFeatures\CSharp\Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Interactive;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.Interactive;
 
[Export(typeof(ISendToInteractiveSubmissionProvider))]
internal sealed class CSharpSendToInteractiveSubmissionProvider
    : AbstractSendToInteractiveSubmissionProvider
{
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public CSharpSendToInteractiveSubmissionProvider()
    {
    }
 
    protected override bool CanParseSubmission(string code)
    {
        var options = CSharpInteractiveEvaluatorLanguageInfoProvider.Instance.ParseOptions;
        var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(code, encoding: null, SourceHashAlgorithms.Default), options);
        return tree.HasCompilationUnitRoot &&
            !tree.GetDiagnostics().Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
    }
 
    protected override IEnumerable<TextSpan> GetExecutableSyntaxTreeNodeSelection(TextSpan selectionSpan, SyntaxNode root)
    {
        var expandedNode = GetSyntaxNodeForSubmission(selectionSpan, root);
        return expandedNode != null
            ? [expandedNode.Span]
            : Array.Empty<TextSpan>();
    }
 
    /// <summary>
    /// Finds a <see cref="SyntaxNode"/> that should be submitted to REPL.
    /// </summary>
    /// <param name="selectionSpan">Selection that user has originally made.</param>
    /// <param name="root">Root of the syntax tree.</param>
    private static SyntaxNode? GetSyntaxNodeForSubmission(TextSpan selectionSpan, SyntaxNode root)
    {
        GetSelectedTokens(selectionSpan, root, out var startToken, out var endToken);
 
        // Ensure that the first token comes before the last token.
        // Otherwise selection did not contain any tokens.
        if (startToken != endToken && startToken.Span.End > endToken.SpanStart)
            return null;
 
        if (startToken == endToken)
        {
            return GetSyntaxNodeForSubmission(startToken.GetRequiredParent());
        }
 
        var startNode = GetSyntaxNodeForSubmission(startToken.GetRequiredParent());
        var endNode = GetSyntaxNodeForSubmission(endToken.GetRequiredParent());
 
        // If there is no SyntaxNode worth sending to the REPL return null.
        if (startNode == null || endNode == null)
        {
            return null;
        }
 
        // If one of the nodes is an ancestor of another node return that node.
        if (startNode.Span.Contains(endNode.Span))
        {
            return startNode;
        }
        else if (endNode.Span.Contains(startNode.Span))
        {
            return endNode;
        }
 
        // Selection spans multiple statements.
        // In this case find common parent and find a span of statements within that parent.
        return GetSyntaxNodeForSubmission(startNode.GetCommonRoot(endNode));
    }
 
    /// <summary>
    /// Finds a <see cref="SyntaxNode"/> that should be submitted to REPL.
    /// </summary>
    /// <param name="node">The currently selected node.</param>
    private static SyntaxNode? GetSyntaxNodeForSubmission(SyntaxNode node)
    {
        SyntaxNode? candidate = node.GetAncestorOrThis<StatementSyntax>();
        if (candidate != null)
        {
            return candidate;
        }
 
        candidate = node.GetAncestorsOrThis<SyntaxNode>()
            .Where(IsSubmissionNode).FirstOrDefault();
        if (candidate != null)
        {
            return candidate;
        }
 
        return null;
    }
 
    /// <summary>Returns <c>true</c> if <c>node</c> could be treated as a REPL submission.</summary>
    private static bool IsSubmissionNode(SyntaxNode node)
    {
        var kind = node.Kind();
        return SyntaxFacts.IsTypeDeclaration(kind)
            || SyntaxFacts.IsGlobalMemberDeclaration(kind)
            || node.IsKind(SyntaxKind.UsingDirective);
    }
 
    private static void GetSelectedTokens(
        TextSpan selectionSpan,
        SyntaxNode root,
        out SyntaxToken startToken,
        out SyntaxToken endToken)
    {
        endToken = root.FindTokenOnLeftOfPosition(selectionSpan.End);
        startToken = selectionSpan.Length == 0
            ? endToken
            : root.FindTokenOnRightOfPosition(selectionSpan.Start);
    }
}