1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Drawing; 5 using System.Data; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using static Mesen.GUI.Debugger.Ld65DbgImporter; 11 using System.IO; 12 using Mesen.GUI.Config; 13 using Mesen.GUI.Controls; 14 using System.Text.RegularExpressions; 15 using System.Diagnostics; 16 17 namespace Mesen.GUI.Debugger.Controls 18 { 19 public partial class ctrlSourceViewer : BaseControl, ICodeViewer 20 { 21 private UInt32? _currentActiveAddress { get; set; } = null; 22 private CodeTooltipManager _tooltipManager = null; 23 private CodeViewerActions _codeViewerActions; 24 private DebugViewInfo _config; 25 private Ld65DbgImporter.FileInfo _selectedFile = null; 26 ctrlSourceViewer()27 public ctrlSourceViewer() 28 { 29 InitializeComponent(); 30 _tooltipManager = new CodeTooltipManager(this, this.ctrlCodeViewer); 31 } 32 OnLoad(EventArgs e)33 protected override void OnLoad(EventArgs e) 34 { 35 base.OnLoad(e); 36 if(!IsDesignMode) { 37 _codeViewerActions = new CodeViewerActions(this, true); 38 39 ctrlFindOccurrences.Viewer = this; 40 splitContainer.Panel2Collapsed = true; 41 42 this.SymbolProvider = DebugWorkspaceManager.SymbolProvider; 43 DebugWorkspaceManager.SymbolProviderChanged += UpdateSymbolProvider; 44 } 45 } 46 UpdateSymbolProvider(Ld65DbgImporter symbolProvider)47 private void UpdateSymbolProvider(Ld65DbgImporter symbolProvider) 48 { 49 this.SymbolProvider = symbolProvider; 50 if(symbolProvider == null && this.Visible) { 51 _codeViewerActions.SwitchView(); 52 } 53 } 54 Focus()55 public new void Focus() 56 { 57 base.Focus(); 58 this.ctrlCodeViewer.Focus(); 59 } 60 SetConfig(DebugViewInfo config, bool disableActions = false)61 public void SetConfig(DebugViewInfo config, bool disableActions = false) 62 { 63 _config = config; 64 if(!disableActions) { 65 _codeViewerActions.InitMenu(config); 66 } 67 if(this.ctrlCodeViewer.TextZoom != config.TextZoom) { 68 this.ctrlCodeViewer.TextZoom = config.TextZoom; 69 } 70 } 71 SetMessage(TextboxMessageInfo message)72 public void SetMessage(TextboxMessageInfo message) 73 { 74 this.ctrlCodeViewer.SetMessage(message); 75 } 76 77 private List<string> _lineNumberNotes = new List<string>(); UpdateCode()78 private void UpdateCode() 79 { 80 if(_symbolProvider == null || CurrentFile == null) { 81 return; 82 } 83 84 List<int> indents = new List<int>(); 85 List<string> addressing = new List<string>(); 86 List<string> comments = new List<string>(); 87 List<int> lineNumbers = new List<int>(); 88 List<string> lineNotes = new List<string>(); 89 _lineNumberNotes = new List<string>(); 90 List<string> codeLines = new List<string>(); 91 92 byte[] prgRomContent = InteropEmu.DebugGetMemoryState(DebugMemoryType.PrgRom); 93 94 bool isC = CurrentFile.Name.EndsWith(".h") || CurrentFile.Name.EndsWith(".c"); 95 int lineIndex = 0; 96 foreach(string line in CurrentFile.Data) { 97 string l = line.Replace("\t", " "); 98 99 addressing.Add(""); 100 101 int prgAddress = _symbolProvider.GetPrgAddress(CurrentFile.ID, lineIndex); 102 103 if(prgAddress >= 0) { 104 int opSize = frmOpCodeTooltip.GetOpSize(prgRomContent[prgAddress]); 105 string byteCode = ""; 106 for(int i = prgAddress, end = prgAddress + opSize; i < end && i < prgRomContent.Length; i++) { 107 byteCode += "$" + prgRomContent[i].ToString("X2") + " "; 108 } 109 lineNotes.Add(byteCode); 110 } else { 111 lineNotes.Add(""); 112 } 113 114 int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)prgAddress, AddressType.PrgRom); 115 lineNumbers.Add(relativeAddress); 116 _lineNumberNotes.Add(prgAddress >= 0 ? prgAddress.ToString("X4") : ""); 117 118 string trimmed = l.TrimStart(); 119 int margin = (l.Length - trimmed.Length) * 10; 120 indents.Add(margin); 121 122 int commentIndex; 123 if(isC) { 124 commentIndex = trimmed.IndexOf("//"); 125 } else { 126 commentIndex = trimmed.IndexOfAny(new char[] { ';', '.' }); 127 } 128 129 if(commentIndex >= 0) { 130 comments.Add(trimmed.Substring(commentIndex)); 131 codeLines.Add(trimmed.Substring(0, commentIndex)); 132 } else { 133 comments.Add(""); 134 codeLines.Add(trimmed); 135 } 136 lineIndex++; 137 } 138 139 ctrlCodeViewer.CodeHighlightingEnabled = !isC; 140 ctrlCodeViewer.LineIndentations = indents.ToArray(); 141 ctrlCodeViewer.Addressing = addressing.ToArray(); 142 ctrlCodeViewer.Comments = comments.ToArray(); 143 144 ctrlCodeViewer.LineNumbers = lineNumbers.ToArray(); 145 ctrlCodeViewer.TextLineNotes = lineNotes.ToArray(); 146 ctrlCodeViewer.LineNumberNotes = _lineNumberNotes.ToArray(); 147 ctrlCodeViewer.TextLines = codeLines.ToArray(); 148 149 this.RefreshViewer(); 150 } 151 RefreshViewer()152 public void RefreshViewer() 153 { 154 if(_symbolProvider != null) { 155 ctrlCodeViewer.ScrollbarColorProvider = new ScrollbarColorProvider(this); 156 ctrlCodeViewer.StyleProvider = new LineStyleProvider(this); 157 } else { 158 ctrlCodeViewer.ScrollbarColorProvider = null; 159 ctrlCodeViewer.StyleProvider = null; 160 } 161 } 162 163 Ld65DbgImporter _symbolProvider; 164 public Ld65DbgImporter SymbolProvider 165 { 166 get { return _symbolProvider; } 167 set 168 { 169 if(_symbolProvider != value) { 170 _symbolProvider = value; 171 172 cboFile.BeginUpdate(); 173 cboFile.Items.Clear(); 174 cboFile.Sorted = false; 175 if(_symbolProvider != null) { 176 foreach(Ld65DbgImporter.FileInfo file in _symbolProvider.Files.Values) { 177 if(file.Data != null && file.Data.Length > 0 && !file.Name.ToLower().EndsWith(".chr")) { 178 cboFile.Items.Add(file); 179 } 180 } 181 } 182 cboFile.Sorted = true; 183 cboFile.EndUpdate(); 184 185 if(cboFile.Items.Count > 0) { 186 cboFile.SelectedIndex = 0; 187 } 188 189 _tooltipManager.SymbolProvider = value; 190 } 191 } 192 } 193 194 public Ld65DbgImporter.FileInfo CurrentFile 195 { 196 get { return (Ld65DbgImporter.FileInfo)_selectedFile; } 197 set 198 { 199 cboFile.SelectedItem = value; 200 _selectedFile = value; 201 } 202 } 203 204 public bool HideFileDropdown { set { cboFile.Visible = !value; lblFile.Visible = !value; } } 205 206 public ctrlScrollableTextbox CodeViewer { get { return this.ctrlCodeViewer; } } 207 public CodeViewerActions CodeViewerActions { get { return _codeViewerActions; } } 208 public UInt32? ActiveAddress { get { return _currentActiveAddress; } } 209 mnuToggleBreakpoint_Click(object sender, EventArgs e)210 private void mnuToggleBreakpoint_Click(object sender, EventArgs e) 211 { 212 _codeViewerActions.ToggleBreakpoint(false); 213 } 214 GetAddressInfo(int lineIndex)215 public AddressTypeInfo GetAddressInfo(int lineIndex) 216 { 217 return new AddressTypeInfo() { 218 Address = _symbolProvider?.GetPrgAddress(CurrentFile.ID, lineIndex) ?? -1, 219 Type = AddressType.PrgRom 220 }; 221 } 222 SetActiveAddress(UInt32 address)223 public void SetActiveAddress(UInt32 address) 224 { 225 _currentActiveAddress = address; 226 this.UpdateCode(); 227 } 228 SelectActiveAddress(UInt32 address)229 public void SelectActiveAddress(UInt32 address) 230 { 231 if(_symbolProvider == null) { 232 return; 233 } 234 235 _currentActiveAddress = address; 236 237 ScrollToLineNumber((int)address); 238 this.RefreshViewer(); 239 } 240 ClearActiveAddress()241 public void ClearActiveAddress() 242 { 243 _currentActiveAddress = null; 244 this.RefreshViewer(); 245 } 246 cboFile_SelectedIndexChanged(object sender, EventArgs e)247 private void cboFile_SelectedIndexChanged(object sender, EventArgs e) 248 { 249 if(cboFile.SelectedIndex >= 0) { 250 _selectedFile = cboFile.SelectedItem as Ld65DbgImporter.FileInfo; 251 this.ctrlCodeViewer.ScrollToLineIndex(0, eHistoryType.Always, true); 252 this.UpdateCode(); 253 } else { 254 _selectedFile = null; 255 } 256 } 257 ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e)258 private void ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e) 259 { 260 if(e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) { 261 if(this.ctrlCodeViewer.ContextMenuStrip != contextMenuMargin) { 262 this.ctrlCodeViewer.ContextMenuStrip = contextMenuMargin; 263 ThemeHelper.FixMonoColors(contextMenuMargin); 264 } 265 } else { 266 if(this.ctrlCodeViewer.ContextMenuStrip != _codeViewerActions.contextMenu) { 267 this.ctrlCodeViewer.ContextMenuStrip = _codeViewerActions.contextMenu; 268 ThemeHelper.FixMonoColors(this.ctrlCodeViewer.ContextMenuStrip); 269 } 270 } 271 } 272 ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e)273 private void ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e) 274 { 275 if(e.Button == MouseButtons.Left && e.Location.X < this.ctrlCodeViewer.CodeMargin / 4) { 276 _codeViewerActions.ToggleBreakpoint(false); 277 } 278 } 279 ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e)280 private void ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e) 281 { 282 _codeViewerActions.ProcessMouseUp(e.Location, e.Button); 283 } 284 ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e)285 private void ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e) 286 { 287 _codeViewerActions.ProcessMouseDoubleClick(e.Location); 288 } 289 ctrlCodeViewer_TextZoomChanged(object sender, EventArgs e)290 private void ctrlCodeViewer_TextZoomChanged(object sender, EventArgs e) 291 { 292 _config.TextZoom = this.ctrlCodeViewer.TextZoom; 293 ConfigManager.ApplyChanges(); 294 } 295 GetCurrentLineBreakpoint()296 private Breakpoint GetCurrentLineBreakpoint() 297 { 298 AddressTypeInfo addressInfo = GetAddressInfo(ctrlCodeViewer.SelectedLine); 299 if(addressInfo.Address >= 0) { 300 int relativeAddress = InteropEmu.DebugGetRelativeAddress((uint)addressInfo.Address, addressInfo.Type); 301 return BreakpointManager.GetMatchingBreakpoint(relativeAddress, addressInfo); 302 } 303 return null; 304 } 305 mnuEditBreakpoint_Click(object sender, EventArgs e)306 private void mnuEditBreakpoint_Click(object sender, EventArgs e) 307 { 308 Breakpoint bp = GetCurrentLineBreakpoint(); 309 if(bp != null) { 310 BreakpointManager.EditBreakpoint(bp); 311 } 312 } 313 mnuDisableBreakpoint_Click(object sender, EventArgs e)314 private void mnuDisableBreakpoint_Click(object sender, EventArgs e) 315 { 316 Breakpoint bp = GetCurrentLineBreakpoint(); 317 bp.SetEnabled(!bp.Enabled); 318 } 319 mnuRemoveBreakpoint_Click(object sender, EventArgs e)320 private void mnuRemoveBreakpoint_Click(object sender, EventArgs e) 321 { 322 Breakpoint bp = GetCurrentLineBreakpoint(); 323 if(bp != null) { 324 BreakpointManager.RemoveBreakpoint(bp); 325 } 326 } 327 contextMenuMargin_Opening(object sender, CancelEventArgs e)328 private void contextMenuMargin_Opening(object sender, CancelEventArgs e) 329 { 330 Breakpoint bp = GetCurrentLineBreakpoint(); 331 if(bp != null) { 332 mnuDisableBreakpoint.Text = bp.Enabled ? "Disable breakpoint" : "Enable breakpoint"; 333 } else { 334 e.Cancel = true; 335 } 336 } 337 ScrollToFileLine(string filename, int lineNumber)338 public void ScrollToFileLine(string filename, int lineNumber) 339 { 340 foreach(Ld65DbgImporter.FileInfo fileInfo in cboFile.Items) { 341 if(fileInfo.Name == filename) { 342 cboFile.SelectedItem = fileInfo; 343 ctrlCodeViewer.ScrollToLineIndex(lineNumber); 344 break; 345 } 346 } 347 } 348 ScrollToAddress(AddressTypeInfo addressInfo, bool scrollToTop = false)349 public void ScrollToAddress(AddressTypeInfo addressInfo, bool scrollToTop = false) 350 { 351 if(addressInfo.Address >= 0 && addressInfo.Type == AddressType.PrgRom) { 352 LineInfo line = _symbolProvider?.GetSourceCodeLineInfo(addressInfo.Address); 353 if(line != null) { 354 foreach(Ld65DbgImporter.FileInfo fileInfo in cboFile.Items) { 355 if(fileInfo.ID == line.FileID) { 356 cboFile.SelectedItem = fileInfo; 357 break; 358 } 359 } 360 ctrlCodeViewer.ScrollToLineIndex(line.LineNumber, eHistoryType.Always, scrollToTop); 361 } 362 } 363 } 364 CurrentFileContainsAddress(int cpuAddress)365 private bool CurrentFileContainsAddress(int cpuAddress) 366 { 367 if(CurrentFile == null) { 368 return false; 369 } 370 371 AddressTypeInfo addressInfo = new AddressTypeInfo(); 372 InteropEmu.DebugGetAbsoluteAddressAndType((uint)cpuAddress, addressInfo); 373 if(addressInfo.Address >= 0 && addressInfo.Type == AddressType.PrgRom) { 374 LineInfo line = _symbolProvider?.GetSourceCodeLineInfo(addressInfo.Address); 375 return CurrentFile.ID == line?.FileID; 376 } 377 return false; 378 } 379 ScrollToLineNumber(int lineNumber, bool scrollToTop = false)380 public void ScrollToLineNumber(int lineNumber, bool scrollToTop = false) 381 { 382 AddressTypeInfo addressInfo = new AddressTypeInfo(); 383 InteropEmu.DebugGetAbsoluteAddressAndType((uint)lineNumber, addressInfo); 384 ScrollToAddress(addressInfo, scrollToTop); 385 } 386 EditSubroutine()387 public void EditSubroutine() 388 { 389 //Not supported 390 } 391 EditSelectedCode()392 public void EditSelectedCode() 393 { 394 //Not supported 395 } 396 EditSourceFile()397 public void EditSourceFile() 398 { 399 if(string.IsNullOrWhiteSpace(ConfigManager.Config.DebugInfo.ExternalEditorPath) || !File.Exists(ConfigManager.Config.DebugInfo.ExternalEditorPath)) { 400 using(frmExternalEditorConfig frm = new frmExternalEditorConfig()) { 401 frm.ShowDialog(null, this.ParentForm); 402 } 403 } 404 405 if(File.Exists(ConfigManager.Config.DebugInfo.ExternalEditorPath)) { 406 string filePath = Path.Combine(_symbolProvider.DbgPath, CurrentFile.Name); 407 if(File.Exists(filePath)) { 408 filePath = "\"" + filePath + "\""; 409 string lineNumber = (ctrlCodeViewer.SelectedLine + 1).ToString(); 410 411 Process.Start( 412 ConfigManager.Config.DebugInfo.ExternalEditorPath, 413 ConfigManager.Config.DebugInfo.ExternalEditorArguments.Replace("%F", filePath).Replace("%f", filePath).Replace("%L", lineNumber).Replace("%l", lineNumber) 414 ); 415 } 416 } 417 } 418 FindAllOccurrences(SymbolInfo symbol)419 public void FindAllOccurrences(SymbolInfo symbol) 420 { 421 List<FindAllOccurrenceResult> results = new List<FindAllOccurrenceResult>(); 422 423 List<ReferenceInfo> references = _symbolProvider.GetSymbolReferences(symbol); 424 ReferenceInfo definition = _symbolProvider.GetSymbolDefinition(symbol); 425 if(definition != null) { 426 references.Insert(0, definition); 427 } 428 429 foreach(ReferenceInfo reference in references) { 430 results.Add(new FindAllOccurrenceResult() { 431 MatchedLine = reference.LineContent.Trim(), 432 Location = Path.GetFileName(reference.FileName) + ":" + (reference.LineNumber + 1).ToString(), 433 Destination = new GoToDestination() { 434 CpuAddress = -1, 435 Line = reference.LineNumber, 436 File = reference.FileName, 437 } 438 }); 439 } 440 441 ctrlFindOccurrences.FindAllOccurrences(symbol.Name, results); 442 this.splitContainer.Panel2Collapsed = false; 443 } 444 FindAllOccurrences(string text, bool matchWholeWord, bool matchCase)445 public void FindAllOccurrences(string text, bool matchWholeWord, bool matchCase) 446 { 447 List<FindAllOccurrenceResult> results = new List<FindAllOccurrenceResult>(); 448 449 string regexPattern; 450 if(matchWholeWord) { 451 regexPattern = $"([^0-9a-zA-Z_#@]+|^){Regex.Escape(text)}([^0-9a-zA-Z_#@]+|$)"; 452 } else { 453 regexPattern = Regex.Escape(text); 454 } 455 456 Regex regex = new Regex(regexPattern, matchCase ? RegexOptions.None : RegexOptions.IgnoreCase); 457 foreach(Ld65DbgImporter.FileInfo file in _symbolProvider.Files.Values) { 458 if(file.Data != null && file.Data.Length > 0 && !file.Name.ToLower().EndsWith(".chr")) { 459 for(int i = 0; i < file.Data.Length; i++) { 460 string line = file.Data[i]; 461 if(regex.IsMatch(line)) { 462 results.Add(new FindAllOccurrenceResult() { 463 MatchedLine = line.Trim(), 464 Location = Path.GetFileName(file.Name) + ":" + (i + 1).ToString(), 465 Destination = new GoToDestination() { 466 CpuAddress = -1, 467 Line = i, 468 File = file.Name, 469 } 470 }); 471 } 472 } 473 } 474 } 475 476 ctrlFindOccurrences.FindAllOccurrences(text, results); 477 this.splitContainer.Panel2Collapsed = false; 478 } 479 ctrlFindOccurrences_OnSearchResultsClosed(object sender, EventArgs e)480 private void ctrlFindOccurrences_OnSearchResultsClosed(object sender, EventArgs e) 481 { 482 this.splitContainer.Panel2Collapsed = true; 483 } 484 485 #region Scrollbar / Code highlighting 486 487 class LineStyleProvider : ctrlTextbox.ILineStyleProvider 488 { 489 private ctrlSourceViewer _viewer; 490 LineStyleProvider(ctrlSourceViewer viewer)491 public LineStyleProvider(ctrlSourceViewer viewer) 492 { 493 _viewer = viewer; 494 } 495 GetLineComment(int lineNumber)496 public string GetLineComment(int lineNumber) 497 { 498 return null; 499 } 500 GetLineStyle(int cpuAddress, int lineIndex)501 public LineProperties GetLineStyle(int cpuAddress, int lineIndex) 502 { 503 LineProperties props = new LineProperties(); 504 505 int nextLineIndex = lineIndex + 1; 506 int nextCpuAddress; 507 do { 508 nextCpuAddress = _viewer.CodeViewer.GetLineNumber(nextLineIndex); 509 nextLineIndex++; 510 } while(nextCpuAddress < 0); 511 512 bool isActiveStatement = ( 513 cpuAddress >= 0 && 514 _viewer._currentActiveAddress.HasValue && ( 515 (_viewer._currentActiveAddress >= cpuAddress && _viewer._currentActiveAddress < nextCpuAddress && nextCpuAddress > cpuAddress) || 516 (_viewer._currentActiveAddress == cpuAddress && nextCpuAddress < cpuAddress) 517 ) 518 ); 519 520 int prgAddress = _viewer._symbolProvider?.GetPrgAddress(_viewer.CurrentFile.ID, lineIndex) ?? -1; 521 522 if(prgAddress >= 0) { 523 AddressTypeInfo addressInfo = new AddressTypeInfo(); 524 addressInfo.Address = prgAddress; 525 addressInfo.Type = AddressType.PrgRom; 526 527 ctrlDebuggerCode.LineStyleProvider.GetBreakpointLineProperties(props, cpuAddress, addressInfo); 528 } 529 530 if(isActiveStatement) { 531 ctrlDebuggerCode.LineStyleProvider.ConfigureActiveStatement(props); 532 } 533 534 return props; 535 } 536 } 537 538 class ScrollbarColorProvider : IScrollbarColorProvider 539 { 540 private Dictionary<int, Color> _breakpointColors = new Dictionary<int, Color>(); 541 542 private ctrlSourceViewer _viewer; ScrollbarColorProvider(ctrlSourceViewer viewer)543 public ScrollbarColorProvider(ctrlSourceViewer viewer) 544 { 545 _viewer = viewer; 546 547 DebugInfo info = ConfigManager.Config.DebugInfo; 548 int len = viewer.ctrlCodeViewer.LineCount; 549 550 int[] relativeAddresses = new int[len]; 551 AddressTypeInfo[] addressInfo = new AddressTypeInfo[len]; 552 for(int i = 0; i < len; i++) { 553 addressInfo[i] = _viewer.GetAddressInfo(i); 554 if(addressInfo[i].Address >= 0) { 555 relativeAddresses[i] = InteropEmu.DebugGetRelativeAddress((uint)addressInfo[i].Address, AddressType.PrgRom); 556 } else { 557 relativeAddresses[i] = -1; 558 } 559 } 560 561 foreach(Breakpoint breakpoint in BreakpointManager.Breakpoints) { 562 for(int i = 0; i < len; i++) { 563 if(breakpoint.Matches(relativeAddresses[i], addressInfo[i])) { 564 Color bpColor = breakpoint.BreakOnExec ? info.CodeExecBreakpointColor : (breakpoint.BreakOnWrite ? info.CodeWriteBreakpointColor : info.CodeReadBreakpointColor); 565 _breakpointColors[i] = bpColor; 566 } 567 } 568 } 569 } 570 GetBackgroundColor(float position)571 public Color GetBackgroundColor(float position) 572 { 573 return Color.Transparent; 574 } 575 GetPreview(int lineIndex)576 public frmCodePreviewTooltip GetPreview(int lineIndex) 577 { 578 if(lineIndex < _viewer.CodeViewer.LineCount) { 579 while(lineIndex > 0 && _viewer.CodeViewer.GetLineNumber(lineIndex) < 0) { 580 lineIndex--; 581 } 582 return new frmCodePreviewTooltip(_viewer.FindForm(), lineIndex, null, _viewer.SymbolProvider, _viewer.CurrentFile); 583 } else { 584 return null; 585 } 586 } 587 GetActiveLine()588 public int GetActiveLine() 589 { 590 if(_viewer._currentActiveAddress.HasValue && _viewer.CurrentFileContainsAddress((int)_viewer._currentActiveAddress.Value)) { 591 return _viewer.ctrlCodeViewer.GetLineIndex((int)_viewer._currentActiveAddress.Value); 592 } else { 593 return -1; 594 } 595 } 596 GetSelectedLine()597 public int GetSelectedLine() 598 { 599 return _viewer.ctrlCodeViewer.SelectedLine; 600 } 601 GetMarkerColor(float position, int linesPerPixel)602 public Color GetMarkerColor(float position, int linesPerPixel) 603 { 604 int lineIndex = (int)((_viewer.ctrlCodeViewer.LineCount - 1) * position); 605 606 Color bpColor; 607 for(int i = 0; i < linesPerPixel; i++) { 608 if(_breakpointColors.TryGetValue(lineIndex + i, out bpColor)) { 609 return bpColor; 610 } 611 } 612 return Color.Transparent; 613 } 614 } 615 #endregion 616 } 617 } 618