/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Core/Interactive/InteractivePasteCommandHandler.cs
177 строк
7 KB
Cyrus Najmabadi
Move files
25 апр 2025, 01:10
25 апр 2025, 01:10
46f9987
Код
Авторство
О чём код?
// 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 System; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Windows; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; namespace Microsoft.CodeAnalysis.Interactive; // This command handler must be invoked after the handlers specified in `Order` attribute // (those handlers also implement `ICommandHandler<PasteCommandArgs>`), // because it will intercept the paste command and skip the rest of handlers in chain. [Export(typeof(ICommandHandler))] [ContentType(ContentTypeNames.RoslynContentType)] [Name(PredefinedCommandHandlerNames.InteractivePaste)] [Order(After = PredefinedCommandHandlerNames.Rename)] [Order(After = PredefinedCommandHandlerNames.FormatDocument)] [Order(After = PredefinedCommandHandlerNames.Commit)] [Order(After = PredefinedCompletionNames.CompletionCommandHandler)] internal sealed class InteractivePasteCommandHandler : ICommandHandler<PasteCommandArgs> { // The following two field definitions have to stay in sync with VS editor implementation /// <summary> /// A data format used to tag the contents of the clipboard so that it's clear /// the data has been put in the clipboard by our editor /// </summary> internal const string ClipboardLineBasedCutCopyTag = "VisualStudioEditorOperationsLineCutCopyClipboardTag"; /// <summary> /// A data format used to tag the contents of the clipboard as a box selection. /// This is the same string that was used in VS9 and previous versions. /// </summary> internal const string BoxSelectionCutCopyTag = "MSDEVColumnSelect"; private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; private readonly ITextUndoHistoryRegistry _textUndoHistoryRegistry; // This is for unit test purpose only, do not explicitly set this field otherwise. internal IRoslynClipboard RoslynClipboard; public string DisplayName => EditorFeaturesResources.Paste_in_Interactive; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public InteractivePasteCommandHandler(IEditorOperationsFactoryService editorOperationsFactoryService, ITextUndoHistoryRegistry textUndoHistoryRegistry) { _editorOperationsFactoryService = editorOperationsFactoryService; _textUndoHistoryRegistry = textUndoHistoryRegistry; RoslynClipboard = new SystemClipboardWrapper(); } public bool ExecuteCommand(PasteCommandArgs args, CommandExecutionContext context) { // InteractiveWindow handles pasting by itself, which including checks for buffer types, etc. if (!args.TextView.TextBuffer.ContentType.IsOfType(PredefinedInteractiveContentTypes.InteractiveContentTypeName) && RoslynClipboard.ContainsData(InteractiveClipboardFormat.Tag)) { PasteInteractiveFormat(args.TextView); return true; } else { return false; } } public CommandState GetCommandState(PasteCommandArgs args) => CommandState.Unspecified; [MethodImpl(MethodImplOptions.NoInlining)] // Avoid loading InteractiveWindow unless necessary private void PasteInteractiveFormat(ITextView textView) { var editorOperations = _editorOperationsFactoryService.GetEditorOperations(textView); var data = RoslynClipboard.GetDataObject(); Debug.Assert(data != null); var dataHasLineCutCopyTag = data.GetDataPresent(ClipboardLineBasedCutCopyTag); var dataHasBoxCutCopyTag = data.GetDataPresent(BoxSelectionCutCopyTag); Debug.Assert(!(dataHasLineCutCopyTag && dataHasBoxCutCopyTag)); string text; try { text = InteractiveClipboardFormat.Deserialize(RoslynClipboard.GetData(InteractiveClipboardFormat.Tag)); } catch (InvalidDataException) { text = "<bad clipboard data>"; } using var transaction = _textUndoHistoryRegistry.GetHistory(textView.TextBuffer).CreateTransaction(EditorFeaturesResources.Paste); editorOperations.AddBeforeTextBufferChangePrimitive(); if (dataHasLineCutCopyTag && textView.Selection.IsEmpty) { editorOperations.MoveToStartOfLine(extendSelection: false); editorOperations.InsertText(text); } else if (dataHasBoxCutCopyTag) { // If the caret is on a blank line, treat this like a normal stream insertion if (textView.Selection.IsEmpty && !HasNonWhiteSpaceCharacter(textView.Caret.Position.BufferPosition.GetContainingLine())) { // trim the last newline before paste var trimmed = text.Remove(text.LastIndexOf(textView.Options.GetNewLineCharacter())); editorOperations.InsertText(trimmed); } else { editorOperations.InsertTextAsBox(text, out _, out _); } } else { editorOperations.InsertText(text); } editorOperations.AddAfterTextBufferChangePrimitive(); transaction.Complete(); } private static bool HasNonWhiteSpaceCharacter(ITextSnapshotLine line) { var snapshot = line.Snapshot; var start = line.Start.Position; var count = line.Length; for (var i = 0; i < count; i++) { if (!char.IsWhiteSpace(snapshot[start + i])) { return true; } } return false; } // The mock clipboard used in tests will implement this interface internal interface IRoslynClipboard { bool ContainsData(string format); object GetData(string format); IDataObject GetDataObject(); } // In product code, we use this simple wrapper around system clipboard. // Maybe at some point we can elevate this class and interface so they could be shared among Roslyn code base. private sealed class SystemClipboardWrapper : IRoslynClipboard { public bool ContainsData(string format) => Clipboard.ContainsData(format); public object GetData(string format) => Clipboard.GetData(format); public IDataObject GetDataObject() => Clipboard.GetDataObject(); } }