// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Threading;
using ICSharpCode.AvalonEdit.AddIn;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Core;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpyAddIn;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Editor.Bookmarks;
using ICSharpCode.SharpDevelop.Workbench;
namespace ICSharpCode.ILSpyAddIn
{
///
/// Hosts a decompiled type.
///
class DecompiledViewContent : AbstractViewContentWithoutFile
{
readonly FileName assemblyFile;
readonly string fullTypeName;
public DecompiledTypeReference DecompiledTypeName { get; private set; }
public override FileName PrimaryFileName {
get { return this.DecompiledTypeName.ToFileName(); }
}
///
/// Entity to jump to once decompilation has finished.
///
string jumpToEntityIdStringWhenDecompilationFinished;
bool decompilationFinished;
readonly CodeEditor codeEditor = new CodeEditor();
readonly CancellationTokenSource cancellation = new CancellationTokenSource();
Dictionary memberLocations;
public Dictionary DebugSymbols { get; private set; }
#region Constructor
public DecompiledViewContent(FileName assemblyFile, string fullTypeName, string entityTag)
{
this.DecompiledTypeName = new DecompiledTypeReference(assemblyFile, new FullTypeName(fullTypeName));
this.Services = codeEditor.GetRequiredService();
this.assemblyFile = assemblyFile;
this.fullTypeName = fullTypeName;
this.jumpToEntityIdStringWhenDecompilationFinished = entityTag;
string shortTypeName = fullTypeName.Substring(fullTypeName.LastIndexOf('.') + 1);
this.TitleName = "[" + ReflectionHelper.SplitTypeParameterCountFromReflectionName(shortTypeName) + "]";
DecompilationThread();
// Thread thread = new Thread(DecompilationThread);
// thread.Name = "Decompiler (" + shortTypeName + ")";
// thread.Start();
// thread.Join();
SD.BookmarkManager.BookmarkRemoved += BookmarkManager_Removed;
SD.BookmarkManager.BookmarkAdded += BookmarkManager_Added;
this.codeEditor.FileName = this.DecompiledTypeName.ToFileName();
this.codeEditor.ActiveTextEditor.IsReadOnly = true;
this.codeEditor.ActiveTextEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
}
#endregion
public static DecompiledViewContent Get(DecompiledTypeReference name)
{
var viewContents = SD.Workbench.ViewContentCollection.OfType();
return viewContents.FirstOrDefault(c => c.DecompiledTypeName == name);
}
public static DecompiledViewContent Get(IEntity entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
// Get the underlying entity for generic instance members
if (entity is IMember)
entity = ((IMember)entity).MemberDefinition;
ITypeDefinition declaringType = (entity as ITypeDefinition) ?? entity.DeclaringTypeDefinition;
if (declaringType == null)
return null;
// get the top-level type
while (declaringType.DeclaringTypeDefinition != null)
declaringType = declaringType.DeclaringTypeDefinition;
FileName assemblyLocation = declaringType.ParentAssembly.GetRuntimeAssemblyLocation();
if (assemblyLocation != null && File.Exists(assemblyLocation)) {
return Get(assemblyLocation, declaringType.ReflectionName);
}
return null;
}
public static DecompiledViewContent Get(FileName assemblyFile, string typeName)
{
if (assemblyFile == null)
throw new ArgumentNullException("assemblyFile");
if (string.IsNullOrEmpty(typeName))
throw new ArgumentException("typeName is null or empty");
foreach (var viewContent in SD.Workbench.ViewContentCollection.OfType()) {
if (viewContent.AssemblyFile == assemblyFile && typeName == viewContent.FullTypeName) {
return viewContent;
}
}
var newViewContent = new DecompiledViewContent(assemblyFile, typeName, null);
SD.Workbench.ShowView(newViewContent);
return newViewContent;
}
#region Properties
public FileName AssemblyFile {
get { return assemblyFile; }
}
///
/// The reflection name of the top-level type displayed in this view content.
///
public string FullTypeName {
get { return fullTypeName; }
}
public override object Control {
get { return codeEditor; }
}
public override bool IsReadOnly {
get { return true; }
}
#endregion
#region Dispose
public override void Dispose()
{
cancellation.Cancel();
codeEditor.Dispose();
SD.BookmarkManager.BookmarkAdded -= BookmarkManager_Added;
SD.BookmarkManager.BookmarkRemoved -= BookmarkManager_Removed;
// DecompileInformation data;
// DebuggerDecompilerService.DebugInformation.TryRemove(decompiledType.MetadataToken.ToInt32(), out data);
base.Dispose();
}
#endregion
#region Load/Save
public override void Load()
{
// nothing to do...
}
public override void Save()
{
if (!decompilationFinished)
return;
// TODO: show Save As dialog to allow the user to save the decompiled file
}
#endregion
public override INavigationPoint BuildNavPoint()
{
return codeEditor.BuildNavPoint();
}
#region JumpToEntity
public void JumpToEntity(string entityIdString)
{
if (!decompilationFinished) {
this.jumpToEntityIdStringWhenDecompilationFinished = entityIdString;
return;
}
TextLocation location;
if (entityIdString != null && memberLocations != null && memberLocations.TryGetValue(entityIdString, out location))
codeEditor.JumpTo(location.Line, location.Column);
}
#endregion
#region Decompilation
void DecompilationThread()
{
try {
var file = ILSpyDecompilerService.DecompileType(DecompiledTypeName);
memberLocations = file.MemberLocations;
DebugSymbols = file.DebugSymbols;
OnDecompilationFinished(file.Writer);
} catch (OperationCanceledException) {
// ignore cancellation
} catch (Exception ex) {
if (cancellation.IsCancellationRequested) {
MessageService.ShowException(ex);
return;
}
SD.AnalyticsMonitor.TrackException(ex);
StringWriter writer = new StringWriter();
writer.WriteLine(string.Format("Exception while decompiling {0} ({1})", fullTypeName, assemblyFile));
writer.WriteLine();
writer.WriteLine(ex.ToString());
SD.MainThread.InvokeAsyncAndForget(() => OnDecompilationFinished(writer));
}
}
void OnDecompilationFinished(StringWriter output)
{
if (cancellation.IsCancellationRequested)
return;
codeEditor.Document.Text = output.ToString();
codeEditor.Document.UndoStack.ClearAll();
this.decompilationFinished = true;
JumpToEntity(this.jumpToEntityIdStringWhenDecompilationFinished);
// update UI
//UpdateIconMargin();
// fire events
OnDecompilationFinished(EventArgs.Empty);
}
#endregion
#region Update UI
/*
void UpdateIconMargin()
{
codeView.IconBarManager.UpdateClassMemberBookmarks(
ParserService.ParseFile(tempFileName, new AvalonEditDocumentAdapter(codeView.Document, null)),
null);
// load bookmarks
foreach (SDBookmark bookmark in BookmarkManager.GetBookmarks(this.codeView.TextEditor.FileName)) {
bookmark.Document = this.codeView.TextEditor.Document;
codeView.IconBarManager.Bookmarks.Add(bookmark);
}
}
*/
#endregion
#region Bookmarks
void BookmarkManager_Removed(object sender, BookmarkEventArgs e)
{
var mark = e.Bookmark;
if (mark != null && codeEditor.IconBarManager.Bookmarks.Contains(mark)) {
codeEditor.IconBarManager.Bookmarks.Remove(mark);
mark.Document = null;
}
}
void BookmarkManager_Added(object sender, BookmarkEventArgs e)
{
var mark = e.Bookmark;
if (mark != null && mark.FileName == PrimaryFileName) {
codeEditor.IconBarManager.Bookmarks.Add(mark);
mark.Document = this.codeEditor.Document;
}
}
#endregion
#region Events
public event EventHandler DecompilationFinished;
protected virtual void OnDecompilationFinished(EventArgs e)
{
if (DecompilationFinished != null) {
DecompilationFinished(this, e);
}
}
#endregion
}
}