Debugger: Added label/symbol/address tooltips
This commit is contained in:
parent
e4868e3fa4
commit
c000b1fcae
8 changed files with 206 additions and 8 deletions
|
@ -1,16 +1,20 @@
|
|||
using Mesen.GUI.Debugger.Controls;
|
||||
using Mesen.GUI.Debugger.Integration;
|
||||
using Mesen.GUI.Debugger.Labels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Mesen.GUI.Debugger.Integration.DbgImporter;
|
||||
|
||||
namespace Mesen.GUI.Debugger.Code
|
||||
{
|
||||
public class CpuDisassemblyManager : IDisassemblyManager
|
||||
{
|
||||
protected ICodeDataProvider _provider;
|
||||
private DbgImporter _symbolProvider;
|
||||
|
||||
public ICodeDataProvider Provider { get { return this._provider; } }
|
||||
|
||||
public virtual SnesMemoryType RelativeMemoryType { get { return SnesMemoryType.CpuMemory; } }
|
||||
|
@ -20,6 +24,7 @@ namespace Mesen.GUI.Debugger.Code
|
|||
|
||||
public virtual void RefreshCode(DbgImporter symbolProvider, DbgImporter.FileInfo file)
|
||||
{
|
||||
_symbolProvider = symbolProvider;
|
||||
if(file == null) {
|
||||
this._provider = new CodeDataProvider(CpuType.Cpu);
|
||||
} else {
|
||||
|
@ -55,5 +60,124 @@ namespace Mesen.GUI.Debugger.Code
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetTooltipData(string word, int lineIndex)
|
||||
{
|
||||
int? arrayIndex = null;
|
||||
int arraySeparatorIndex = word.IndexOf("+");
|
||||
if(arraySeparatorIndex >= 0) {
|
||||
int index;
|
||||
if(int.TryParse(word.Substring(arraySeparatorIndex + 1), out index)) {
|
||||
arrayIndex = index;
|
||||
}
|
||||
word = word.Substring(0, arraySeparatorIndex);
|
||||
}
|
||||
|
||||
if(_provider is DbgCodeDataProvider && _symbolProvider != null) {
|
||||
int rangeStart, rangeEnd;
|
||||
GetSymbolByteRange(lineIndex, out rangeStart, out rangeEnd);
|
||||
SymbolInfo symbol = _symbolProvider.GetSymbol(word, rangeStart, rangeEnd);
|
||||
if(symbol != null) {
|
||||
AddressInfo? symbolAddress = _symbolProvider.GetSymbolAddressInfo(symbol);
|
||||
|
||||
if(symbolAddress != null && symbolAddress.Value.Address >= 0) {
|
||||
int relativeAddress = DebugApi.GetRelativeAddress(symbolAddress.Value).Address;
|
||||
byte byteValue = relativeAddress >= 0 ? DebugApi.GetMemoryValue(this.RelativeMemoryType, (UInt32)relativeAddress) : (byte)0;
|
||||
UInt16 wordValue = relativeAddress >= 0 ? (UInt16)(byteValue | (DebugApi.GetMemoryValue(this.RelativeMemoryType, (UInt32)relativeAddress + 1) << 8)) : (UInt16)0;
|
||||
|
||||
var values = new Dictionary<string, string>() {
|
||||
{ "Symbol", symbol.Name + (arrayIndex != null ? $"+{arrayIndex.Value}" : "") }
|
||||
};
|
||||
|
||||
if(relativeAddress >= 0) {
|
||||
values["CPU Address"] = "$" + relativeAddress.ToString("X4");
|
||||
} else {
|
||||
values["CPU Address"] = "<out of scope>";
|
||||
}
|
||||
|
||||
if(symbolAddress.Value.Type == SnesMemoryType.PrgRom) {
|
||||
values["PRG Offset"] = "$" + (symbolAddress.Value.Address + (arrayIndex ?? 0)).ToString("X4");
|
||||
}
|
||||
|
||||
values["Value"] = (relativeAddress >= 0 ? $"${byteValue.ToString("X2")} (byte){Environment.NewLine}${wordValue.ToString("X4")} (word)" : "n/a");
|
||||
return values;
|
||||
} else {
|
||||
return new Dictionary<string, string>() {
|
||||
{ "Symbol", symbol.Name },
|
||||
{ "Constant", symbol.Address.HasValue ? ("$" + symbol.Address.Value.ToString("X2")) : "<unknown>" }
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CodeLabel label = LabelManager.GetLabel(word);
|
||||
if(label != null) {
|
||||
AddressInfo absAddress = label.GetAbsoluteAddress();
|
||||
int relativeAddress;
|
||||
if(absAddress.Type == SnesMemoryType.Register) {
|
||||
relativeAddress = absAddress.Address;
|
||||
} else {
|
||||
relativeAddress = label.GetRelativeAddress().Address;
|
||||
}
|
||||
|
||||
byte byteValue = relativeAddress >= 0 ? DebugApi.GetMemoryValue(this.RelativeMemoryType, (UInt32)relativeAddress) : (byte)0;
|
||||
UInt16 wordValue = relativeAddress >= 0 ? (UInt16)(byteValue | (DebugApi.GetMemoryValue(this.RelativeMemoryType, (UInt32)relativeAddress + 1) << 8)) : (UInt16)0;
|
||||
|
||||
var values = new Dictionary<string, string>() {
|
||||
{ "Label", label.Label + (arrayIndex != null ? $"+{arrayIndex.Value}" : "") },
|
||||
{ "Address", (relativeAddress >= 0 ? "$" + relativeAddress.ToString("X4") : "n/a") },
|
||||
{ "Value", (relativeAddress >= 0 ? $"${byteValue.ToString("X2")} (byte){Environment.NewLine}${wordValue.ToString("X4")} (word)" : "n/a") },
|
||||
};
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(label.Comment)) {
|
||||
values["Comment"] = label.Comment;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
System.Globalization.NumberStyles style = System.Globalization.NumberStyles.None;
|
||||
if(word.StartsWith("$")) {
|
||||
style = System.Globalization.NumberStyles.HexNumber;
|
||||
word = word.Replace("$", "");
|
||||
}
|
||||
|
||||
uint address;
|
||||
if(UInt32.TryParse(word, style, null, out address)) {
|
||||
byte byteValue = DebugApi.GetMemoryValue(this.RelativeMemoryType, address);
|
||||
UInt16 wordValue = (UInt16)(byteValue | (DebugApi.GetMemoryValue(this.RelativeMemoryType, address + 1) << 8));
|
||||
return new Dictionary<string, string>() {
|
||||
{ "Address", "$" + address.ToString("X4") },
|
||||
{ "Value", $"${byteValue.ToString("X2")} (byte){Environment.NewLine}${wordValue.ToString("X4")} (word)" }
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void GetSymbolByteRange(int lineIndex, out int rangeStart, out int rangeEnd)
|
||||
{
|
||||
int lineCount = _provider.GetLineCount();
|
||||
while(lineIndex < lineCount - 2 && _provider.GetCodeLineData(lineIndex).AbsoluteAddress < 0) {
|
||||
//Find the address of the next line with an address
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
rangeStart = _provider.GetCodeLineData(lineIndex).AbsoluteAddress;
|
||||
if(rangeStart >= 0) {
|
||||
while(lineIndex < lineCount - 2 && _provider.GetCodeLineData(lineIndex + 1).AbsoluteAddress < 0) {
|
||||
//Find the next line with an address
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
rangeEnd = _provider.GetCodeLineData(lineIndex + 1).AbsoluteAddress - 1;
|
||||
|
||||
if(rangeStart < rangeEnd && (rangeEnd - rangeStart) < 500) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rangeStart = 0;
|
||||
rangeEnd = Int32.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ namespace Mesen.GUI.Debugger.Code
|
|||
data.Comment = trimmed.Substring(commentIndex);
|
||||
data.Text = trimmed.Substring(0, commentIndex).TrimEnd();
|
||||
} else {
|
||||
data.Comment = "";
|
||||
data.Text = trimmed;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,5 +19,7 @@ namespace Mesen.GUI.Debugger.Code
|
|||
void RefreshCode(DbgImporter symbolProvider, DbgImporter.FileInfo file);
|
||||
void ToggleBreakpoint(int lineIndex);
|
||||
void EnableDisableBreakpoint(int lineIndex);
|
||||
|
||||
Dictionary<string, string> GetTooltipData(string word, int lineIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Mesen.GUI.Debugger
|
|||
}
|
||||
return "[" + label.Label + "]";
|
||||
} else {
|
||||
return "[" + EffectiveAddress.ToString(format) + "]";
|
||||
return "[$" + EffectiveAddress.ToString(format) + "]";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
this.ctrlCode.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.ctrlCode.HideSelection = false;
|
||||
this.ctrlCode.Location = new System.Drawing.Point(0, 27);
|
||||
this.ctrlCode.Margin = new System.Windows.Forms.Padding(0, 0, 0, 0);
|
||||
this.ctrlCode.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.ctrlCode.Name = "ctrlCode";
|
||||
this.ctrlCode.ShowCompactPrgAddresses = false;
|
||||
this.ctrlCode.ShowContentNotes = false;
|
||||
|
@ -67,7 +67,9 @@
|
|||
this.ctrlCode.ShowSingleLineLineNumberNotes = false;
|
||||
this.ctrlCode.Size = new System.Drawing.Size(465, 371);
|
||||
this.ctrlCode.TabIndex = 0;
|
||||
this.ctrlCode.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ctrlCode_MouseMove);
|
||||
this.ctrlCode.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ctrlCode_MouseDown);
|
||||
this.ctrlCode.MouseLeave += new System.EventHandler(this.ctrlCode_MouseLeave);
|
||||
this.ctrlCode.TextZoomChanged += new System.EventHandler(this.ctrlCode_TextZoomChanged);
|
||||
//
|
||||
// ctxMenu
|
||||
|
|
|
@ -14,6 +14,7 @@ using Mesen.GUI.Debugger.Labels;
|
|||
using Mesen.GUI.Debugger.Integration;
|
||||
using Mesen.GUI.Debugger.Workspace;
|
||||
using static Mesen.GUI.Debugger.Integration.DbgImporter;
|
||||
using Mesen.GUI.Forms;
|
||||
|
||||
namespace Mesen.GUI.Debugger.Controls
|
||||
{
|
||||
|
@ -230,6 +231,36 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
int lineIndex = ctrlCode.GetLineIndexAtPosition(e.Y);
|
||||
_manager.ToggleBreakpoint(lineIndex);
|
||||
}
|
||||
BaseForm.GetPopupTooltip(this.FindForm()).Hide();
|
||||
}
|
||||
|
||||
private string _lastWord = null;
|
||||
private void ctrlCode_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
string word = ctrlCode.GetWordUnderLocation(e.Location).Trim();
|
||||
if(word == _lastWord) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(word.Length > 0) {
|
||||
Dictionary<string, string> values = _manager.GetTooltipData(word, ctrlCode.GetLineIndexAtPosition(e.Y));
|
||||
if(values != null) {
|
||||
Form form = this.FindForm();
|
||||
Point tooltipLocation = ctrlCode.GetWordEndPosition(e.Location);
|
||||
BaseForm.GetPopupTooltip(form).SetTooltip(form.PointToClient(ctrlCode.PointToScreen(tooltipLocation)), values);
|
||||
} else {
|
||||
BaseForm.GetPopupTooltip(this.FindForm()).Hide();
|
||||
}
|
||||
} else {
|
||||
BaseForm.GetPopupTooltip(this.FindForm()).Hide();
|
||||
}
|
||||
|
||||
_lastWord = word;
|
||||
}
|
||||
|
||||
private void ctrlCode_MouseLeave(object sender, EventArgs e)
|
||||
{
|
||||
BaseForm.GetPopupTooltip(this.FindForm()).Hide();
|
||||
}
|
||||
|
||||
private void ctrlCode_TextZoomChanged(object sender, EventArgs e)
|
||||
|
|
|
@ -147,6 +147,11 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
return this.ctrlTextbox.GetWordUnderLocation(position);
|
||||
}
|
||||
|
||||
public Point GetWordEndPosition(Point position)
|
||||
{
|
||||
return this.ctrlTextbox.GetWordEndPosition(position);
|
||||
}
|
||||
|
||||
private void ctrlTextbox_ScrollPositionChanged(object sender, EventArgs e)
|
||||
{
|
||||
this.vScrollBar.Value = this.ctrlTextbox.ScrollPosition;
|
||||
|
|
|
@ -406,7 +406,15 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
public string GetFullWidthString(int lineIndex)
|
||||
{
|
||||
CodeLineData lineData = _dataProvider.GetCodeLineData(lineIndex);
|
||||
string text = lineData.Text + lineData.GetEffectiveAddressString(_addressFormat);
|
||||
if(lineData.Flags.HasFlag(LineFlags.BlockStart) || lineData.Flags.HasFlag(LineFlags.BlockEnd)) {
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string text = lineData.Text;
|
||||
if(!lineData.Flags.HasFlag(LineFlags.Label) && text.Length > 0) {
|
||||
text += " " + lineData.GetEffectiveAddressString(_addressFormat);
|
||||
}
|
||||
|
||||
if(lineData.Comment.Length > 0) {
|
||||
return text.PadRight(text.Length > 0 ? CommentSpacingCharCount : 0) + lineData.Comment;
|
||||
}
|
||||
|
@ -424,8 +432,7 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
}
|
||||
|
||||
int positionX = position.X - marginLeft;
|
||||
//TODO
|
||||
//positionX -= (LineIndentations != null ? LineIndentations[lineIndex] : 0);
|
||||
positionX -= _dataProvider.GetCodeLineData(lineIndex).Indentation;
|
||||
if(positionX >= 0) {
|
||||
float charWidth = g.MeasureString("W", this.Font, int.MaxValue, StringFormat.GenericTypographic).Width;
|
||||
charIndex = (int)(positionX / charWidth);
|
||||
|
@ -452,7 +459,7 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
}
|
||||
int startIndex = text.LastIndexOfAny(_wordDelimiters, charIndex);
|
||||
|
||||
if(startIndex >= 0 && text[startIndex] == '#' && text.Length > startIndex && text[startIndex + 1] == '$') {
|
||||
if(startIndex >= 0 && text[startIndex] == '#' && text.Length > startIndex && (text[startIndex + 1] == '$' || text[startIndex + 1] >= '0' && text[startIndex + 1] <= '9')) {
|
||||
//Special case for immediate values. e.g: we want to show a tooltip for #MyLabel, but not for #$EF
|
||||
return text.Substring(startIndex, endIndex - startIndex);
|
||||
} else {
|
||||
|
@ -464,6 +471,32 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public Point GetWordEndPosition(Point position)
|
||||
{
|
||||
int charIndex;
|
||||
int lineIndex;
|
||||
if(this.GetCharIndex(position, out charIndex, out lineIndex)) {
|
||||
using(Graphics g = Graphics.FromHwnd(this.Handle)) {
|
||||
int marginLeft = this.GetMargin(g, true);
|
||||
int positionX = marginLeft + _dataProvider.GetCodeLineData(lineIndex).Indentation;
|
||||
|
||||
string text = this.GetFullWidthString(lineIndex);
|
||||
int endIndex = text.IndexOfAny(_wordDelimiters, charIndex);
|
||||
if(endIndex == -1) {
|
||||
endIndex = text.Length;
|
||||
}
|
||||
|
||||
float charWidth = g.MeasureString("W", this.Font, int.MaxValue, StringFormat.GenericTypographic).Width;
|
||||
positionX += (int)(endIndex * charWidth);
|
||||
|
||||
int positionY = (GetLineAtPosition(position.Y) + 1) * this.LineHeight;
|
||||
|
||||
return new Point(positionX, positionY);
|
||||
}
|
||||
}
|
||||
return Point.Empty;
|
||||
}
|
||||
|
||||
private int GetLineAtPosition(int yPos)
|
||||
{
|
||||
return Math.Max(0, yPos / this.LineHeight);
|
||||
|
@ -967,7 +1000,7 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
Color defaultColor = Color.FromArgb(60, 60, 60);
|
||||
if(codeString.Length > 0) {
|
||||
Match match = CodeHighlightingEnabled ? _codeRegex.Match(codeString) : null;
|
||||
if(match != null && match.Success && !codeString.EndsWith(":")) {
|
||||
if(match != null && match.Success && !lineData.Flags.HasFlag(LineFlags.Label)) {
|
||||
string padding = match.Groups[1].Value;
|
||||
string opcode = match.Groups[2].Value;
|
||||
string invalidStar = match.Groups[3].Value;
|
||||
|
@ -1000,7 +1033,7 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
|
||||
float xOffset = 0;
|
||||
for(int i = 0; i < parts.Count; i++) {
|
||||
using(Brush b = new SolidBrush(textColor.HasValue && (i < codePartCount || i == parts.Count - 1) ? textColor.Value : colors[i])) {
|
||||
using(Brush b = new SolidBrush(textColor.HasValue && (i <= codePartCount) ? textColor.Value : colors[i])) {
|
||||
g.DrawString(parts[i], this.Font, b, marginLeft + xOffset, positionY, StringFormat.GenericTypographic);
|
||||
xOffset += g.MeasureString("".PadLeft(parts[i].Length, 'w'), this.Font, Point.Empty, StringFormat.GenericTypographic).Width;
|
||||
characterCount += parts[i].Length;
|
||||
|
|
Loading…
Add table
Reference in a new issue