502 lines
14 KiB
C#
502 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using static Mesen.GUI.Debugger.Ld65DbgImporter;
|
|
using System.IO;
|
|
using Mesen.GUI.Config;
|
|
using Mesen.GUI.Controls;
|
|
|
|
namespace Mesen.GUI.Debugger.Controls
|
|
{
|
|
public partial class ctrlSourceViewer : BaseControl, ICodeViewer
|
|
{
|
|
private UInt32? _currentActiveAddress { get; set; } = null;
|
|
private CodeTooltipManager _tooltipManager = null;
|
|
private CodeViewerActions _codeViewerActions;
|
|
private DebugViewInfo _config;
|
|
private Ld65DbgImporter.FileInfo _selectedFile = null;
|
|
|
|
public ctrlSourceViewer()
|
|
{
|
|
InitializeComponent();
|
|
_tooltipManager = new CodeTooltipManager(this, this.ctrlCodeViewer);
|
|
}
|
|
|
|
protected override void OnLoad(EventArgs e)
|
|
{
|
|
base.OnLoad(e);
|
|
if(!IsDesignMode) {
|
|
_codeViewerActions = new CodeViewerActions(this, true);
|
|
}
|
|
}
|
|
|
|
public new void Focus()
|
|
{
|
|
base.Focus();
|
|
this.ctrlCodeViewer.Focus();
|
|
}
|
|
|
|
public void SetConfig(DebugViewInfo config, bool disableActions = false)
|
|
{
|
|
_config = config;
|
|
if(!disableActions) {
|
|
_codeViewerActions.InitMenu(config);
|
|
}
|
|
if(this.ctrlCodeViewer.TextZoom != config.TextZoom) {
|
|
this.ctrlCodeViewer.TextZoom = config.TextZoom;
|
|
}
|
|
}
|
|
|
|
private List<string> _lineNumberNotes = new List<string>();
|
|
private void UpdateCode()
|
|
{
|
|
List<int> indents = new List<int>();
|
|
List<string> addressing = new List<string>();
|
|
List<string> comments = new List<string>();
|
|
List<int> lineNumbers = new List<int>();
|
|
List<string> lineNotes = new List<string>();
|
|
_lineNumberNotes = new List<string>();
|
|
List<string> codeLines = new List<string>();
|
|
|
|
bool isC = CurrentFile.Name.EndsWith(".h") || CurrentFile.Name.EndsWith(".c");
|
|
int index = 0;
|
|
foreach(string line in CurrentFile.Data) {
|
|
string l = line.Replace("\t", " ");
|
|
|
|
addressing.Add("");
|
|
|
|
int prgAddress = _symbolProvider.GetPrgAddress(CurrentFile.ID, index);
|
|
int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)prgAddress, AddressType.PrgRom);
|
|
lineNumbers.Add(relativeAddress);
|
|
lineNotes.Add("");
|
|
_lineNumberNotes.Add(prgAddress >= 0 ? prgAddress.ToString("X4") : "");
|
|
|
|
string trimmed = l.TrimStart();
|
|
int margin = (l.Length - trimmed.Length) * 10;
|
|
indents.Add(margin);
|
|
|
|
int commentIndex;
|
|
if(isC) {
|
|
commentIndex = trimmed.IndexOf("//");
|
|
} else {
|
|
commentIndex = trimmed.IndexOfAny(new char[] { ';', '.' });
|
|
}
|
|
|
|
if(commentIndex >= 0) {
|
|
comments.Add(trimmed.Substring(commentIndex));
|
|
codeLines.Add(trimmed.Substring(0, commentIndex));
|
|
} else {
|
|
comments.Add("");
|
|
codeLines.Add(trimmed);
|
|
}
|
|
index++;
|
|
}
|
|
|
|
ctrlCodeViewer.CodeHighlightingEnabled = !isC;
|
|
ctrlCodeViewer.LineIndentations = indents.ToArray();
|
|
ctrlCodeViewer.Addressing = addressing.ToArray();
|
|
ctrlCodeViewer.Comments = comments.ToArray();
|
|
|
|
ctrlCodeViewer.LineNumbers = lineNumbers.ToArray();
|
|
ctrlCodeViewer.TextLineNotes = lineNotes.ToArray();
|
|
ctrlCodeViewer.LineNumberNotes = _lineNumberNotes.ToArray();
|
|
ctrlCodeViewer.TextLines = codeLines.ToArray();
|
|
|
|
this.RefreshViewer();
|
|
}
|
|
|
|
public void RefreshViewer()
|
|
{
|
|
if(_symbolProvider != null) {
|
|
ctrlCodeViewer.ScrollbarColorProvider = new ScrollbarColorProvider(this);
|
|
ctrlCodeViewer.StyleProvider = new LineStyleProvider(this);
|
|
} else {
|
|
ctrlCodeViewer.ScrollbarColorProvider = null;
|
|
ctrlCodeViewer.StyleProvider = null;
|
|
}
|
|
}
|
|
|
|
Ld65DbgImporter _symbolProvider;
|
|
public Ld65DbgImporter SymbolProvider
|
|
{
|
|
get { return _symbolProvider; }
|
|
set
|
|
{
|
|
if(_symbolProvider != value) {
|
|
_symbolProvider = value;
|
|
|
|
cboFile.BeginUpdate();
|
|
cboFile.Items.Clear();
|
|
cboFile.Sorted = false;
|
|
if(_symbolProvider != null) {
|
|
foreach(Ld65DbgImporter.FileInfo file in _symbolProvider.Files.Values) {
|
|
if(file.Data != null && file.Data.Length > 0 && !file.Name.ToLower().EndsWith(".chr")) {
|
|
cboFile.Items.Add(file);
|
|
}
|
|
}
|
|
}
|
|
cboFile.Sorted = true;
|
|
cboFile.EndUpdate();
|
|
|
|
if(cboFile.Items.Count > 0) {
|
|
cboFile.SelectedIndex = 0;
|
|
}
|
|
|
|
_tooltipManager.SymbolProvider = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Ld65DbgImporter.FileInfo CurrentFile
|
|
{
|
|
get { return (Ld65DbgImporter.FileInfo)_selectedFile; }
|
|
set
|
|
{
|
|
cboFile.SelectedItem = value;
|
|
_selectedFile = value;
|
|
}
|
|
}
|
|
|
|
public bool HideFileDropdown { set { cboFile.Visible = !value; lblFile.Visible = !value; } }
|
|
|
|
public ctrlScrollableTextbox CodeViewer { get { return this.ctrlCodeViewer; } }
|
|
public CodeViewerActions CodeViewerActions { get { return _codeViewerActions; } }
|
|
public UInt32? ActiveAddress { get { return _currentActiveAddress; } }
|
|
|
|
private void mnuToggleBreakpoint_Click(object sender, EventArgs e)
|
|
{
|
|
ToggleBreakpoint();
|
|
}
|
|
|
|
public AddressTypeInfo GetAddressInfo(int lineIndex)
|
|
{
|
|
return new AddressTypeInfo() {
|
|
Address = _symbolProvider?.GetPrgAddress(CurrentFile.ID, lineIndex) ?? -1,
|
|
Type = AddressType.PrgRom
|
|
};
|
|
}
|
|
|
|
public void SetActiveAddress(UInt32 address)
|
|
{
|
|
_currentActiveAddress = address;
|
|
this.RefreshViewer();
|
|
}
|
|
|
|
public void SelectActiveAddress(UInt32 address)
|
|
{
|
|
if(_symbolProvider == null) {
|
|
return;
|
|
}
|
|
|
|
_currentActiveAddress = address;
|
|
|
|
ScrollToLineNumber((int)address);
|
|
this.RefreshViewer();
|
|
}
|
|
|
|
public void ClearActiveAddress()
|
|
{
|
|
_currentActiveAddress = null;
|
|
this.RefreshViewer();
|
|
}
|
|
|
|
private void cboFile_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if(cboFile.SelectedIndex >= 0) {
|
|
_selectedFile = cboFile.SelectedItem as Ld65DbgImporter.FileInfo;
|
|
this.ctrlCodeViewer.ScrollToLineIndex(0, eHistoryType.Always, true);
|
|
this.UpdateCode();
|
|
} else {
|
|
_selectedFile = null;
|
|
}
|
|
}
|
|
|
|
public void ToggleBreakpoint()
|
|
{
|
|
AddressTypeInfo info = GetAddressInfo(ctrlCodeViewer.SelectedLine);
|
|
if(info.Address >= 0) {
|
|
BreakpointManager.ToggleBreakpoint(-1, info, false);
|
|
}
|
|
}
|
|
|
|
private void ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if(e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) {
|
|
this.ctrlCodeViewer.ContextMenuStrip = contextMenuMargin;
|
|
} else {
|
|
this.ctrlCodeViewer.ContextMenuStrip = _codeViewerActions.contextMenu;
|
|
}
|
|
|
|
_tooltipManager.ProcessMouseMove(e.Location);
|
|
}
|
|
|
|
private void ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if(e.Button == MouseButtons.Left && e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) {
|
|
ToggleBreakpoint();
|
|
}
|
|
}
|
|
|
|
private void ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
_codeViewerActions.ProcessMouseUp(e.Location, e.Button);
|
|
}
|
|
|
|
private void ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e)
|
|
{
|
|
_codeViewerActions.ProcessMouseDoubleClick(e.Location);
|
|
}
|
|
|
|
private void ctrlCodeViewer_TextZoomChanged(object sender, EventArgs e)
|
|
{
|
|
_config.TextZoom = this.ctrlCodeViewer.TextZoom;
|
|
ConfigManager.ApplyChanges();
|
|
}
|
|
|
|
private Breakpoint GetCurrentLineBreakpoint()
|
|
{
|
|
AddressTypeInfo addressInfo = GetAddressInfo(ctrlCodeViewer.SelectedLine);
|
|
if(addressInfo.Address >= 0) {
|
|
int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type);
|
|
return BreakpointManager.GetMatchingBreakpoint(relativeAddress, addressInfo);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void mnuEditBreakpoint_Click(object sender, EventArgs e)
|
|
{
|
|
Breakpoint bp = GetCurrentLineBreakpoint();
|
|
if(bp != null) {
|
|
BreakpointManager.EditBreakpoint(bp);
|
|
}
|
|
}
|
|
|
|
private void mnuDisableBreakpoint_Click(object sender, EventArgs e)
|
|
{
|
|
Breakpoint bp = GetCurrentLineBreakpoint();
|
|
bp.SetEnabled(!bp.Enabled);
|
|
}
|
|
|
|
private void mnuRemoveBreakpoint_Click(object sender, EventArgs e)
|
|
{
|
|
Breakpoint bp = GetCurrentLineBreakpoint();
|
|
if(bp != null) {
|
|
BreakpointManager.RemoveBreakpoint(bp);
|
|
}
|
|
}
|
|
|
|
private void contextMenuMargin_Opening(object sender, CancelEventArgs e)
|
|
{
|
|
Breakpoint bp = GetCurrentLineBreakpoint();
|
|
if(bp != null) {
|
|
mnuDisableBreakpoint.Text = bp.Enabled ? "Disable breakpoint" : "Enable breakpoint";
|
|
} else {
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
private void ctrlCodeViewer_MouseLeave(object sender, EventArgs e)
|
|
{
|
|
_tooltipManager.Close();
|
|
}
|
|
|
|
private void ctrlCodeViewer_ScrollPositionChanged(object sender, EventArgs e)
|
|
{
|
|
_tooltipManager?.Close();
|
|
}
|
|
|
|
public void ScrollToAddress(AddressTypeInfo addressInfo, bool scrollToTop = false)
|
|
{
|
|
if(addressInfo.Address >= 0 && addressInfo.Type == AddressType.PrgRom) {
|
|
LineInfo line = _symbolProvider.GetSourceCodeLineInfo(addressInfo.Address);
|
|
if(line != null) {
|
|
foreach(Ld65DbgImporter.FileInfo fileInfo in cboFile.Items) {
|
|
if(fileInfo.ID == line.FileID) {
|
|
cboFile.SelectedItem = fileInfo;
|
|
break;
|
|
}
|
|
}
|
|
ctrlCodeViewer.ScrollToLineIndex(line.LineNumber, eHistoryType.Always, scrollToTop);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool CurrentFileContainsAddress(int cpuAddress)
|
|
{
|
|
if(CurrentFile == null) {
|
|
return false;
|
|
}
|
|
|
|
AddressTypeInfo addressInfo = new AddressTypeInfo();
|
|
InteropEmu.DebugGetAbsoluteAddressAndType((uint)cpuAddress, addressInfo);
|
|
if(addressInfo.Address >= 0 && addressInfo.Type == AddressType.PrgRom) {
|
|
LineInfo line = _symbolProvider.GetSourceCodeLineInfo(addressInfo.Address);
|
|
return CurrentFile.ID == line?.FileID;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void ScrollToLineNumber(int lineNumber, bool scrollToTop = false)
|
|
{
|
|
AddressTypeInfo addressInfo = new AddressTypeInfo();
|
|
InteropEmu.DebugGetAbsoluteAddressAndType((uint)lineNumber, addressInfo);
|
|
ScrollToAddress(addressInfo, scrollToTop);
|
|
}
|
|
|
|
public void EditSubroutine()
|
|
{
|
|
//Not supported
|
|
}
|
|
|
|
public void EditSelectedCode()
|
|
{
|
|
//Not supported
|
|
}
|
|
|
|
public void FindAllOccurrences(string text, bool matchWholeWord, bool matchCase)
|
|
{
|
|
//Not supported (yet?)
|
|
}
|
|
|
|
#region Scrollbar / Code highlighting
|
|
|
|
class LineStyleProvider : ctrlTextbox.ILineStyleProvider
|
|
{
|
|
private ctrlSourceViewer _viewer;
|
|
|
|
public LineStyleProvider(ctrlSourceViewer viewer)
|
|
{
|
|
_viewer = viewer;
|
|
}
|
|
|
|
public string GetLineComment(int lineNumber)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public LineProperties GetLineStyle(int cpuAddress, int lineIndex)
|
|
{
|
|
DebugInfo info = ConfigManager.Config.DebugInfo;
|
|
LineProperties props = new LineProperties();
|
|
|
|
int nextLineIndex = lineIndex + 1;
|
|
int nextCpuAddress;
|
|
do {
|
|
nextCpuAddress = _viewer.CodeViewer.GetLineNumber(nextLineIndex);
|
|
nextLineIndex++;
|
|
} while(nextCpuAddress < 0);
|
|
|
|
bool isActiveStatement = (
|
|
cpuAddress >= 0 &&
|
|
_viewer._currentActiveAddress.HasValue && (
|
|
(_viewer._currentActiveAddress >= cpuAddress && _viewer._currentActiveAddress < nextCpuAddress && nextCpuAddress > cpuAddress) ||
|
|
(_viewer._currentActiveAddress == cpuAddress && nextCpuAddress < cpuAddress)
|
|
)
|
|
);
|
|
|
|
int prgAddress = _viewer._symbolProvider.GetPrgAddress(_viewer.CurrentFile.ID, lineIndex);
|
|
|
|
if(prgAddress >= 0) {
|
|
AddressTypeInfo addressInfo = new AddressTypeInfo();
|
|
addressInfo.Address = prgAddress;
|
|
addressInfo.Type = AddressType.PrgRom;
|
|
|
|
ctrlDebuggerCode.LineStyleProvider.GetBreakpointLineProperties(props, cpuAddress, addressInfo);
|
|
}
|
|
|
|
if(isActiveStatement) {
|
|
props.FgColor = Color.Black;
|
|
props.TextBgColor = info.CodeActiveStatementColor;
|
|
props.Symbol |= LineSymbol.Arrow;
|
|
}
|
|
|
|
return props;
|
|
}
|
|
}
|
|
|
|
class ScrollbarColorProvider : IScrollbarColorProvider
|
|
{
|
|
private Dictionary<int, Color> _breakpointColors = new Dictionary<int, Color>();
|
|
|
|
private ctrlSourceViewer _viewer;
|
|
public ScrollbarColorProvider(ctrlSourceViewer viewer)
|
|
{
|
|
_viewer = viewer;
|
|
|
|
DebugInfo info = ConfigManager.Config.DebugInfo;
|
|
int len = viewer.ctrlCodeViewer.LineCount;
|
|
|
|
int[] relativeAddresses = new int[len];
|
|
AddressTypeInfo[] addressInfo = new AddressTypeInfo[len];
|
|
for(int i = 0; i < len; i++) {
|
|
addressInfo[i] = _viewer.GetAddressInfo(i);
|
|
if(addressInfo[i].Address >= 0) {
|
|
relativeAddresses[i] = InteropEmu.DebugGetRelativeAddress((uint)addressInfo[i].Address, AddressType.PrgRom);
|
|
} else {
|
|
relativeAddresses[i] = -1;
|
|
}
|
|
}
|
|
|
|
foreach(Breakpoint breakpoint in BreakpointManager.Breakpoints) {
|
|
for(int i = 0; i < len; i++) {
|
|
if(breakpoint.Matches(relativeAddresses[i], addressInfo[i])) {
|
|
Color bpColor = breakpoint.BreakOnExec ? info.CodeExecBreakpointColor : (breakpoint.BreakOnWrite ? info.CodeWriteBreakpointColor : info.CodeReadBreakpointColor);
|
|
_breakpointColors[i] = bpColor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public Color GetBackgroundColor(float position)
|
|
{
|
|
return Color.Transparent;
|
|
}
|
|
|
|
public frmCodePreviewTooltip GetPreview(int lineIndex)
|
|
{
|
|
if(lineIndex < _viewer.CodeViewer.LineCount) {
|
|
while(lineIndex > 0 && _viewer.CodeViewer.GetLineNumber(lineIndex) < 0) {
|
|
lineIndex--;
|
|
}
|
|
return new frmCodePreviewTooltip(_viewer.FindForm(), lineIndex, null, _viewer.SymbolProvider, _viewer.CurrentFile);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public int GetActiveLine()
|
|
{
|
|
if(_viewer._currentActiveAddress.HasValue && _viewer.CurrentFileContainsAddress((int)_viewer._currentActiveAddress.Value)) {
|
|
return _viewer.ctrlCodeViewer.GetLineIndex((int)_viewer._currentActiveAddress.Value);
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
public int GetSelectedLine()
|
|
{
|
|
return _viewer.ctrlCodeViewer.SelectedLine;
|
|
}
|
|
|
|
public Color GetMarkerColor(float position, int linesPerPixel)
|
|
{
|
|
int lineIndex = (int)((_viewer.ctrlCodeViewer.LineCount - 1) * position);
|
|
|
|
Color bpColor;
|
|
for(int i = 0; i < linesPerPixel; i++) {
|
|
if(_breakpointColors.TryGetValue(lineIndex + i, out bpColor)) {
|
|
return bpColor;
|
|
}
|
|
}
|
|
return Color.Transparent;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|