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