|
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Wpf;
using Microsoft.Internal.VisualStudio.Shell;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Imaging.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Extensions;
using Microsoft.VisualStudio.LanguageServices.Utilities;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.NavigationBar;
internal class NavigationBarClient :
IVsDropdownBarClient,
IVsDropdownBarClient3,
IVsDropdownBarClient4,
IVsDropdownBarClientEx,
IVsCoTaskMemFreeMyStrings,
INavigationBarPresenter
{
private readonly IVsDropdownBarManager _manager;
private readonly VsCodeWindowViewTracker _codeWindowViewTracker;
private readonly VisualStudioWorkspaceImpl _workspace;
private readonly IVsImageService2 _imageService;
private IVsDropdownBar? _dropdownBar;
private IList<NavigationBarProjectItem> _projectItems;
private IList<NavigationBarItem> _currentTypeItems;
public NavigationBarClient(
IVsDropdownBarManager manager,
IVsCodeWindow codeWindow,
IServiceProvider serviceProvider,
VisualStudioWorkspaceImpl workspace)
{
_manager = manager;
_codeWindowViewTracker = new VsCodeWindowViewTracker(
codeWindow,
serviceProvider.GetMefService<IThreadingContext>(),
serviceProvider.GetMefService<IVsEditorAdaptersFactoryService>());
_codeWindowViewTracker.CaretMovedOrActiveViewChanged += OnCaretMovedOrActiveViewChanged;
_workspace = workspace;
_imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService));
_projectItems = [];
_currentTypeItems = [];
}
private NavigationBarItem? GetCurrentTypeItem()
{
if (_dropdownBar == null)
return null;
_dropdownBar.GetCurrentSelection(1, out var currentTypeIndex);
return currentTypeIndex >= 0
? _currentTypeItems[currentTypeIndex]
: null;
}
private NavigationBarItem GetItem(int combo, int index)
{
switch (combo)
{
case 0:
return _projectItems[index];
case 1:
return _currentTypeItems[index];
case 2:
var item = GetCurrentTypeItem();
Contract.ThrowIfNull(item, "We shouldn't be getting requests for member items if we had no type selected.");
return item.ChildItems[index];
default:
throw new ArgumentException();
}
}
int IVsDropdownBarClient.GetComboAttributes(int iCombo, out uint pcEntries, out uint puEntryType, out IntPtr phImageList)
{
puEntryType = (uint)(DROPDOWNENTRYTYPE.ENTRY_TEXT | DROPDOWNENTRYTYPE.ENTRY_ATTR | DROPDOWNENTRYTYPE.ENTRY_IMAGE);
// We no longer need to return an HIMAGELIST, we now use IVsDropdownBarClient4.GetEntryImage which uses monikers directly.
phImageList = IntPtr.Zero;
switch (iCombo)
{
case 0:
pcEntries = (uint)_projectItems.Count;
break;
case 1:
pcEntries = (uint)_currentTypeItems.Count;
break;
case 2:
var currentTypeItem = GetCurrentTypeItem();
pcEntries = currentTypeItem != null
? (uint)currentTypeItem.ChildItems.Length
: 0;
break;
default:
pcEntries = 0;
return VSConstants.E_INVALIDARG;
}
return VSConstants.S_OK;
}
int IVsDropdownBarClient.GetComboTipText(int iCombo, out string? pbstrText)
{
Contract.ThrowIfNull(_dropdownBar, "The dropdown is asking us for things, so we should have a reference to it.");
var selectedItemPreviewText = string.Empty;
if (_dropdownBar.GetCurrentSelection(iCombo, out var selectionIndex) == VSConstants.S_OK && selectionIndex >= 0)
{
selectedItemPreviewText = GetItem(iCombo, selectionIndex).Text;
}
switch (iCombo)
{
case 0:
var keybindingString = KeyBindingHelper.GetGlobalKeyBinding(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.MoveToDropdownBar);
if (!string.IsNullOrWhiteSpace(keybindingString))
{
pbstrText = string.Format(ServicesVSResources.Project_colon_0_1_Use_the_dropdown_to_view_and_switch_to_other_projects_this_file_may_belong_to, selectedItemPreviewText, keybindingString);
}
else
{
pbstrText = string.Format(ServicesVSResources.Project_colon_0_Use_the_dropdown_to_view_and_switch_to_other_projects_this_file_may_belong_to, selectedItemPreviewText);
}
return VSConstants.S_OK;
case 1:
case 2:
pbstrText = string.Format(ServicesVSResources._0_Use_the_dropdown_to_view_and_navigate_to_other_items_in_this_file, selectedItemPreviewText);
return VSConstants.S_OK;
default:
pbstrText = null;
return VSConstants.E_INVALIDARG;
}
}
int IVsDropdownBarClient.GetEntryAttributes(int iCombo, int iIndex, out uint pAttr)
{
var attributes = DROPDOWNFONTATTR.FONTATTR_PLAIN;
var item = GetItem(iCombo, iIndex);
if (item.Grayed)
{
attributes |= DROPDOWNFONTATTR.FONTATTR_GRAY;
}
if (item.Bolded)
{
attributes |= DROPDOWNFONTATTR.FONTATTR_BOLD;
}
pAttr = (uint)attributes;
return VSConstants.S_OK;
}
int IVsDropdownBarClient.GetEntryImage(int iCombo, int iIndex, out int piImageIndex)
{
// This class implements IVsDropdownBarClient4 and expects IVsDropdownBarClient4.GetEntryImage() to be called instead
piImageIndex = -1;
return VSConstants.E_UNEXPECTED;
}
int IVsDropdownBarClient.GetEntryText(int iCombo, int iIndex, out string ppszText)
{
ppszText = GetItem(iCombo, iIndex).Text;
return VSConstants.S_OK;
}
int IVsDropdownBarClient.OnComboGetFocus(int iCombo)
{
return VSConstants.S_OK;
}
int IVsDropdownBarClient.OnItemChosen(int iCombo, int iIndex)
{
Contract.ThrowIfNull(_dropdownBar, "The dropdown is asking us for things, so we should have a reference to it.");
int selection;
// If we chose an item for the type drop-down, then refresh the member dropdown
if (iCombo == (int)NavigationBarDropdownKind.Type)
{
_dropdownBar.GetCurrentSelection((int)NavigationBarDropdownKind.Member, out selection);
_dropdownBar.RefreshCombo((int)NavigationBarDropdownKind.Member, selection);
}
_dropdownBar.GetCurrentSelection(iCombo, out selection);
if (selection >= 0)
{
var item = GetItem(iCombo, selection);
ItemSelected?.Invoke(this, new NavigationBarItemSelectedEventArgs(item));
}
return VSConstants.S_OK;
}
int IVsDropdownBarClient.OnItemSelected(int iCombo, int iIndex)
=> VSConstants.S_OK;
int IVsDropdownBarClient.SetDropdownBar(IVsDropdownBar pDropdownBar)
{
_dropdownBar = pDropdownBar;
return VSConstants.S_OK;
}
int IVsDropdownBarClient3.GetComboWidth(int iCombo, out int piWidthPercent)
{
piWidthPercent = 100;
return VSConstants.S_OK;
}
int IVsDropdownBarClient3.GetAutomationProperties(int iCombo, out string? pbstrName, out string? pbstrId)
{
switch (iCombo)
{
case 0:
pbstrName = NavigationBarAutomationStrings.ProjectDropdownName;
pbstrId = NavigationBarAutomationStrings.ProjectDropdownId;
return VSConstants.S_OK;
case 1:
pbstrName = NavigationBarAutomationStrings.TypeDropdownName;
pbstrId = NavigationBarAutomationStrings.TypeDropdownId;
return VSConstants.S_OK;
case 2:
pbstrName = NavigationBarAutomationStrings.MemberDropdownName;
pbstrId = NavigationBarAutomationStrings.MemberDropdownId;
return VSConstants.S_OK;
default:
pbstrName = null;
pbstrId = null;
return VSConstants.E_INVALIDARG;
}
}
int IVsDropdownBarClient3.GetEntryImage(int iCombo, int iIndex, out int piImageIndex, out IntPtr phImageList)
{
// This class implements IVsDropdownBarClient4 and expects IVsDropdownBarClient4.GetEntryImage() to be called instead
phImageList = IntPtr.Zero;
piImageIndex = -1;
return VSConstants.E_UNEXPECTED;
}
ImageMoniker IVsDropdownBarClient4.GetEntryImage(int iCombo, int iIndex)
{
var item = GetItem(iCombo, iIndex);
if (item is NavigationBarProjectItem projectItem)
{
if (_workspace.TryGetHierarchy(projectItem.DocumentId.ProjectId, out var hierarchy))
{
return _imageService.GetImageMonikerForHierarchyItem(hierarchy, VSConstants.VSITEMID_ROOT, (int)__VSHIERARCHYIMAGEASPECT.HIA_Icon);
}
}
return item.Glyph.GetImageMoniker();
}
int IVsDropdownBarClientEx.GetEntryIndent(int iCombo, int iIndex, out uint pIndent)
{
pIndent = (uint)GetItem(iCombo, iIndex).Indent;
return VSConstants.S_OK;
}
void INavigationBarPresenter.Disconnect()
{
_codeWindowViewTracker.CaretMovedOrActiveViewChanged -= OnCaretMovedOrActiveViewChanged;
_codeWindowViewTracker.Dispose();
_manager.RemoveDropdownBar();
}
void INavigationBarPresenter.PresentItems(
ImmutableArray<NavigationBarProjectItem> projects,
NavigationBarProjectItem? selectedProject,
ImmutableArray<NavigationBarItem> types,
NavigationBarItem? selectedType,
NavigationBarItem? selectedMember)
{
_projectItems = projects;
_currentTypeItems = types;
// It's possible we're presenting items before the dropdown bar has been initialized.
if (_dropdownBar == null)
{
return;
}
var projectIndex = selectedProject != null ? _projectItems.IndexOf(selectedProject) : -1;
var typeIndex = selectedType != null ? _currentTypeItems.IndexOf(selectedType) : -1;
var memberIndex = selectedType != null && selectedMember != null ? selectedType.ChildItems.IndexOf(selectedMember) : -1;
_dropdownBar.RefreshCombo((int)NavigationBarDropdownKind.Project, projectIndex);
_dropdownBar.RefreshCombo((int)NavigationBarDropdownKind.Type, typeIndex);
_dropdownBar.RefreshCombo((int)NavigationBarDropdownKind.Member, memberIndex);
}
private void OnCaretMovedOrActiveViewChanged(object? sender, EventArgs e)
{
CaretMovedOrActiveViewChanged?.Invoke(this, e);
}
public event EventHandler<EventArgs>? CaretMovedOrActiveViewChanged;
public event EventHandler<NavigationBarItemSelectedEventArgs>? ItemSelected;
ITextView INavigationBarPresenter.TryGetCurrentView()
{
return _codeWindowViewTracker.GetActiveView();
}
}
|