/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Core/Preview/DifferenceViewerPreview.cs
226 строк
9 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. using System; using System.Windows; using System.Windows.Interop; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Operations; namespace Microsoft.CodeAnalysis.Editor.Implementation.Preview; internal sealed partial class DifferenceViewerPreview : IDifferenceViewerPreview<IWpfDifferenceViewer> { private const int WM_KEYFIRST = 0x0100; private const int WM_KEYLAST = 0x0108; private readonly IVsFilterKeys2? _filterKeys; private IWpfDifferenceViewer? _viewer; private bool _hasFocus; private NavigationalCommandTarget? _editorCommandTarget; public DifferenceViewerPreview(IWpfDifferenceViewer viewer, IEditorOperationsFactoryService editorOperationsFactoryService) { Contract.ThrowIfNull(viewer); _viewer = viewer; _viewer.VisualElement.IsKeyboardFocusWithinChanged += OnDifferenceViewerKeyboardFocusWithinChanged; _hasFocus = _viewer.VisualElement.IsKeyboardFocusWithin; var host = _viewer.ViewMode switch { DifferenceViewMode.Inline => _viewer.InlineHost, DifferenceViewMode.LeftViewOnly => _viewer.LeftHost, DifferenceViewMode.RightViewOnly => _viewer.RightHost, _ => throw ExceptionUtilities.UnexpectedValue(_viewer.ViewMode), }; _editorCommandTarget = new NavigationalCommandTarget(host.TextView, editorOperationsFactoryService.GetEditorOperations(host.TextView)); _filterKeys = Package.GetGlobalService(typeof(SVsFilterKeys)) as IVsFilterKeys2; } public IWpfDifferenceViewer Viewer { get { Contract.ThrowIfNull(_viewer); return _viewer; } } public void Dispose() { ThreadHelper.ThrowIfNotOnUIThread(); GC.SuppressFinalize(this); if (_viewer != null) { ComponentDispatcher.ThreadFilterMessage -= FilterThreadMessage; _viewer.VisualElement.IsKeyboardFocusWithinChanged -= OnDifferenceViewerKeyboardFocusWithinChanged; if (!_viewer.IsClosed) _viewer.Close(); } _viewer = null; _editorCommandTarget = null; } ~DifferenceViewerPreview() { // make sure we are not leaking diff viewer // we can't close the view from finalizer thread since it must be same // thread (owner thread) this UI is created. if (Environment.HasShutdownStarted) { return; } FatalError.ReportAndCatch(new Exception($"Dispose is not called how? viewer state : {_viewer?.IsClosed}")); } private void OnDifferenceViewerKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) { _hasFocus = (bool)e.NewValue; if (_hasFocus) { // Hook into WPFs thread message handling so we can handle WM_KEYDOWN messages ComponentDispatcher.ThreadFilterMessage += FilterThreadMessage; } else { // Unhook from WPF's thread message handling. ComponentDispatcher.ThreadFilterMessage -= FilterThreadMessage; } } public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { ThreadHelper.ThrowIfNotOnUIThread(); if (_hasFocus && _editorCommandTarget != null) { return _editorCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); } return (int)VisualStudio.OLE.Interop.Constants.OLECMDERR_E_UNKNOWNGROUP; } public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { ThreadHelper.ThrowIfNotOnUIThread(); if (_hasFocus && _editorCommandTarget != null) { return _editorCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); } return (int)VisualStudio.OLE.Interop.Constants.OLECMDERR_E_UNKNOWNGROUP; } /// <summary> /// Preprocess input (keyboard) messages in order to translate them to editor commands if they map. Since we are in a modal dialog /// we need to tell the shell to allow pre-translate during a modal loop as well as instructing it to use the editor keyboard scope /// even though, as far as the shell knows, there is no editor active. /// </summary> private void FilterThreadMessage(ref System.Windows.Interop.MSG msg, ref bool handled) { ThreadHelper.ThrowIfNotOnUIThread(); if (_filterKeys != null && msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) { var oleMSG = new VisualStudio.OLE.Interop.MSG() { hwnd = msg.hwnd, lParam = msg.lParam, wParam = msg.wParam, message = (uint)msg.message }; // Ask the shell to do the command mapping for us and without firing off the command. We need to check if this command is one of the // supported commands first before actually firing the command. if (ErrorHandler.Succeeded( _filterKeys.TranslateAcceleratorEx( [oleMSG], (uint)(__VSTRANSACCELEXFLAGS.VSTAEXF_NoFireCommand | __VSTRANSACCELEXFLAGS.VSTAEXF_UseTextEditorKBScope | __VSTRANSACCELEXFLAGS.VSTAEXF_AllowModalState), 0 /*scope count*/, [] /*scopes*/, out var cmdGuid, out var cmdId, out _, out _))) { // If the command is an allowed command then we fire it. if (IsCommandAllowed(cmdGuid, cmdId)) { var res = _filterKeys.TranslateAcceleratorEx( [oleMSG], (uint)(__VSTRANSACCELEXFLAGS.VSTAEXF_UseTextEditorKBScope | __VSTRANSACCELEXFLAGS.VSTAEXF_AllowModalState), 0 /*scope count*/, [] /*scopes*/, out _, out _, out _, out _); // We set handled to true if the command was executed, otherwise handled will be false handled = ErrorHandler.Succeeded(res); } } } } /// <summary> /// Determines if the command specified is a valid command in Diff preview. /// </summary> /// <param name="cmdGuid">The command set guid for the command</param> /// <param name="cmdId">The command id</param> /// <returns>true for the supported commands that are copy, selection, navigation. False otherwise</returns> private static bool IsCommandAllowed(Guid cmdGuid, uint cmdId) { if (cmdGuid == VsMenus.guidStandardCommandSet2K) { return cmdId == (uint)VSConstants.VSStd2KCmdID.COPY || cmdId == (uint)VSConstants.VSStd2KCmdID.SELECTALL || cmdId == (uint)VSConstants.VSStd2KCmdID.UP || cmdId == (uint)VSConstants.VSStd2KCmdID.UP_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.PAGEUP || cmdId == (uint)VSConstants.VSStd2KCmdID.PAGEUP_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.DOWN || cmdId == (uint)VSConstants.VSStd2KCmdID.DOWN_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.PAGEDN || cmdId == (uint)VSConstants.VSStd2KCmdID.PAGEDN_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.LEFT || cmdId == (uint)VSConstants.VSStd2KCmdID.LEFT_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.RIGHT || cmdId == (uint)VSConstants.VSStd2KCmdID.RIGHT_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.BOL || cmdId == (uint)VSConstants.VSStd2KCmdID.BOL_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.FIRSTCHAR || cmdId == (uint)VSConstants.VSStd2KCmdID.FIRSTCHAR_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.EOL || cmdId == (uint)VSConstants.VSStd2KCmdID.EOL_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.LASTCHAR || cmdId == (uint)VSConstants.VSStd2KCmdID.LASTCHAR_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.TOPLINE || cmdId == (uint)VSConstants.VSStd2KCmdID.TOPLINE_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.BOTTOMLINE || cmdId == (uint)VSConstants.VSStd2KCmdID.BOTTOMLINE_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.HOME || cmdId == (uint)VSConstants.VSStd2KCmdID.HOME_EXT || cmdId == (uint)VSConstants.VSStd2KCmdID.END || cmdId == (uint)VSConstants.VSStd2KCmdID.END_EXT; } return false; } }