|
// 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.
#nullable disable
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
using Microsoft.VisualStudio.Text.Editor.Commanding;
namespace Microsoft.CodeAnalysis.Interactive;
/// <summary>
/// Implementers of this interface are responsible for retrieving source code that
/// should be sent to the REPL given the user's selection.
///
/// If the user does not make a selection then a line should be selected.
/// If the user selects code that fails to be parsed then the selection gets expanded
/// to a syntax node.
/// </summary>
internal abstract class AbstractSendToInteractiveSubmissionProvider : ISendToInteractiveSubmissionProvider
{
/// <summary>Expands the selection span of an invalid selection to a span that should be sent to REPL.</summary>
protected abstract IEnumerable<TextSpan> GetExecutableSyntaxTreeNodeSelection(TextSpan selectedSpan, SyntaxNode node);
/// <summary>Returns whether the submission can be parsed in interactive.</summary>
protected abstract bool CanParseSubmission(string code);
string ISendToInteractiveSubmissionProvider.GetSelectedText(IEditorOptions editorOptions, EditorCommandArgs args, CancellationToken cancellationToken)
{
var selectedSpans = args.TextView.Selection.IsEmpty
? GetExpandedLine(editorOptions, args, cancellationToken)
: args.TextView.Selection.GetSnapshotSpansOnBuffer(args.SubjectBuffer).Where(ss => ss.Length > 0);
return GetSubmissionFromSelectedSpans(editorOptions, selectedSpans);
}
/// <summary>Returns the span for the selected line. Extends it if it is a part of a multi line statement or declaration.</summary>
private IEnumerable<SnapshotSpan> GetExpandedLine(IEditorOptions editorOptions, EditorCommandArgs args, CancellationToken cancellationToken)
{
var selectedSpans = GetSelectedLine(args.TextView);
var candidateSubmission = GetSubmissionFromSelectedSpans(editorOptions, selectedSpans);
return CanParseSubmission(candidateSubmission)
? selectedSpans
: ExpandSelection(selectedSpans, args, cancellationToken);
}
/// <summary>Returns the span for the currently selected line.</summary>
private static IEnumerable<SnapshotSpan> GetSelectedLine(ITextView textView)
{
var snapshotLine = textView.Caret.Position.VirtualBufferPosition.Position.GetContainingLine();
var span = new SnapshotSpan(snapshotLine.Start, snapshotLine.LengthIncludingLineBreak);
return new NormalizedSnapshotSpanCollection(span);
}
private IEnumerable<SnapshotSpan> ExpandSelection(IEnumerable<SnapshotSpan> selectedSpans, EditorCommandArgs args, CancellationToken cancellationToken)
{
var selectedSpansStart = selectedSpans.Min(span => span.Start);
var selectedSpansEnd = selectedSpans.Max(span => span.End);
var snapshot = args.TextView.TextSnapshot;
var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
var root = document.GetSyntaxRootSynchronously(cancellationToken);
var newSpans = GetExecutableSyntaxTreeNodeSelection(TextSpan.FromBounds(selectedSpansStart, selectedSpansEnd), root).
Select(span => new SnapshotSpan(snapshot, span.Start, span.Length));
return newSpans.Any() ? newSpans : selectedSpans;
}
private static string GetSubmissionFromSelectedSpans(IEditorOptions editorOptions, IEnumerable<SnapshotSpan> selectedSpans)
=> string.Join(editorOptions.GetNewLineCharacter(), selectedSpans.Select(ss => ss.GetText()));
}
|