Debugger: Implement right-click options (and keybindings) in disassembly view

This commit is contained in:
Sour 2022-02-12 13:36:27 -05:00
parent c5000fb6b8
commit 68ab85dd90
11 changed files with 198 additions and 33 deletions

View file

@ -98,9 +98,6 @@ namespace Mesen.Config
Add(new() { Shortcut = DebuggerShortcut.SaveAsPng, KeyBinding = new(KeyModifiers.Control, Key.S) });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_EditInMemoryViewer, KeyBinding = new(Key.F1) });
Add(new() { Shortcut = DebuggerShortcut.MemoryViewer_ViewInDebugger, KeyBinding = new() });
Add(new() { Shortcut = DebuggerShortcut.OpenAssembler, KeyBinding = new(KeyModifiers.Control, Key.U) });
Add(new() { Shortcut = DebuggerShortcut.OpenDebugger, KeyBinding = new(KeyModifiers.Control, Key.D) });
Add(new() { Shortcut = DebuggerShortcut.OpenSpcDebugger, KeyBinding = new(KeyModifiers.Control, Key.F) });
@ -147,6 +144,9 @@ namespace Mesen.Config
Add(new() { Shortcut = DebuggerShortcut.FindOccurrences, KeyBinding = new(KeyModifiers.Control | KeyModifiers.Shift, Key.F) });
Add(new() { Shortcut = DebuggerShortcut.GoToProgramCounter, KeyBinding = new(KeyModifiers.Alt, Key.Multiply) });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_ViewInMemoryViewer, KeyBinding = new(Key.F1) });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_AddToWatch, KeyBinding = new() });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_GoToLocation, KeyBinding = new() });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_SetNextStatement, KeyBinding = new(KeyModifiers.Control | KeyModifiers.Shift, Key.F10) });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_EditSelectedCode, KeyBinding = new() });
Add(new() { Shortcut = DebuggerShortcut.CodeWindow_EditSourceFile, KeyBinding = new(Key.F4) });
@ -194,6 +194,7 @@ namespace Mesen.Config
Add(new() { Shortcut = DebuggerShortcut.MemoryViewer_Export, KeyBinding = new(KeyModifiers.Control, Key.S) });
Add(new() { Shortcut = DebuggerShortcut.MemoryViewer_ViewInCpuMemory, KeyBinding = new() });
Add(new() { Shortcut = DebuggerShortcut.MemoryViewer_ViewInMemoryType, KeyBinding = new() });
Add(new() { Shortcut = DebuggerShortcut.MemoryViewer_ViewInDebugger, KeyBinding = new() });
//Script Window
Add(new() { Shortcut = DebuggerShortcut.ScriptWindow_NewScript, KeyBinding = new(KeyModifiers.Control, Key.N) });
@ -226,7 +227,7 @@ namespace Mesen.Config
ZoomIn,
ZoomOut,
SaveAsPng,
CodeWindow_EditInMemoryViewer,
CodeWindow_ViewInMemoryViewer,
MemoryViewer_ViewInDebugger,
OpenAssembler,
OpenDebugger,
@ -311,6 +312,8 @@ namespace Mesen.Config
ScriptWindow_SaveScript,
ScriptWindow_RunScript,
ScriptWindow_StopScript,
CodeWindow_AddToWatch,
CodeWindow_GoToLocation,
}
public class DebuggerShortcutInfo : ViewModelBase

View file

@ -50,7 +50,8 @@ namespace Mesen.Debugger.Disassembly
return address;
}
public LocationInfo GetLocationInfo(string word, int lineIndex)
//TODO
/*public LocationInfo GetLocationInfo(string word, int lineIndex)
{
LocationInfo location = new LocationInfo();
@ -64,11 +65,11 @@ namespace Mesen.Debugger.Disassembly
}
//TODO
/*if(_provider is SymbolCodeDataProvider && _symbolProvider != null) {
if(_provider is SymbolCodeDataProvider && _symbolProvider != null) {
int rangeStart, rangeEnd;
GetSymbolByteRange(lineIndex, out rangeStart, out rangeEnd);
location.Symbol = _symbolProvider.GetSymbol(word, rangeStart, rangeEnd);
}*/
}
location.Label = LabelManager.GetLabel(word);
@ -110,7 +111,7 @@ namespace Mesen.Debugger.Disassembly
return location;
}
*/
//TODO
/*public Dictionary<string, string>? GetTooltipData(string word, int lineIndex)
{

View file

@ -29,7 +29,8 @@ namespace Mesen.Debugger.Disassembly
public class LocationInfo
{
public int Address;
public AddressInfo? RelAddress;
public AddressInfo? AbsAddress;
public CodeLabel? Label;
public SourceSymbol? Symbol;

View file

@ -17,6 +17,16 @@ namespace Mesen.Debugger.Labels
public CodeLabelFlags Flags { get; set; }
public UInt32 Length { get; set; } = 1;
public CodeLabel()
{
}
public CodeLabel(AddressInfo absAddress)
{
Address = (uint)absAddress.Address;
MemoryType = absAddress.Type;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();

View file

@ -12,29 +12,57 @@ namespace Mesen.Debugger.Utilities
{
public static class CodeTooltipHelper
{
public static DynamicTooltip? GetTooltip(CpuType cpuType, CodeSegmentInfo codeSegment)
public static LocationInfo? GetLocation(CpuType cpuType, CodeSegmentInfo codeSegment)
{
int address = -1;
CodeLabel? label = null;
if(codeSegment.Type == CodeSegmentType.Address || codeSegment.Type == CodeSegmentType.EffectiveAddress) {
string addressText = codeSegment.Text.Trim(' ', '[', ']', '$');
int.TryParse(addressText, System.Globalization.NumberStyles.HexNumber, null, out address);
if(address >= 0) {
return GetCodeAddressTooltip(cpuType, address, label);
AddressInfo relAddress = new AddressInfo() { Address = address, Type = cpuType.ToMemoryType() };
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
return new LocationInfo {
RelAddress = relAddress,
AbsAddress = absAddress.Address >= 0 ? absAddress : null,
};
}
} else if(codeSegment.Type == CodeSegmentType.Label || codeSegment.Type == CodeSegmentType.LabelDefinition) {
string labelText = codeSegment.Text.Trim(' ', ',', ':', ']', '[');
label = LabelManager.GetLabel(labelText);
CodeLabel? label = LabelManager.GetLabel(labelText);
if(label != null) {
address = label.GetRelativeAddress(cpuType).Address;
}
if(address >= 0) {
return GetCodeAddressTooltip(cpuType, address, label);
return new LocationInfo {
Label = label,
RelAddress = label.GetRelativeAddress(cpuType),
AbsAddress = label.GetAbsoluteAddress()
};
}
} else if(codeSegment.Type == CodeSegmentType.MarginAddress) {
string addressText = codeSegment.Text.Trim(' ', '[', ']', '$');
if(int.TryParse(addressText, System.Globalization.NumberStyles.HexNumber, null, out address) && address >= 0) {
return GetMarginAddressTooltip(cpuType, codeSegment, address);
AddressInfo relAddress = new AddressInfo() { Address = address, Type = cpuType.ToMemoryType() };
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
return new LocationInfo {
RelAddress = relAddress,
AbsAddress = absAddress.Address >= 0 ? absAddress : null,
};
}
}
return null;
}
public static DynamicTooltip? GetTooltip(CpuType cpuType, CodeSegmentInfo codeSegment)
{
LocationInfo? codeLoc = GetLocation(cpuType, codeSegment);
if(codeLoc != null) {
if(codeSegment.Type == CodeSegmentType.Address || codeSegment.Type == CodeSegmentType.EffectiveAddress || codeSegment.Type == CodeSegmentType.Label || codeSegment.Type == CodeSegmentType.LabelDefinition) {
if(codeLoc.RelAddress?.Address >= 0) {
return GetCodeAddressTooltip(cpuType, codeLoc.RelAddress.Value.Address, codeLoc.Label);
}
} else if(codeSegment.Type == CodeSegmentType.MarginAddress) {
if(codeLoc.RelAddress?.Address >= 0) {
return GetMarginAddressTooltip(cpuType, codeSegment, codeLoc.RelAddress.Value.Address);
}
}
}

View file

@ -37,7 +37,10 @@ namespace Mesen.Debugger.Utilities
string label = ResourceHelper.GetEnumText(ActionType);
if(HintText != null) {
label += " (" + HintText() + ")";
string hint = HintText();
if(!string.IsNullOrWhiteSpace(hint)) {
label += " (" + hint + ")";
}
}
return label;
}

View file

@ -124,13 +124,15 @@ namespace Mesen.Debugger.ViewModels
//DebuggerShortcut.CodeWindow_SetNextStatement,
DebuggerShortcut.CodeWindow_EditSelectedCode,
//DebuggerShortcut.CodeWindow_EditSourceFile,
DebuggerShortcut.CodeWindow_EditInMemoryViewer,
DebuggerShortcut.CodeWindow_AddToWatch,
DebuggerShortcut.CodeWindow_GoToLocation,
DebuggerShortcut.CodeWindow_ViewInMemoryViewer,
DebuggerShortcut.CodeWindow_EditLabel,
//DebuggerShortcut.CodeWindow_NavigateBack,
//DebuggerShortcut.CodeWindow_NavigateForward,
DebuggerShortcut.CodeWindow_ToggleBreakpoint,
DebuggerShortcut.CodeWindow_DisableEnableBreakpoint,
DebuggerShortcut.CodeWindow_SwitchView,
//DebuggerShortcut.CodeWindow_SwitchView,
//DebuggerShortcut.FunctionList_EditLabel,
//DebuggerShortcut.FunctionList_AddBreakpoint,
//DebuggerShortcut.FunctionList_FindOccurrences,

View file

@ -130,7 +130,7 @@ namespace Mesen.Debugger.ViewModels
}
}
public void SetSelectedRow(int address)
public void SetSelectedRow(int address, bool scrollToRow = false)
{
SelectionStart = address;
SelectionEnd = address;
@ -138,6 +138,10 @@ namespace Mesen.Debugger.ViewModels
SelectionAnchor = address;
InvalidateVisual();
if(scrollToRow) {
ScrollToAddress((uint)address);
}
}
public void MoveCursor(int rowOffset, bool extendSelection)

View file

@ -38,6 +38,7 @@
RowClicked="Diassembly_RowClicked"
PointerWheelChanged="Disassembly_PointerWheelChanged"
CodePointerMoved="Disassembly_CodePointerMoved"
PointerLeave="Disassembly_PointerLeave"
/>
</DockPanel>
</UserControl>

View file

@ -5,6 +5,8 @@ using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Mesen.Config;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Disassembly;
using Mesen.Debugger.Labels;
using Mesen.Debugger.Utilities;
using Mesen.Debugger.ViewModels;
using Mesen.Debugger.Windows;
@ -16,6 +18,8 @@ namespace Mesen.Debugger.Views
public class DisassemblyView : UserControl
{
private DisassemblyViewModel Model => (DisassemblyViewModel)DataContext!;
private LocationInfo? _mouseOverCodeLocation;
private LocationInfo? _contextMenuLocation;
static DisassemblyView()
{
@ -54,23 +58,90 @@ namespace Mesen.Debugger.Views
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ToggleBreakpoint,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.CodeWindow_ToggleBreakpoint),
HintText = () => GetHint(ActionLocation),
IsEnabled = () => ActionLocation.RelAddress != null || ActionLocation.AbsAddress != null,
OnClick = () => {
if(ActionLocation.AbsAddress != null) {
BreakpointManager.ToggleBreakpoint(ActionLocation.AbsAddress.Value, Model.DataProvider.CpuType);
} else if(ActionLocation.RelAddress != null) {
BreakpointManager.ToggleBreakpoint(ActionLocation.RelAddress.Value, Model.DataProvider.CpuType);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.AddWatch,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.CodeWindow_AddToWatch),
HintText = () => GetHint(ActionLocation),
IsEnabled = () => ActionLocation.Label != null || ActionLocation.RelAddress != null,
OnClick = () => {
if(ActionLocation.Label != null) {
WatchManager.GetWatchManager(Model.DataProvider.CpuType).AddWatch("[" + ActionLocation.Label.Label + "]");
} else if(ActionLocation.RelAddress != null) {
WatchManager.GetWatchManager(Model.DataProvider.CpuType).AddWatch("[$" + ActionLocation.RelAddress.Value.Address.ToString(GetFormatString()) + "]");
}
}
},
new ContextMenuAction() {
ActionType = ActionType.EditLabel,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.CodeWindow_EditLabel),
HintText = () => GetHint(ActionLocation),
IsEnabled = () => ActionLocation.Label != null || ActionLocation.AbsAddress != null,
OnClick = () => {
CodeLabel? label = ActionLocation.Label ?? (ActionLocation.AbsAddress.HasValue ? LabelManager.GetLabel(ActionLocation.AbsAddress.Value) : null);
if(label != null) {
LabelEditWindow.EditLabel(this, label);
} else if(ActionLocation.AbsAddress != null) {
LabelEditWindow.EditLabel(this, new CodeLabel(ActionLocation.AbsAddress.Value));
}
}
},
new ContextMenuAction() {
ActionType = ActionType.ViewInMemoryViewer,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.CodeWindow_ViewInMemoryViewer),
HintText = () => GetHint(ActionLocation),
IsEnabled = () => ActionLocation.Label != null || ActionLocation.RelAddress != null || ActionLocation.AbsAddress != null,
OnClick = () => {
if(ActionLocation.RelAddress != null) {
MemoryToolsWindow.ShowInMemoryTools(ActionLocation.RelAddress.Value.Type, ActionLocation.RelAddress.Value.Address);
} else if(ActionLocation.AbsAddress != null) {
MemoryToolsWindow.ShowInMemoryTools(ActionLocation.AbsAddress.Value.Type, ActionLocation.AbsAddress.Value.Address);
}
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.GoToLocation,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.CodeWindow_GoToLocation),
HintText = () => GetHint(ActionLocation),
IsEnabled = () => ActionLocation.Label != null || ActionLocation.RelAddress != null,
OnClick = () => {
if(ActionLocation.RelAddress != null) {
Model.SetSelectedRow(ActionLocation.RelAddress.Value.Address, true);
}
}
},
});
}
private string GetFormatString()
{
return Model.DataProvider.CpuType.ToMemoryType().GetFormatString();
}
private string GetHint(LocationInfo? codeLoc)
{
if(codeLoc == null) {
return string.Empty;
}
if(codeLoc?.RelAddress != null) {
return "$" + codeLoc.RelAddress.Value.Address.ToString(GetFormatString());
}
return string.Empty;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
@ -86,6 +157,31 @@ namespace Mesen.Debugger.Views
Model.ViewerActive = false;
}
private LocationInfo ActionLocation
{
get
{
if(ContextMenu?.IsOpen == true && _contextMenuLocation != null) {
return _contextMenuLocation;
} else if(_mouseOverCodeLocation != null) {
return _mouseOverCodeLocation;
}
return GetSelectedRowLocation();
}
}
private LocationInfo GetSelectedRowLocation()
{
CpuType cpuType = Model.DataProvider.CpuType;
AddressInfo relAddress = new AddressInfo() {
Address = Model.SelectedRowAddress,
Type = cpuType.ToMemoryType()
};
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
return new LocationInfo() { RelAddress = relAddress, AbsAddress = absAddress };
}
public void Diassembly_RowClicked(DisassemblyViewer sender, RowClickedEventArgs e)
{
if(e.Properties.IsLeftButtonPressed) {
@ -95,7 +191,6 @@ namespace Mesen.Debugger.Views
Address = e.CodeLineData.Address,
Type = cpuType.ToMemoryType()
};
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
BreakpointManager.ToggleBreakpoint(absAddress.Address < 0 ? relAddress : absAddress, cpuType);
} else {
@ -106,22 +201,37 @@ namespace Mesen.Debugger.Views
}
}
} else if(e.Properties.IsRightButtonPressed) {
_contextMenuLocation = _mouseOverCodeLocation;
if(e.CodeLineData.Address < Model.SelectionStart || e.CodeLineData.Address > Model.SelectionEnd) {
Model.SetSelectedRow(e.CodeLineData.Address);
}
}
}
public void Disassembly_PointerLeave(object? sender, PointerEventArgs e)
{
_mouseOverCodeLocation = null;
}
public void Disassembly_CodePointerMoved(DisassemblyViewer sender, CodePointerMovedEventArgs e)
{
DynamicTooltip? tooltip;
ICodeDataProvider? dp = Model.DataProvider;
if(e.CodeSegment != null && dp != null && (tooltip = CodeTooltipHelper.GetTooltip(dp.CpuType, e.CodeSegment)) != null) {
ToolTip.SetTip(this, tooltip);
ToolTip.SetHorizontalOffset(this, 14);
ToolTip.SetHorizontalOffset(this, 15);
ToolTip.SetIsOpen(this, true);
} else {
DynamicTooltip? tooltip = null;
ICodeDataProvider dp = Model.DataProvider;
if(e.CodeSegment != null) {
_mouseOverCodeLocation = CodeTooltipHelper.GetLocation(dp.CpuType, e.CodeSegment);
tooltip = CodeTooltipHelper.GetTooltip(dp.CpuType, e.CodeSegment);
if(tooltip != null) {
ToolTip.SetTip(this, tooltip);
ToolTip.SetHorizontalOffset(this, 14);
ToolTip.SetHorizontalOffset(this, 15);
ToolTip.SetIsOpen(this, true);
}
}
if(tooltip == null) {
ToolTip.SetIsOpen(this, false);
ToolTip.SetTip(this, null);
}

View file

@ -1860,8 +1860,6 @@ x == [$150] || y == [10]
<Value ID="ZoomIn">Zoom In</Value>
<Value ID="ZoomOut">Zoom Out</Value>
<Value ID="SaveAsPng">Save As PNG</Value>
<Value ID="CodeWindow_EditInMemoryViewer">Edit in Memory Viewer</Value>
<Value ID="MemoryViewer_ViewInDebugger">View in Debugger</Value>
<Value ID="OpenAssembler">Open Assembler</Value>
<Value ID="OpenDebugger">Open Debugger</Value>
<Value ID="OpenSpcDebugger">Open SPC Debugger</Value>
@ -1899,6 +1897,9 @@ x == [$150] || y == [10]
<Value ID="BreakOn">Break On...</Value>
<Value ID="FindOccurrences">Find Occurrences</Value>
<Value ID="GoToProgramCounter">Go To Program Counter</Value>
<Value ID="CodeWindow_ViewInMemoryViewer">Code Window: View in Memory Viewer</Value>
<Value ID="CodeWindow_AddToWatch">Code Window: Add to Watch</Value>
<Value ID="CodeWindow_GoToLocation">Code Window: Go to Location</Value>
<Value ID="CodeWindow_SetNextStatement">Code Window: Set Next Statement</Value>
<Value ID="CodeWindow_EditSelectedCode">Code Window: Edit Selected Code</Value>
<Value ID="CodeWindow_EditSourceFile">Code Window: Edit Source File (Source View)</Value>
@ -1939,6 +1940,7 @@ x == [$150] || y == [10]
<Value ID="MemoryViewer_Export">Export</Value>
<Value ID="MemoryViewer_ViewInCpuMemory">View in CPU/PPU Memory</Value>
<Value ID="MemoryViewer_ViewInMemoryType">View in [memory type]</Value>
<Value ID="MemoryViewer_ViewInDebugger">View in Debugger</Value>
<Value ID="ScriptWindow_OpenScript">Open Script</Value>
<Value ID="ScriptWindow_SaveScript">Save Script</Value>
<Value ID="ScriptWindow_RunScript">Run Script</Value>