/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Core/AutomaticCompletion/AbstractAutomaticLineEnderCommandHandler.cs
216 строк
10 KB
Tomáš Matoušek
Syntax formatting options (#74223)
08 июл 2024, 23:42
Не верифицирован
08 июл 2024, 23:42
f4fada1
Код
Авторство
О чём код?
// 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.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Operations; namespace Microsoft.CodeAnalysis.AutomaticCompletion; internal abstract class AbstractAutomaticLineEnderCommandHandler : IChainedCommandHandler<AutomaticLineEnderCommandArgs> { private readonly ITextUndoHistoryRegistry _undoRegistry; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; public readonly EditorOptionsService EditorOptionsService; public string DisplayName => EditorFeaturesResources.Automatic_Line_Ender; protected AbstractAutomaticLineEnderCommandHandler( ITextUndoHistoryRegistry undoRegistry, IEditorOperationsFactoryService editorOperationsFactoryService, EditorOptionsService editorOptionsService) { _undoRegistry = undoRegistry; _editorOperationsFactoryService = editorOperationsFactoryService; EditorOptionsService = editorOptionsService; } /// <summary> /// get ending string if there is one /// </summary> protected abstract string? GetEndingString(ParsedDocument document, int position); /// <summary> /// do next action /// </summary> protected abstract void NextAction(IEditorOperations editorOperation, Action nextAction); /// <summary> /// format after inserting ending string /// </summary> protected abstract IList<TextChange> FormatBasedOnEndToken(ParsedDocument document, int position, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken); /// <summary> /// special cases where we do not want to do line completion but just fall back to line break and formatting. /// </summary> protected abstract bool TreatAsReturn(ParsedDocument document, int caretPosition, CancellationToken cancellationToken); /// <summary> /// Add or remove the braces for <param name="selectedNode"/>. /// </summary> protected abstract void ModifySelectedNode(AutomaticLineEnderCommandArgs args, ParsedDocument document, SyntaxNode selectedNode, bool addBrace, int caretPosition, StructuredAnalyzerConfigOptions fallbackOptions, CancellationToken cancellationToken); /// <summary> /// Get the syntax node needs add/remove braces. /// </summary> protected abstract (SyntaxNode selectedNode, bool addBrace)? GetValidNodeToModifyBraces(ParsedDocument document, int caretPosition, CancellationToken cancellationToken); public CommandState GetCommandState(AutomaticLineEnderCommandArgs args, Func<CommandState> nextHandler) => CommandState.Available; public void ExecuteCommand(AutomaticLineEnderCommandArgs args, Action nextHandler, CommandExecutionContext context) { // get editor operation var operations = _editorOperationsFactoryService.GetEditorOperations(args.TextView); if (operations == null) { nextHandler(); return; } var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { NextAction(operations, nextHandler); return; } // feature off if (!EditorOptionsService.GlobalOptions.GetOption(AutomaticLineEnderOptionsStorage.AutomaticLineEnder)) { NextAction(operations, nextHandler); return; } using (context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Automatically_completing)) { var cancellationToken = context.OperationContext.UserCancellationToken; var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken); // caret is not on the subject buffer. nothing we can do var caret = args.TextView.GetCaretPoint(args.SubjectBuffer); if (!caret.HasValue) { NextAction(operations, nextHandler); return; } var caretPosition = caret.Value; // special cases where we treat this command simply as Return. if (TreatAsReturn(parsedDocument, caretPosition, cancellationToken)) { // leave it to the VS editor to handle this command. // VS editor's default implementation of SmartBreakLine is simply BreakLine, which inserts // a new line and positions the caret with smart indent. nextHandler(); return; } var subjectLineWhereCaretIsOn = caretPosition.GetContainingLine(); // Two possible operations // 1. Add/remove the brace for the selected syntax node (only for C#) // 2. Append an ending string to the line. (For C#, it is semicolon ';', For VB, it is underline '_') // Check if the node could be used to add/remove brace. var selectNodeAndOperationKind = GetValidNodeToModifyBraces(parsedDocument, caretPosition, cancellationToken); if (selectNodeAndOperationKind != null) { var (selectedNode, addBrace) = selectNodeAndOperationKind.Value; using var transaction = args.TextView.CreateEditTransaction(EditorFeaturesResources.Automatic_Line_Ender, _undoRegistry, _editorOperationsFactoryService); ModifySelectedNode(args, parsedDocument, selectedNode, addBrace, caretPosition, document.Project.GetFallbackAnalyzerOptions(), cancellationToken); NextAction(operations, nextHandler); transaction.Complete(); return; } // Check if we could find the ending position var endingInsertionPosition = GetInsertionPositionForEndingString(parsedDocument, subjectLineWhereCaretIsOn); if (endingInsertionPosition != null) { using var transaction = args.TextView.CreateEditTransaction(EditorFeaturesResources.Automatic_Line_Ender, _undoRegistry, _editorOperationsFactoryService); var formattingOptions = args.SubjectBuffer.GetSyntaxFormattingOptions(EditorOptionsService, document.Project.GetFallbackAnalyzerOptions(), parsedDocument.LanguageServices, explicitFormat: false); InsertEnding(args.TextView, args.SubjectBuffer, parsedDocument, endingInsertionPosition.Value, caretPosition, formattingOptions, cancellationToken); NextAction(operations, nextHandler); transaction.Complete(); return; } // Neither of the two operations could be performed using var editTransaction = args.TextView.CreateEditTransaction(EditorFeaturesResources.Automatic_Line_Ender, _undoRegistry, _editorOperationsFactoryService); NextAction(operations, nextHandler); editTransaction.Complete(); } } /// <summary> /// return insertion point for the ending string /// </summary> private static int? GetInsertionPositionForEndingString(ParsedDocument document, ITextSnapshotLine line) { // find last token on the line var token = document.Root.FindTokenOnLeftOfPosition(line.End); if (token.RawKind == 0) return null; // bug # 16770 // don't do anything if token is multiline token such as verbatim string if (line.End < token.Span.End) return null; // if there is only whitespace, token doesn't need to be on same line var text = document.Text; if (string.IsNullOrWhiteSpace(text.ToString(TextSpan.FromBounds(token.Span.End, line.End)))) return line.End; // if token is on different line than caret but caret line is empty, we insert ending point at the end of the line if (text.Lines.IndexOf(token.Span.End) != text.Lines.IndexOf(line.End)) return string.IsNullOrWhiteSpace(line.GetText()) ? line.End : null; return token.Span.End; } /// <summary> /// insert ending string if there is one to insert /// </summary> private void InsertEnding( ITextView textView, ITextBuffer buffer, ParsedDocument document, int insertPosition, SnapshotPoint caretPosition, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) { // 1. Move the caret to line end. textView.TryMoveCaretToAndEnsureVisible(caretPosition.GetContainingLine().End); // 2. Insert ending to the document. var newDocument = document; var endingString = GetEndingString(document, caretPosition); if (endingString != null) { var insertChange = new TextChange(new TextSpan(insertPosition, 0), endingString); buffer.ApplyChange(insertChange); newDocument = document.WithChange(insertChange, cancellationToken); } // 3. format the document and apply the changes to the workspace var changes = FormatBasedOnEndToken(newDocument, insertPosition, formattingOptions, cancellationToken); buffer.ApplyChanges(changes); } }