From aa38480b32504888d7fe347fd4559ef1bd8f469a Mon Sep 17 00:00:00 2001 From: Ivan Basov Date: Mon, 25 Nov 2019 18:28:07 -0800 Subject: [PATCH] draft of completion support for types (excluding user control files) --- .../AbstractChangeSignatureService.cs | 1 + .../ChangeSignature/AddParameterDialog.xaml | 144 +++---- .../AddParameterDialog.xaml.cs | 132 +----- .../AddParameterDialogViewModel.cs | 1 - .../AddParameterTextViewModelProvider.cs | 56 +++ .../ChangeSignatureDialog.xaml.cs | 1 - .../ChangeSignatureWorkspace.cs | 43 ++ .../DependencyObjectExtensions.cs | 32 ++ .../ElisionBufferTextViewModel.cs | 41 ++ .../ParameterTypeEditorControl.cs | 389 ------------------ ...sualStudioChangeSignatureOptionsService.cs | 138 +++++-- 11 files changed, 352 insertions(+), 626 deletions(-) create mode 100644 src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterTextViewModelProvider.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureWorkspace.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/ChangeSignature/DependencyObjectExtensions.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/ChangeSignature/ElisionBufferTextViewModel.cs delete mode 100644 src/VisualStudio/Core/Def/Implementation/ChangeSignature/ParameterTypeEditorControl.cs diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 4834706a7d9..bfdab88050f 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -142,6 +142,7 @@ internal ChangeSignatureResult ChangeSignature(Document document, int position, return new ChangeSignatureAnalyzedContext(CannotChangeSignatureReason.InsufficientParameters); } + // TODO add here a span to be typed in return new ChangeSignatureAnalyzedContext( document, symbol, parameterConfiguration); } diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml index 7c44b46fb5f..e295ec28b85 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml @@ -1,72 +1,72 @@ - - - 0, 5, 0, 2 - 9,2,9,2 - 9,2,9,2 - 4 0 8 0 - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - + + + 0, 5, 0, 2 + 9,2,9,2 + 9,2,9,2 + 4 0 8 0 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml.cs index 5be6bc9ef65..e4b073b6974 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml.cs +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialog.xaml.cs @@ -1,18 +1,9 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Runtime.InteropServices; -using System.Text; using System.Windows; -using System.Windows.Input; -using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.PlatformUI; -using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Editor.Commanding; -using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.VisualStudio.Text.Operations; -using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.TextManager.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature { @@ -22,18 +13,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature internal partial class AddParameterDialog : DialogWindow { private readonly AddParameterDialogViewModel _viewModel; - private readonly ITextEditorFactoryService _textEditorFactoryService; - private readonly ITextBufferFactoryService _textBufferFactoryService; - private IEditorCommandHandlerServiceFactory _commandServiceFactory; - private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; - private readonly IContentType _contentType; - - private IWpfTextView _wpfView; - private IEditorCommandHandlerService _commandService; - - private Action Noop { get; } = new Action(() => { }); - - private Func Available { get; } = () => CommandState.Available; + private readonly IVsTextBuffer _textBuffer; + private readonly IVsTextView _textView; + private readonly IWpfTextView _wpfTextView; public string OK { get { return ServicesVSResources.OK; } } public string Cancel { get { return ServicesVSResources.Cancel; } } @@ -48,18 +30,14 @@ internal partial class AddParameterDialog : DialogWindow public AddParameterDialog( AddParameterDialogViewModel viewModel, - ITextEditorFactoryService textEditorFactoryService, - ITextBufferFactoryService textBufferFactoryService, - IEditorCommandHandlerServiceFactory commandServiceFactory, - IEditorOperationsFactoryService editorOperationsFactoryService, - IContentType contentType) + IVsTextBuffer textBuffer, + IVsTextView vsTextView, + IWpfTextView wpfTextView) { _viewModel = viewModel; - _textEditorFactoryService = textEditorFactoryService; - _textBufferFactoryService = textBufferFactoryService; - _commandServiceFactory = commandServiceFactory; - _editorOperationsFactoryService = editorOperationsFactoryService; - _contentType = contentType; + _textBuffer = textBuffer; + _textView = vsTextView; + _wpfTextView = wpfTextView; this.Loaded += AddParameterDialog_Loaded; InitializeComponent(); @@ -67,15 +45,9 @@ internal partial class AddParameterDialog : DialogWindow private void AddParameterDialog_Loaded(object sender, RoutedEventArgs e) { - var buffer = _textBufferFactoryService.CreateTextBuffer(_contentType); - _wpfView = _textEditorFactoryService.CreateTextView(buffer, _textEditorFactoryService.AllPredefinedRoles); // DefaultRoles might be ok - var viewHost = _textEditorFactoryService.CreateTextViewHost(_wpfView, setFocus: true).HostControl; - this.TypeControl = viewHost; - _commandService = _commandServiceFactory.GetService(_wpfView, buffer); - - var editorOperations = _editorOperationsFactoryService.GetEditorOperations(_wpfView); - - this.TypeControl.Focus(); + this.TypeNameTextBox.TextBuffer = _textBuffer; + this.TypeNameTextBox.TextView = _textView; + this.TypeNameTextBox.WpfTextView = _wpfTextView; } private void OK_Click(object sender, RoutedEventArgs e) @@ -90,83 +62,5 @@ private void Cancel_Click(object sender, RoutedEventArgs e) { DialogResult = false; } - - private void TypeControl_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) - { - // TODO shift/alt - InsertChar(GetCharFromKey(e.Key)); - } - - // --- Get char from Key, courtesy of https://stackoverflow.com/a/5826175/879243 - - public enum MapType : uint - { - MAPVK_VK_TO_VSC = 0x0, - MAPVK_VSC_TO_VK = 0x1, - MAPVK_VK_TO_CHAR = 0x2, - MAPVK_VSC_TO_VK_EX = 0x3, - } - - [DllImport("user32.dll")] - public static extern int ToUnicode( - uint wVirtKey, - uint wScanCode, - byte[] lpKeyState, - [Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 4)] - StringBuilder pwszBuff, - int cchBuff, - uint wFlags); - - [DllImport("user32.dll")] - public static extern bool GetKeyboardState(byte[] lpKeyState); - - [DllImport("user32.dll")] - public static extern uint MapVirtualKey(uint uCode, MapType uMapType); - - public static char GetCharFromKey(Key key) - { - char ch = '\0'; - - int virtualKey = KeyInterop.VirtualKeyFromKey(key); - byte[] keyboardState = new byte[256]; - GetKeyboardState(keyboardState); - - uint scanCode = MapVirtualKey((uint)virtualKey, MapType.MAPVK_VK_TO_VSC); - StringBuilder stringBuilder = new StringBuilder(2); - - int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, stringBuilder, stringBuilder.Capacity, 0); - switch (result) - { - case -1: - break; - case 0: - break; - case 1: - { - ch = stringBuilder[0]; - break; - } - default: - { - ch = stringBuilder[0]; - break; - } - } - return ch; - } - - public void InsertChar(char character) - { - QueryAndExecute((v, b) => new TypeCharCommandArgs(v, b, character)); - } - - public void QueryAndExecute(Func argsFactory) where T : EditorCommandArgs - { - var state = _commandService.GetCommandState(argsFactory, Available); - if (state.IsAvailable) - { - _commandService.Execute(argsFactory, Noop); - } - } } } diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialogViewModel.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialogViewModel.cs index ae6c1c883b8..ee2907ce948 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterDialogViewModel.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Windows.Input; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterTextViewModelProvider.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterTextViewModelProvider.cs new file mode 100644 index 00000000000..f9628d04543 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/AddParameterTextViewModelProvider.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Projection; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature +{ + // TODO here or below, we need a split for different languages + // We may do this eiter creating such files for each content type or making the split below. + // Actually, ITextDataModel contains ContentType. So, we may do the split below. + [Export(typeof(ITextViewModelProvider))] + [ContentType(ContentTypeNames.RoslynContentType)] + [TextViewRole(VisualStudioChangeSignatureOptionsService.AddParameterTextViewRole)] + internal class AddParameterTextViewModelProvider : ITextViewModelProvider + { + [Import] + public IProjectionBufferFactoryService ProjectionBufferFactoryService { get; set; } + + public ITextViewModel CreateTextViewModel(ITextDataModel dataModel, ITextViewRoleSet roles) + { + var namespaceSpan = GetNamespaceSpan(dataModel.DataBuffer.CurrentSnapshot); + + var elisionBuffer = ProjectionBufferFactoryService.CreateElisionBuffer( + null, + new NormalizedSnapshotSpanCollection(namespaceSpan), + ElisionBufferOptions.None); + + return new ElisionBufferTextViewModel(dataModel, elisionBuffer); + } + + private SnapshotSpan GetNamespaceSpan(ITextSnapshot snapshot) + { + var totalLineNumber = snapshot.LineCount; + var start = snapshot.GetLineFromLineNumber(0).Start; + for (int i = 0; i < totalLineNumber; i++) + { + var currentLine = snapshot.GetLineFromLineNumber(i); + string text = currentLine.GetText().Trim(); + //if (text.StartsWith("public virtual SomeCollection Include(string path) => null;", StringComparison.Ordinal)) + if (text.StartsWith("namespace", StringComparison.Ordinal)) + { + int offset = "namespace".Length; + return new SnapshotSpan(currentLine.Start + offset, text.Length - offset); + //return new SnapshotSpan(currentLine.Start + text.IndexOf(")") - 1, 0); + } + } + + throw new Exception("Unable to find namespace span."); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml.cs index f4f296c81fb..eea9ea481ed 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml.cs +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Input; diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureWorkspace.cs new file mode 100644 index 00000000000..1146ca21f74 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureWorkspace.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Editor.Options; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature +{ + internal class ChangeSignatureWorkspace : Workspace + { + public ChangeSignatureWorkspace(Solution solution, Project project) + : base(project.Solution.Workspace.Services.HostServices, nameof(ChangeSignatureWorkspace)) + { + // The solution we are handed is still parented by the original workspace. We want to + // inherit it's "no partial solutions" flag so that way this workspace will also act + // deterministically if we're in unit tests + this.TestHookPartialSolutionsDisabled = solution.Workspace.TestHookPartialSolutionsDisabled; + + // Create a new document to hold the temporary code + ChangeSignatureDocumentId = DocumentId.CreateNewId(project.Id); + this.SetCurrentSolution(solution.AddDocument(ChangeSignatureDocumentId, Guid.NewGuid().ToString(), GetDocumentText())); + + Options = Options.WithChangedOption(EditorCompletionOptions.UseSuggestionMode, true); + } + + private string GetDocumentText() + { + return $@" +{{ +}} +"; + } + + public Document ChangeSignatureDocument => this.CurrentSolution.GetDocument(this.ChangeSignatureDocumentId); + public DocumentId ChangeSignatureDocumentId { get; } + + public void OpenDocument(DocumentId documentId, SourceTextContainer textContainer) + { + this.OnDocumentOpened(documentId, textContainer); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/DependencyObjectExtensions.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/DependencyObjectExtensions.cs new file mode 100644 index 00000000000..b815ce212fc --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/DependencyObjectExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Windows; +using System.Windows.Media; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature +{ + // TODO move to utilities + internal static class DependencyObjectExtensions + { + public static DependencyObject TryGetParent(this DependencyObject obj) + { + return (obj is Visual) ? VisualTreeHelper.GetParent(obj) : null; + } + + public static T GetParentOfType(this DependencyObject element) where T : Visual + { + var parent = element.TryGetParent(); + if (parent is T) + { + return (T)parent; + } + + if (parent == null) + { + return null; + } + + return parent.GetParentOfType(); + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ElisionBufferTextViewModel.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ElisionBufferTextViewModel.cs new file mode 100644 index 00000000000..5a220fbe1dd --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ElisionBufferTextViewModel.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Projection; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature +{ + internal class ElisionBufferTextViewModel : ITextViewModel + { + public ElisionBufferTextViewModel(ITextDataModel dataModel, IElisionBuffer elisionBuffer) + { + DataModel = dataModel; + ElisionBuffer = elisionBuffer; + Properties = new PropertyCollection(); + } + + public IElisionBuffer ElisionBuffer { get; } + public ITextDataModel DataModel { get; } + + public ITextBuffer DataBuffer => DataModel.DataBuffer; + + public ITextBuffer EditBuffer => ElisionBuffer; + + public ITextBuffer VisualBuffer => ElisionBuffer; + + public PropertyCollection Properties { get; } + + public void Dispose() + { + } + + public SnapshotPoint GetNearestPointInVisualBuffer(SnapshotPoint editBufferPoint) => editBufferPoint; + + public SnapshotPoint GetNearestPointInVisualSnapshot(SnapshotPoint editBufferPoint, ITextSnapshot targetVisualSnapshot, PointTrackingMode trackingMode) + => editBufferPoint.TranslateTo(targetVisualSnapshot, trackingMode); + + public bool IsPointInVisualBuffer(SnapshotPoint editBufferPoint, PositionAffinity affinity) => true; + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ParameterTypeEditorControl.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ParameterTypeEditorControl.cs deleted file mode 100644 index bc8f6284a71..00000000000 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ParameterTypeEditorControl.cs +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel.Design; -using System.Net.Mime; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Operations; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Utilities; -using Constants = Microsoft.VisualStudio.OLE.Interop.Constants; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature -{ - internal sealed class ParameterTypeEditorControl : TextBox - { - private IVsTextView _vsTextView; - private IWpfTextView _wpfTextView; - private IWpfTextViewHost _host; - private IEditorOperations _editorOperations; - private IVsEditorAdaptersFactoryService _vsEditorAdaptersFactoryService; - private IEditorOperationsFactoryService _editorOperationsFactoryService; - private System.IServiceProvider _serviceProvider; - private IOleCommandTarget _nextCommandTarget; - - public void Initialize( - IVsTextView vsTextView, - IWpfTextView wpfTextView, - IWpfTextViewHost textViewHost, - IVsEditorAdaptersFactoryService editorAdaptersFactory, - IEditorOperationsFactoryService editorOperationsFactoryService, - ITextEditorFactoryService textEditorFactoryService, - ITextBufferFactoryService textBufferFactoryService, - System.IServiceProvider serviceProvider, - IContentType contentType) - { - var buffer = textBufferFactoryService.CreateTextBuffer(contentType); - var view = textEditorFactoryService.CreateTextView(buffer, textEditorFactoryService.AllPredefinedRoles); // DefaultRoles might be ok - var viewHost = textEditorFactoryService.CreateTextViewHost((IWpfTextView)view, setFocus: true).HostControl; - - //Control viewHost = textViewHost.HostControl; - - - - _vsTextView = vsTextView; - _wpfTextView = wpfTextView; - _host = textViewHost; - _vsEditorAdaptersFactoryService = editorAdaptersFactory; - _editorOperationsFactoryService = editorOperationsFactoryService; - _serviceProvider = serviceProvider; - - InstallCommandFilter(); - InitializeEditorControl(); - } - - private void InitializeEditorControl() - { - //AddLogicalChild(_host.HostControl); - //AddVisualChild(_host.HostControl); - } - - private void InstallCommandFilter() - { - //if (_vsEditorAdaptersFactoryService != null) - //{ - // _editorOperations = _editorOperationsFactoryService.GetEditorOperations(this._host.TextView); - //} - - //ErrorHandler.ThrowOnFailure(this._vsTextView.AddCommandFilter(this, out this._nextCommandTarget)); - } - - public string GetText() => this._wpfTextView.TextSnapshot.GetText(); - - /// - /// Query command status - /// - /// Command group guid - /// The number of commands in the OLECMD array - /// The set of command ids - /// Unuses pCmdText - /// A Microsoft.VisualStudio.OLE.Interop.Constants value - public int QueryStatus(ref Guid pguidCmdGroup, uint cmdCount, OLECMD[] prgCmds, IntPtr cmdText) - { - - // Return status UNKNOWNGROUP if the passed command group is different than the ones we know about - if (pguidCmdGroup != VsMenus.guidStandardCommandSet2K && - pguidCmdGroup != VsMenus.guidStandardCommandSet97) - { - return (int)Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_UNKNOWNGROUP; - } - - // 1. For the commands we support and don't need to have a custom implementation - // simply ask the next command handler in the filter chain for the command status - // 2. For the commands we have a custom implementation, calculate and return status value - // 3. For other commands, set status to NOTSUPPORTED (0) - for (int i = 0; i < cmdCount; i++) - { - if (this.IsPassThroughCommand(ref pguidCmdGroup, prgCmds[i].cmdID)) - { - OLECMD[] cmdArray = new OLECMD[] { new OLECMD() }; - cmdArray[0].cmdID = prgCmds[i].cmdID; - int hr = this._nextCommandTarget.QueryStatus(ref pguidCmdGroup, 1, cmdArray, cmdText); - - if (ErrorHandler.Failed(hr)) - { - continue; - } - - prgCmds[i].cmdf = cmdArray[0].cmdf; - } - else if ((pguidCmdGroup == VsMenus.guidStandardCommandSet97 && prgCmds[i].cmdID == StandardCommands.Cut.ID) || - (pguidCmdGroup == VsMenus.guidStandardCommandSet2K && prgCmds[i].cmdID == (uint)VSConstants.VSStd2KCmdID.CUT) || - (pguidCmdGroup == VsMenus.guidStandardCommandSet97 && prgCmds[i].cmdID == StandardCommands.Copy.ID) || - (pguidCmdGroup == VsMenus.guidStandardCommandSet2K && prgCmds[i].cmdID == (uint)VSConstants.VSStd2KCmdID.COPY)) - { - prgCmds[i].cmdf = (uint)OLECMDF.OLECMDF_SUPPORTED; - - //if (this.CanCutCopy()) - //{ - // prgCmds[i].cmdf |= (uint)OLECMDF.OLECMDF_ENABLED; - //} - } - else if ((pguidCmdGroup == VsMenus.guidStandardCommandSet97 && prgCmds[i].cmdID == StandardCommands.Paste.ID) || - (pguidCmdGroup == VsMenus.guidStandardCommandSet2K && prgCmds[i].cmdID == (uint)VSConstants.VSStd2KCmdID.PASTE)) - { - prgCmds[i].cmdf = (uint)OLECMDF.OLECMDF_SUPPORTED; - - //if (this.CanPaste()) - //{ - // prgCmds[i].cmdf |= (uint)OLECMDF.OLECMDF_ENABLED; - //} - } - else - { - prgCmds[i].cmdf = 0; - } - } - - return VSConstants.S_OK; - } - - /// - /// Executes the given shell command - /// - /// Command group guid - /// Command id - /// Options for the executing command - /// The input arguments structure - /// The command output structure - /// Exec return value - public int Exec(ref Guid pguidCmdGroup, uint cmdID, uint cmdExecOpt, IntPtr pvaIn, IntPtr pvaOut) - { - // Return status UNKNOWNGROUP if the passed command group is different than the ones we know about - if (pguidCmdGroup != VsMenus.guidStandardCommandSet2K && - pguidCmdGroup != VsMenus.guidStandardCommandSet97) - { - return (int)Constants.OLECMDERR_E_UNKNOWNGROUP; - } - - int hr = 0; - - // 1. For the commands we support and don't need to have a custom implementation - // simply pass the command to the next command handler in the filter chain - // 2. For the commands we have a custom implementation, carry out the command - // don't pass it to the next command handler - // 3. For other commands, simply return with NOTSUPPORTED - if (this.IsPassThroughCommand(ref pguidCmdGroup, cmdID)) - { - hr = this._nextCommandTarget.Exec(ref pguidCmdGroup, cmdID, cmdExecOpt, pvaIn, pvaOut); - } - - else - { - hr = (int)Constants.OLECMDERR_E_NOTSUPPORTED; - } - - return hr; - } - - /// - /// Determines whether the given command should be passed to the - /// next command handler in the text view command filter chain. - /// - /// The command group guid - /// The command id - /// True, if the command is supported and should be passed to the next command handler - private bool IsPassThroughCommand(ref Guid pguidCmdGroup, uint cmdID) - { - if (pguidCmdGroup == VsMenus.guidStandardCommandSet2K) - { - switch ((VSConstants.VSStd2KCmdID)cmdID) - { - case VSConstants.VSStd2KCmdID.COMPLETEWORD: - case VSConstants.VSStd2KCmdID.TYPECHAR: - case VSConstants.VSStd2KCmdID.BACKSPACE: - case VSConstants.VSStd2KCmdID.TAB: - case VSConstants.VSStd2KCmdID.BACKTAB: - case VSConstants.VSStd2KCmdID.DELETE: - case VSConstants.VSStd2KCmdID.DELETEWORDRIGHT: - case VSConstants.VSStd2KCmdID.DELETEWORDLEFT: - case VSConstants.VSStd2KCmdID.DELETETOBOL: - case VSConstants.VSStd2KCmdID.DELETETOEOL: - case VSConstants.VSStd2KCmdID.UP: - case VSConstants.VSStd2KCmdID.DOWN: - case VSConstants.VSStd2KCmdID.LEFT: - case VSConstants.VSStd2KCmdID.LEFT_EXT: - case VSConstants.VSStd2KCmdID.LEFT_EXT_COL: - case VSConstants.VSStd2KCmdID.RIGHT: - case VSConstants.VSStd2KCmdID.RIGHT_EXT: - case VSConstants.VSStd2KCmdID.RIGHT_EXT_COL: - case VSConstants.VSStd2KCmdID.EditorLineFirstColumn: - case VSConstants.VSStd2KCmdID.EditorLineFirstColumnExtend: - case VSConstants.VSStd2KCmdID.BOL: - case VSConstants.VSStd2KCmdID.BOL_EXT: - case VSConstants.VSStd2KCmdID.BOL_EXT_COL: - case VSConstants.VSStd2KCmdID.EOL: - case VSConstants.VSStd2KCmdID.EOL_EXT: - case VSConstants.VSStd2KCmdID.EOL_EXT_COL: - case VSConstants.VSStd2KCmdID.SELECTALL: - case VSConstants.VSStd2KCmdID.CANCEL: - case VSConstants.VSStd2KCmdID.WORDPREV: - case VSConstants.VSStd2KCmdID.WORDPREV_EXT: - case VSConstants.VSStd2KCmdID.WORDPREV_EXT_COL: - case VSConstants.VSStd2KCmdID.WORDNEXT: - case VSConstants.VSStd2KCmdID.WORDNEXT_EXT: - case VSConstants.VSStd2KCmdID.WORDNEXT_EXT_COL: - case VSConstants.VSStd2KCmdID.SELECTCURRENTWORD: - case VSConstants.VSStd2KCmdID.TOGGLE_OVERTYPE_MODE: - return true; - } - } - else if (pguidCmdGroup == VsMenus.guidStandardCommandSet97) - { - switch ((VSConstants.VSStd97CmdID)cmdID) - { - case VSConstants.VSStd97CmdID.Delete: - case VSConstants.VSStd97CmdID.SelectAll: - case VSConstants.VSStd97CmdID.Undo: - case VSConstants.VSStd97CmdID.Redo: - return true; - } - } - - return false; - } - - - /// - /// Return visual child at given index - /// - /// child index - /// returns visual child - // protected override Visual GetVisualChild(int index) => this._host?.HostControl; - - // protected override Size ArrangeOverride(Size finalSize) - // { - //// _host.HostControl.Arrange(new Rect(new Point(0, 0), finalSize)); - // return finalSize; - // } - - // protected override void OnGotFocus(RoutedEventArgs e) - // { - // e.Handled = true; - // this._host.TextView.VisualElement.Focus(); - // } - - private static DependencyObject TryGetParent(DependencyObject obj) - { - return (obj is Visual) ? VisualTreeHelper.GetParent(obj) : null; - } - - private static T GetParentOfType(DependencyObject element) where T : Visual - { - var parent = TryGetParent(element); - if (parent is T) - { - return (T)parent; - } - - if (parent == null) - { - return null; - } - - return GetParentOfType(parent); - } - - internal static void HandleKeyDown(object sender, KeyEventArgs e) - { - var parameterTypeEditorControl = Keyboard.FocusedElement as ParameterTypeEditorControl; - - if (parameterTypeEditorControl != null && parameterTypeEditorControl._vsTextView != null) - { - switch (e.Key) - { - case Key.Escape: - case Key.Tab: - case Key.Enter: - e.Handled = true; - break; - - default: - // Let the editor control handle the keystrokes - var msg = ComponentDispatcher.CurrentKeyboardMessage; - - var oleInteropMsg = new OLE.Interop.MSG(); - - oleInteropMsg.hwnd = msg.hwnd; - oleInteropMsg.message = (uint)msg.message; - oleInteropMsg.wParam = msg.wParam; - oleInteropMsg.lParam = msg.lParam; - oleInteropMsg.pt.x = msg.pt_x; - oleInteropMsg.pt.y = msg.pt_y; - - e.Handled = parameterTypeEditorControl.HandleKeyDown(oleInteropMsg); - break; - } - } - else - { - if (e.Key == Key.Escape) - { - //OnCancel(); - } - } - } - - private bool HandleKeyDown(OLE.Interop.MSG message) - { - uint editCmdID = 0; - Guid editCmdGuid = Guid.Empty; - int VariantSize = 16; - - var filterKeys = Package.GetGlobalService(typeof(SVsFilterKeys)) as IVsFilterKeys2; - - if (filterKeys != null) - { - int translated; - int firstKeyOfCombo; - var pMsg = new OLE.Interop.MSG[1]; - pMsg[0] = message; - ErrorHandler.ThrowOnFailure(filterKeys.TranslateAcceleratorEx(pMsg, - (uint)(__VSTRANSACCELEXFLAGS.VSTAEXF_NoFireCommand | __VSTRANSACCELEXFLAGS.VSTAEXF_UseTextEditorKBScope | __VSTRANSACCELEXFLAGS.VSTAEXF_AllowModalState), - 0, - null, - out editCmdGuid, - out editCmdID, - out translated, - out firstKeyOfCombo)); - - if (translated == 1) - { - var inArg = IntPtr.Zero; - try - { - // if the command is undo (Ctrl + Z) or redo (Ctrl + Y) then leave it as IntPtr.Zero because of a bug in undomgr.cpp where - // it does undo or redo only for null, VT_BSTR and VT_EMPTY - if ((int)message.wParam != Convert.ToInt32('Z') && (int)message.wParam != Convert.ToInt32('Y')) - { - inArg = Marshal.AllocHGlobal(VariantSize); - Marshal.GetNativeVariantForObject(message.wParam, inArg); - } - - return Exec(ref editCmdGuid, editCmdID, 0, inArg, IntPtr.Zero) == VSConstants.S_OK; - } - finally - { - if (inArg != IntPtr.Zero) - Marshal.FreeHGlobal(inArg); - } - } - } - - // no translation available for this message - return false; - } - - } -} diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs index ebf27059391..899f57394fe 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs @@ -10,13 +10,13 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Editor.Commanding; -using Microsoft.VisualStudio.Text.Operations; +using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Utilities; using ImportingConstructorAttribute = System.Composition.ImportingConstructorAttribute; @@ -26,16 +26,15 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature [ExportWorkspaceService(typeof(IChangeSignatureOptionsService), ServiceLayer.Host), Shared] internal class VisualStudioChangeSignatureOptionsService : IChangeSignatureOptionsService { + public const string AddParameterTextViewRole = "AddParameter"; + private readonly IClassificationFormatMap _classificationFormatMap; private readonly ClassificationTypeMap _classificationTypeMap; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; - private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; private readonly ITextEditorFactoryService _textEditorFactoryService; - private readonly IServiceProvider _originalServiceProvider; private readonly IContentType _contentType; private readonly OLE.Interop.IServiceProvider _serviceProvider; - private readonly ITextBufferFactoryService _textBufferFactoryService; - private readonly IEditorCommandHandlerServiceFactory _editorCommandHandlerServiceFactory; + private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -44,22 +43,17 @@ internal class VisualStudioChangeSignatureOptionsService : IChangeSignatureOptio ClassificationTypeMap classificationTypeMap, IVsEditorAdaptersFactoryService editorAdaptersFactoryService, ITextEditorFactoryService textEditorFactoryService, - IEditorOperationsFactoryService editorOperationsFactoryService, IContentTypeRegistryService contentTypeRegistryService, - ITextBufferFactoryService textBufferFactoryService, - IEditorCommandHandlerServiceFactory editorCommandHandlerServiceFactory, + IProjectionBufferFactoryService projectionBufferFactoryService, SVsServiceProvider services) { _classificationFormatMap = classificationFormatMapService.GetClassificationFormatMap("tooltip"); _classificationTypeMap = classificationTypeMap; _editorAdaptersFactoryService = editorAdaptersFactoryService; _textEditorFactoryService = textEditorFactoryService; - _editorOperationsFactoryService = editorOperationsFactoryService; _contentType = contentTypeRegistryService.GetContentType(ContentTypeNames.CSharpContentType); - _originalServiceProvider = services; - _editorCommandHandlerServiceFactory = editorCommandHandlerServiceFactory; _serviceProvider = (OLE.Interop.IServiceProvider)services.GetService(typeof(OLE.Interop.IServiceProvider)); - _textBufferFactoryService = textBufferFactoryService; + _projectionBufferFactoryService = projectionBufferFactoryService; } public ChangeSignatureOptionsResult GetChangeSignatureOptions( @@ -91,18 +85,7 @@ internal class VisualStudioChangeSignatureOptionsService : IChangeSignatureOptio public AddedParameterResult GetAddedParameter(Document document) { - // TODO async? - var tuple = GetTextViewAsync(document, CancellationToken.None).Result; - var wpfTextView = _editorAdaptersFactoryService.GetWpfTextView(tuple.Item1); - var viewModel = new AddParameterDialogViewModel(); - var dialog = new AddParameterDialog( - viewModel, - _textEditorFactoryService, - _textBufferFactoryService, - _editorCommandHandlerServiceFactory, - _editorOperationsFactoryService, - _contentType); - + var (dialog, viewModel) = CreateAddParameterDialogAsync(document, CancellationToken.None).Result; var result = dialog.ShowModal(); if (result.HasValue && result.Value) @@ -122,29 +105,22 @@ public AddedParameterResult GetAddedParameter(Document document) } } - private async Task GetDocumentTextAsync(Document document, CancellationToken cancellationToken) + private async Task<(AddParameterDialog, AddParameterDialogViewModel)> CreateAddParameterDialogAsync( + Document document, CancellationToken cancellationToken) { var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var sourceText = await syntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); - - return sourceText.ToString(); - } - - public async Task<(IVsTextView, IWpfTextViewHost)> GetTextViewAsync(Document document, CancellationToken cancellationToken) - { - var documentText = await GetDocumentTextAsync(document, cancellationToken).ConfigureAwait(false); + var documentText = sourceText.ToString(); var roleSet = _textEditorFactoryService.CreateTextViewRoleSet( PredefinedTextViewRoles.Document, PredefinedTextViewRoles.Editable, - PredefinedTextViewRoles.Interactive); - - var textViewAdapter = _editorAdaptersFactoryService.CreateVsTextViewAdapter(_serviceProvider, roleSet); - var bufferAdapter = _editorAdaptersFactoryService.CreateVsTextBufferAdapter(_serviceProvider, _contentType); - bufferAdapter.InitializeContent(documentText, documentText.Length); + PredefinedTextViewRoles.Interactive, + AddParameterTextViewRole); - // var textBuffer = _vsEditorAdaptersFactoryService.GetDataBuffer(bufferAdapter); - // document.Project.Solution.Workspace.OnDocumentOpened(document.Id, textBuffer.AsTextContainer()); + var vsTextView = _editorAdaptersFactoryService.CreateVsTextViewAdapter(_serviceProvider, roleSet); + var vsTextBuffer = _editorAdaptersFactoryService.CreateVsTextBufferAdapter(_serviceProvider, _contentType); + vsTextBuffer.InitializeContent(documentText, documentText.Length); var initView = new[] { new INITVIEW() @@ -156,15 +132,89 @@ public async Task<(IVsTextView, IWpfTextViewHost)> GetTextViewAsync(Document doc } }; - textViewAdapter.Initialize( - bufferAdapter as IVsTextLines, + vsTextView.Initialize( + vsTextBuffer as IVsTextLines, IntPtr.Zero, (uint)TextViewInitFlags3.VIF_NO_HWND_SUPPORT, initView); - var textViewHost = _editorAdaptersFactoryService.GetWpfTextViewHost(textViewAdapter); + var wpfTextView = _editorAdaptersFactoryService.GetWpfTextView(vsTextView); + + var originalContextBuffer = _editorAdaptersFactoryService.GetDataBuffer(vsTextBuffer); + // Get the workspace, and from there, the solution and document containing this buffer. + // If there's an ExternalSource, we won't get a document. Give up in that case. + var solution = document.Project.Solution; + + // Get the appropriate ITrackingSpan for the window the user is typing in + var viewSnapshot = wpfTextView.TextSnapshot; + var debuggerMappedSpan = CreateFullTrackingSpan(viewSnapshot, SpanTrackingMode.EdgeInclusive); + + // Wrap the original ContextBuffer in a projection buffer that we can make read-only + var contextBuffer = _projectionBufferFactoryService.CreateProjectionBuffer(null, + new object[] { CreateFullTrackingSpan(originalContextBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive) }, ProjectionBufferOptions.None, _contentType); + + // Make projection readonly so we can't edit it by mistake. + using (var regionEdit = contextBuffer.CreateReadOnlyRegionEdit()) + { + regionEdit.CreateReadOnlyRegion(new Span(0, contextBuffer.CurrentSnapshot.Length), SpanTrackingMode.EdgeInclusive, EdgeInsertionMode.Deny); + regionEdit.Apply(); + } + + // Adjust the context point to ensure that the right information is in scope. + // For example, we may need to move the point to the end of the last statement in a method body + // in order to be able to access all local variables. + // var contextPoint = contextBuffer.CurrentSnapshot.GetLineFromLineNumber(currentStatementSpan.iEndLine).Start + currentStatementSpan.iEndIndex; + //var adjustedContextPoint = GetAdjustedContextPoint(contextPoint, document); + + // Get the previous span/text. We might have to insert another newline or something. + // var previousStatementSpan = GetPreviousStatementBufferAndSpan(adjustedContextPoint, document); + + // Build the tracking span that includes the rest of the file + // var restOfFileSpan = CreateTrackingSpanFromIndexToEnd(contextBuffer.CurrentSnapshot, adjustedContextPoint, SpanTrackingMode.EdgePositive); - return (textViewAdapter, textViewHost); + // Put it all into a projection buffer + var projectionBuffer = _projectionBufferFactoryService.CreateProjectionBuffer(null, + new object[] { /* previousStatementSpan, */ debuggerMappedSpan, /* this.StatementTerminator */ /*, restOfFileSpan */}, ProjectionBufferOptions.None, _contentType); + + // Fork the solution using this new primary buffer for the document and all of its linked documents. + var forkedSolution = solution.WithDocumentText(document.Id, projectionBuffer.CurrentSnapshot.AsText(), PreservationMode.PreserveIdentity); + foreach (var link in document.GetLinkedDocumentIds()) + { + forkedSolution = forkedSolution.WithDocumentText(link, projectionBuffer.CurrentSnapshot.AsText(), PreservationMode.PreserveIdentity); + } + + // Put it into a new workspace, and open it and its related documents + // with the projection buffer as the text. + var workspace = new ChangeSignatureWorkspace(forkedSolution, document.Project); + workspace.OpenDocument(workspace.ChangeSignatureDocumentId, originalContextBuffer.AsTextContainer()); + foreach (var link in document.GetLinkedDocumentIds()) + { + workspace.OpenDocument(link, projectionBuffer.AsTextContainer()); + } + + // Start getting the compilation so the PartialSolution will be ready when the user starts typing in the window + await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + wpfTextView.TextBuffer.ChangeContentType(_contentType, null); + + var viewModel = new AddParameterDialogViewModel(); + var dialog = new AddParameterDialog( + viewModel, + vsTextBuffer, + vsTextView, + wpfTextView); + + return (dialog, viewModel); + } + + public static ITrackingSpan CreateFullTrackingSpan(ITextSnapshot textSnapshot, SpanTrackingMode trackingMode) + { + return textSnapshot.CreateTrackingSpan(Span.FromBounds(0, textSnapshot.Length), trackingMode); + } + + public static ITrackingSpan CreateTrackingSpanFromIndexToEnd(ITextSnapshot textSnapshot, int index, SpanTrackingMode trackingMode) + { + return textSnapshot.CreateTrackingSpan(Span.FromBounds(index, textSnapshot.Length), trackingMode); } } } -- GitLab