You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
10 KiB
300 lines
10 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Windows.Controls; |
|
using System.Windows.Media; |
|
using ICSharpCode.AvalonEdit.CodeCompletion; |
|
using ICSharpCode.Core; |
|
using ICSharpCode.NRefactory; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
|
using ICSharpCode.SharpDevelop; |
|
using ICSharpCode.SharpDevelop.Parser; |
|
using ICSharpCode.SharpDevelop.Project; |
|
|
|
namespace ICSharpCode.AvalonEdit.AddIn |
|
{ |
|
/// <summary> |
|
/// Panel with two combo boxes. Used to quickly navigate to entities in the current file. |
|
/// </summary> |
|
public partial class QuickClassBrowser : UserControl |
|
{ |
|
/// <summary> |
|
/// ViewModel used for combobox items. |
|
/// </summary> |
|
class EntityItem : IComparable<EntityItem>, System.ComponentModel.INotifyPropertyChanged |
|
{ |
|
IUnresolvedEntity entity; |
|
ImageSource image; |
|
string text; |
|
|
|
public IUnresolvedEntity Entity { |
|
get { return entity; } |
|
} |
|
|
|
public EntityItem(IUnresolvedTypeDefinition typeDef, ICompilation compilation) |
|
{ |
|
this.IsInSamePart = true; |
|
this.entity = typeDef; |
|
var resolvedDefinition = typeDef.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)).GetDefinition(); |
|
if (resolvedDefinition != null) { |
|
var ambience = compilation.GetAmbience(); |
|
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList | ConversionFlags.ShowDeclaringType; |
|
this.text = ambience.ConvertEntity(resolvedDefinition); |
|
} else { |
|
this.text = typeDef.Name; |
|
} |
|
this.image = CompletionImage.GetImage(typeDef); |
|
} |
|
|
|
public EntityItem(IMember member, IAmbience ambience) |
|
{ |
|
this.IsInSamePart = true; |
|
this.entity = member.UnresolvedMember; |
|
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList | ConversionFlags.ShowParameterList | ConversionFlags.ShowParameterNames; |
|
text = ambience.ConvertEntity(member); |
|
image = CompletionImage.GetImage(member); |
|
} |
|
|
|
/// <summary> |
|
/// Text to display in combo box. |
|
/// </summary> |
|
public string Text { |
|
get { return text; } |
|
} |
|
|
|
/// <summary> |
|
/// Image to use in combox box |
|
/// </summary> |
|
public ImageSource Image { |
|
get { |
|
return image; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets/Sets whether the item is in the current file. |
|
/// </summary> |
|
/// <returns> |
|
/// <c>true</c>: item is in current file; |
|
/// <c>false</c>: item is in another part of the partial class |
|
/// </returns> |
|
public bool IsInSamePart { get; set; } |
|
|
|
public int CompareTo(EntityItem other) |
|
{ |
|
int r = this.Entity.EntityType.CompareTo(other.Entity.EntityType); |
|
if (r != 0) |
|
return r; |
|
r = string.Compare(text, other.text, StringComparison.OrdinalIgnoreCase); |
|
if (r != 0) |
|
return r; |
|
return string.Compare(text, other.text, StringComparison.Ordinal); |
|
} |
|
|
|
/// <summary> |
|
/// ToString override is necessary to support keyboard navigation in WPF |
|
/// </summary> |
|
public override string ToString() |
|
{ |
|
return text; |
|
} |
|
|
|
// I'm not sure if it actually was a leak or caused by something else, but I saw QCB.EntityItem being alive for longer |
|
// than it should when looking at the heap with WinDbg. |
|
// Maybe this was caused by http://support.microsoft.com/kb/938416/en-us, so I'm adding INotifyPropertyChanged to be sure. |
|
event System.ComponentModel.PropertyChangedEventHandler System.ComponentModel.INotifyPropertyChanged.PropertyChanged { |
|
add { } |
|
remove { } |
|
} |
|
} |
|
|
|
public QuickClassBrowser() |
|
{ |
|
InitializeComponent(); |
|
} |
|
|
|
/// <summary> |
|
/// Updates the list of available classes. |
|
/// This causes the classes combo box to lose its current selection, |
|
/// so the members combo box will be cleared. |
|
/// </summary> |
|
public void Update(IUnresolvedFile compilationUnit) |
|
{ |
|
runUpdateWhenDropDownClosed = true; |
|
runUpdateWhenDropDownClosedCU = compilationUnit; |
|
if (!IsDropDownOpen) |
|
ComboBox_DropDownClosed(null, null); |
|
} |
|
|
|
// The lists of items currently visible in the combo boxes. |
|
// These should never be null. |
|
List<EntityItem> classItems = new List<EntityItem>(); |
|
List<EntityItem> memberItems = new List<EntityItem>(); |
|
|
|
void DoUpdate(IUnresolvedFile unresolvedFile) |
|
{ |
|
classItems = new List<EntityItem>(); |
|
if (unresolvedFile != null) { |
|
ICompilation compilation = SD.ParserService.GetCompilationForFile(FileName.Create(unresolvedFile.FileName)); |
|
AddClasses(unresolvedFile.TopLevelTypeDefinitions, compilation); |
|
} |
|
classItems.Sort(); |
|
classComboBox.ItemsSource = classItems; |
|
} |
|
|
|
bool IsDropDownOpen { |
|
get { return classComboBox.IsDropDownOpen || membersComboBox.IsDropDownOpen; } |
|
} |
|
|
|
// Delayed execution - avoid changing combo boxes while the user is browsing the dropdown list. |
|
bool runUpdateWhenDropDownClosed; |
|
IUnresolvedFile runUpdateWhenDropDownClosedCU; |
|
bool runSelectItemWhenDropDownClosed; |
|
TextLocation runSelectItemWhenDropDownClosedLocation; |
|
|
|
void ComboBox_DropDownClosed(object sender, EventArgs e) |
|
{ |
|
if (runUpdateWhenDropDownClosed) { |
|
runUpdateWhenDropDownClosed = false; |
|
DoUpdate(runUpdateWhenDropDownClosedCU); |
|
runUpdateWhenDropDownClosedCU = null; |
|
} |
|
if (runSelectItemWhenDropDownClosed) { |
|
runSelectItemWhenDropDownClosed = false; |
|
DoSelectItem(runSelectItemWhenDropDownClosedLocation); |
|
} |
|
} |
|
|
|
void AddClasses(IEnumerable<IUnresolvedTypeDefinition> classes, ICompilation compilation) |
|
{ |
|
foreach (var c in classes) { |
|
if (c.IsSynthetic) |
|
continue; |
|
classItems.Add(new EntityItem(c, compilation)); |
|
AddClasses(c.NestedTypes, compilation); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Selects the class and member closest to the specified location. |
|
/// </summary> |
|
public void SelectItemAtCaretPosition(TextLocation location) |
|
{ |
|
runSelectItemWhenDropDownClosed = true; |
|
runSelectItemWhenDropDownClosedLocation = location; |
|
if (!IsDropDownOpen) |
|
ComboBox_DropDownClosed(null, null); |
|
} |
|
|
|
void DoSelectItem(TextLocation location) |
|
{ |
|
EntityItem matchInside = null; |
|
EntityItem nearestMatch = null; |
|
int nearestMatchDistance = int.MaxValue; |
|
foreach (EntityItem item in classItems) { |
|
if (item.IsInSamePart) { |
|
IUnresolvedTypeDefinition c = (IUnresolvedTypeDefinition)item.Entity; |
|
if (c.Region.IsInside(location.Line, location.Column)) { |
|
matchInside = item; |
|
// when there are multiple matches inside (nested classes), use the last one |
|
} else { |
|
// Not a perfect match? |
|
// Try to first the nearest match. We want the classes combo box to always |
|
// have a class selected if possible. |
|
int matchDistance = Math.Min(Math.Abs(location.Line - c.Region.BeginLine), |
|
Math.Abs(location.Line - c.Region.EndLine)); |
|
if (matchDistance < nearestMatchDistance) { |
|
nearestMatchDistance = matchDistance; |
|
nearestMatch = item; |
|
} |
|
} |
|
} |
|
} |
|
jumpOnSelectionChange = false; |
|
try { |
|
classComboBox.SelectedItem = matchInside ?? nearestMatch; |
|
// the SelectedItem setter will update the list of member items |
|
} finally { |
|
jumpOnSelectionChange = true; |
|
} |
|
matchInside = null; |
|
foreach (EntityItem item in memberItems) { |
|
if (item.IsInSamePart) { |
|
IUnresolvedMember member = (IUnresolvedMember)item.Entity; |
|
if (member.Region.IsInside(location.Line, location.Column) || member.BodyRegion.IsInside(location.Line, location.Column)) { |
|
matchInside = item; |
|
} |
|
} |
|
} |
|
jumpOnSelectionChange = false; |
|
try { |
|
membersComboBox.SelectedItem = matchInside; |
|
} finally { |
|
jumpOnSelectionChange = true; |
|
} |
|
} |
|
|
|
bool jumpOnSelectionChange = true; |
|
|
|
void classComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e) |
|
{ |
|
// The selected class was changed. |
|
// Update the list of member items to be the list of members of the current class. |
|
EntityItem item = classComboBox.SelectedItem as EntityItem; |
|
IUnresolvedTypeDefinition selectedClass = item != null ? item.Entity as IUnresolvedTypeDefinition : null; |
|
memberItems = new List<EntityItem>(); |
|
if (selectedClass != null) { |
|
ICompilation compilation = SD.ParserService.GetCompilationForFile(FileName.Create(selectedClass.UnresolvedFile.FileName)); |
|
var context = new SimpleTypeResolveContext(compilation.MainAssembly); |
|
ITypeDefinition compoundClass = selectedClass.Resolve(context).GetDefinition(); |
|
if (compoundClass != null) { |
|
var ambience = compilation.GetAmbience(); |
|
foreach (var member in compoundClass.Members) { |
|
if (member.IsSynthetic) |
|
continue; |
|
bool isInSamePart = string.Equals(member.UnresolvedMember.UnresolvedFile.FileName, selectedClass.UnresolvedFile.FileName, StringComparison.OrdinalIgnoreCase); |
|
memberItems.Add(new EntityItem(member, ambience) { IsInSamePart = isInSamePart }); |
|
} |
|
memberItems.Sort(); |
|
if (jumpOnSelectionChange) { |
|
SD.AnalyticsMonitor.TrackFeature(GetType(), "JumpToClass"); |
|
JumpTo(item, selectedClass.Region); |
|
} |
|
} |
|
} |
|
membersComboBox.ItemsSource = memberItems; |
|
} |
|
|
|
void membersComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e) |
|
{ |
|
EntityItem item = membersComboBox.SelectedItem as EntityItem; |
|
if (item != null) { |
|
IMember member = item.Entity as IMember; |
|
if (member != null && jumpOnSelectionChange) { |
|
SD.AnalyticsMonitor.TrackFeature(GetType(), "JumpToMember"); |
|
JumpTo(item, member.Region); |
|
} |
|
} |
|
} |
|
|
|
void JumpTo(EntityItem item, DomRegion region) |
|
{ |
|
if (region.IsEmpty) |
|
return; |
|
Action<int, int> jumpAction = this.JumpAction; |
|
if (item.IsInSamePart && jumpAction != null) { |
|
jumpAction(region.BeginLine, region.BeginColumn); |
|
} else { |
|
FileService.JumpToFilePosition(region.FileName, region.BeginLine, region.BeginColumn); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Action used for jumping to a position inside the current file. |
|
/// </summary> |
|
public Action<int, int> JumpAction { get; set; } |
|
} |
|
}
|
|
|