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; } } public void SetMessage(TextboxMessageInfo message) { this.ctrlCodeViewer.SetMessage(message); } private List _lineNumberNotes = new List(); private void UpdateCode() { if(_symbolProvider == null || CurrentFile == null) { return; } List indents = new List(); List addressing = new List(); List comments = new List(); List lineNumbers = new List(); List lineNotes = new List(); _lineNumberNotes = new List(); List codeLines = new List(); 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.UpdateCode(); } 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); } else { //Current line has no address, try using the next line instead. //(Used when trying to set a breakpoint on a row containing only a label) info = GetAddressInfo(ctrlCodeViewer.SelectedLine + 1); 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) { ctrlDebuggerCode.LineStyleProvider.ConfigureActiveStatement(props); } return props; } } class ScrollbarColorProvider : IScrollbarColorProvider { private Dictionary _breakpointColors = new Dictionary(); 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 } }