// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using ICSharpCode.Decompiler; using ICSharpCode.NRefactory.CSharp; using Mono.Cecil; using Mono.CSharp; namespace ICSharpCode.ILSpy.Debugger.Bookmarks { /// /// Static class that maintains the list of bookmarks and breakpoints. /// public static partial class BookmarkManager { static List bookmarks = new List(); public static List Bookmarks { get { return bookmarks; } } public static List GetBookmarks(string typeName) { if (typeName == null) throw new ArgumentNullException("typeName"); List marks = new List(); foreach (BookmarkBase mark in bookmarks) { if (typeName == mark.Member.FullName) { marks.Add(mark); } } return marks; } public static void AddMark(BookmarkBase bookmark) { if (bookmark == null) return; if (bookmarks.Contains(bookmark)) return; if (bookmarks.Exists(b => IsEqualBookmark(b, bookmark))) return; bookmarks.Add(bookmark); OnAdded(new BookmarkEventArgs(bookmark)); } static bool IsEqualBookmark(BookmarkBase a, BookmarkBase b) { if (a == b) return true; if (a == null || b == null) return false; if (a.GetType() != b.GetType()) return false; if (a.Member.FullName != b.Member.FullName) return false; return a.LineNumber == b.LineNumber; } public static void RemoveMark(BookmarkBase bookmark) { bookmarks.Remove(bookmark); OnRemoved(new BookmarkEventArgs(bookmark)); } public static void Clear() { while (bookmarks.Count > 0) { var b = bookmarks[bookmarks.Count - 1]; bookmarks.RemoveAt(bookmarks.Count - 1); OnRemoved(new BookmarkEventArgs(b)); } } internal static void Initialize() { } static void OnRemoved(BookmarkEventArgs e) { if (Removed != null) { Removed(null, e); } } static void OnAdded(BookmarkEventArgs e) { if (Added != null) { Added(null, e); } } public static void ToggleBookmark(string typeName, int line, Predicate canToggle, Func bookmarkFactory) { foreach (BookmarkBase bookmark in GetBookmarks(typeName)) { if (canToggle(bookmark) && bookmark.LineNumber == line) { BookmarkManager.RemoveMark(bookmark); return; } } // no bookmark at that line: create a new bookmark BookmarkManager.AddMark(bookmarkFactory(new AstLocation(line, 0))); } public static event BookmarkEventHandler Removed; public static event BookmarkEventHandler Added; } // This is for the synchronize bookmarks logic public static partial class BookmarkManager { static BookmarkManager() { DebugData.LanguageChanged += OnLanguageChanged; } static void OnLanguageChanged(object sender, LanguageEventArgs e) { var oldLanguage = e.OldLanguage; var newLanguage = e.NewLanguage; SyncCurrentLineBookmark(oldLanguage, newLanguage); SyncBreakpointBookmarks(oldLanguage, newLanguage); } /// /// Synchronize the IL<->C# current line marker. /// /// Old language. /// New language. static void SyncCurrentLineBookmark(DecompiledLanguages oldLanguage, DecompiledLanguages newLanguage) { // checks if (CurrentLineBookmark.Instance == null) return; var oldMappings = CodeMappings.GetStorage(oldLanguage); var newMappings = CodeMappings.GetStorage(newLanguage); if (oldMappings == null || oldMappings.Count == 0 || newMappings == null || newMappings.Count == 0) return; // 1. Save it's data int line = CurrentLineBookmark.Instance.LineNumber; var markerType = CurrentLineBookmark.Instance.Member; // 2. Remove it CurrentLineBookmark.Remove(); // 3. map the marker line uint token; var instruction = oldMappings.GetInstructionByTypeAndLine(markerType.FullName, line, out token); if (instruction == null) return; MemberReference memberReference; int newline; if (newMappings.GetSourceCodeFromMetadataTokenAndOffset(markerType.FullName, token, instruction.ILInstructionOffset.From, out memberReference, out newline)) { // 4. create breakpoint for new languages CurrentLineBookmark.SetPosition(memberReference, newline, 0, newline, 0); } } /// /// Synchronize the IL<->C# breakpoints bookmarks. /// /// Old language. /// New language. static void SyncBreakpointBookmarks(DecompiledLanguages oldLanguage, DecompiledLanguages newLanguage) { // checks var oldMappings = CodeMappings.GetStorage(oldLanguage); var newMappings = CodeMappings.GetStorage(newLanguage); if (oldMappings == null || oldMappings.Count == 0 || newMappings == null || newMappings.Count == 0) return; // 1. map the breakpoint lines var oldbps = bookmarks.FindAll(b => b is BreakpointBookmark && ((BreakpointBookmark)b).Language == oldLanguage); if (oldbps == null || oldbps.Count == 0) return; foreach (var bp in oldbps) { uint token; var instruction = oldMappings.GetInstructionByTypeAndLine(bp.Member.FullName, bp.LineNumber, out token); if (instruction == null) continue; MemberReference memberReference; int line; if (newMappings.GetSourceCodeFromMetadataTokenAndOffset(bp.Member.FullName, token, instruction.ILInstructionOffset.From, out memberReference, out line)) { // 2. create breakpoint for new languages var bookmark = new BreakpointBookmark(memberReference, new AstLocation(line, 0), BreakpointAction.Break, newLanguage); AddMark(bookmark); } } // 3. remove all breakpoints for the old language for (int i = bookmarks.Count - 1; i >= 0; --i) { var bm = bookmarks[i]; if (bm is BreakpointBookmark && ((BreakpointBookmark)bm).Language == oldLanguage) RemoveMark(bm); } } } }