Konstantin Koch 8287c54432 Included the Visual Studio extension and made the necessary changes to make it run.
Replaced the old VS templates with ones that offer more flexiblity.
Started replacing the Content Project for the samples with our custom project type.
Inlcuded a basic not yet working AssimpImporter.
2015-04-08 14:50:03 +02:00

486 lines
20 KiB
C#

/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Apache License, Version 2.0, please send an email to
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*
* ***************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Project;
namespace Microsoft.VisualStudio.Navigation {
/// <summary>
/// Inplementation of the service that builds the information to expose to the symbols
/// navigation tools (class view or object browser) from the source files inside a
/// hierarchy.
/// </summary>
public abstract partial class LibraryManager : IDisposable, IVsRunningDocTableEvents {
private readonly CommonPackage/*!*/ _package;
private readonly Dictionary<uint, TextLineEventListener> _documents;
private readonly Dictionary<IVsHierarchy, HierarchyListener> _hierarchies;
private readonly Dictionary<ModuleId, LibraryNode> _files;
private readonly Library _library;
private readonly IVsEditorAdaptersFactoryService _adapterFactory;
private uint _objectManagerCookie;
private uint _runningDocTableCookie;
public LibraryManager(CommonPackage/*!*/ package) {
Contract.Assert(package != null);
_package = package;
_documents = new Dictionary<uint, TextLineEventListener>();
_hierarchies = new Dictionary<IVsHierarchy, HierarchyListener>();
_library = new Library(new Guid(CommonConstants.LibraryGuid));
_library.LibraryCapabilities = (_LIB_FLAGS2)_LIB_FLAGS.LF_PROJECT;
_files = new Dictionary<ModuleId, LibraryNode>();
var model = ((IServiceContainer)package).GetService(typeof(SComponentModel)) as IComponentModel;
_adapterFactory = model.GetService<IVsEditorAdaptersFactoryService>();
// Register our library now so it'll be available for find all references
RegisterLibrary();
}
public Library Library {
get { return _library; }
}
protected abstract LibraryNode CreateLibraryNode(IScopeNode subItem, string namePrefix, IVsHierarchy hierarchy, uint itemid);
public virtual LibraryNode CreateFileLibraryNode(HierarchyNode hierarchy, string name, string filename, LibraryNodeType libraryNodeType) {
return new LibraryNode(name, filename, libraryNodeType);
}
private object GetPackageService(Type/*!*/ type) {
return ((System.IServiceProvider)_package).GetService(type);
}
private void RegisterForRDTEvents() {
if (0 != _runningDocTableCookie) {
return;
}
IVsRunningDocumentTable rdt = GetPackageService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (null != rdt) {
// Do not throw here in case of error, simply skip the registration.
rdt.AdviseRunningDocTableEvents(this, out _runningDocTableCookie);
}
}
private void UnregisterRDTEvents() {
if (0 == _runningDocTableCookie) {
return;
}
IVsRunningDocumentTable rdt = GetPackageService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (null != rdt) {
// Do not throw in case of error.
rdt.UnadviseRunningDocTableEvents(_runningDocTableCookie);
}
_runningDocTableCookie = 0;
}
#region ILibraryManager Members
public void RegisterHierarchy(IVsHierarchy hierarchy) {
if ((null == hierarchy) || _hierarchies.ContainsKey(hierarchy)) {
return;
}
RegisterLibrary();
HierarchyListener listener = new HierarchyListener(hierarchy, this);
listener.StartListening(true);
_hierarchies.Add(hierarchy, listener);
RegisterForRDTEvents();
}
private void RegisterLibrary() {
if (0 == _objectManagerCookie) {
IVsObjectManager2 objManager = GetPackageService(typeof(SVsObjectManager)) as IVsObjectManager2;
if (null == objManager) {
return;
}
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(
objManager.RegisterSimpleLibrary(_library, out _objectManagerCookie));
}
}
public void UnregisterHierarchy(IVsHierarchy hierarchy) {
if ((null == hierarchy) || !_hierarchies.ContainsKey(hierarchy)) {
return;
}
HierarchyListener listener = _hierarchies[hierarchy];
if (null != listener) {
listener.Dispose();
}
_hierarchies.Remove(hierarchy);
if (0 == _hierarchies.Count) {
UnregisterRDTEvents();
}
lock (_files) {
ModuleId[] keys = new ModuleId[_files.Keys.Count];
_files.Keys.CopyTo(keys, 0);
foreach (ModuleId id in keys) {
if (hierarchy.Equals(id.Hierarchy)) {
_library.RemoveNode(_files[id]);
_files.Remove(id);
}
}
}
// Remove the document listeners.
uint[] docKeys = new uint[_documents.Keys.Count];
_documents.Keys.CopyTo(docKeys, 0);
foreach (uint id in docKeys) {
TextLineEventListener docListener = _documents[id];
if (hierarchy.Equals(docListener.FileID.Hierarchy)) {
_documents.Remove(id);
docListener.Dispose();
}
}
}
public void RegisterLineChangeHandler(uint document,
TextLineChangeEvent lineChanged, Action<IVsTextLines> onIdle) {
_documents[document].OnFileChangedImmediate += delegate(object sender, TextLineChange[] changes, int fLast) {
lineChanged(sender, changes, fLast);
};
_documents[document].OnFileChanged += (sender, args) => onIdle(args.TextBuffer);
}
#endregion
#region Library Member Production
/// <summary>
/// Overridden in the base class to receive notifications of when a file should
/// be analyzed for inclusion in the library. The derived class should queue
/// the parsing of the file and when it's complete it should call FileParsed
/// with the provided LibraryTask and an IScopeNode which provides information
/// about the members of the file.
/// </summary>
protected virtual void OnNewFile(LibraryTask task) {
}
/// <summary>
/// Called by derived class when a file has been parsed. The caller should
/// provide the LibraryTask received from the OnNewFile call and an IScopeNode
/// which represents the contents of the library.
///
/// It is safe to call this method from any thread.
/// </summary>
protected void FileParsed(LibraryTask task, IScopeNode scope) {
try {
var project = task.ModuleID.Hierarchy.GetProject().GetCommonProject();
HierarchyNode fileNode = fileNode = project.NodeFromItemId(task.ModuleID.ItemID);
if (fileNode == null) {
return;
}
LibraryNode module = CreateFileLibraryNode(
fileNode,
System.IO.Path.GetFileName(task.FileName),
task.FileName,
LibraryNodeType.PhysicalContainer
);
// TODO: Creating the module tree should be done lazily as needed
// Currently we replace the entire tree and rely upon the libraries
// update count to invalidate the whole thing. We could do this
// finer grained and only update the changed nodes. But then we
// need to make sure we're not mutating lists which are handed out.
CreateModuleTree(module, module, scope, task.FileName + ":", task.ModuleID);
if (null != task.ModuleID) {
LibraryNode previousItem = null;
lock (_files) {
if (_files.TryGetValue(task.ModuleID, out previousItem)) {
_files.Remove(task.ModuleID);
}
}
_library.RemoveNode(previousItem);
}
_library.AddNode(module);
if (null != task.ModuleID) {
lock (_files) {
_files.Add(task.ModuleID, module);
}
}
} catch (COMException) {
// we're shutting down and can't get the project
}
}
private void CreateModuleTree(LibraryNode root, LibraryNode current, IScopeNode scope, string namePrefix, ModuleId moduleId) {
if ((null == root) || (null == scope) || (null == scope.NestedScopes)) {
return;
}
foreach (IScopeNode subItem in scope.NestedScopes) {
LibraryNode newNode = CreateLibraryNode(subItem, namePrefix, moduleId.Hierarchy, moduleId.ItemID);
string newNamePrefix = namePrefix;
// The classes are always added to the root node, the functions to the
// current node.
if ((newNode.NodeType & LibraryNodeType.Classes) != LibraryNodeType.None) {
// Classes are always added to the root.
root.AddNode(newNode);
newNamePrefix = namePrefix + newNode.Name + ".";
} else {
current.AddNode(newNode);
}
// Now use recursion to get the other types.
CreateModuleTree(root, newNode, subItem, newNamePrefix, moduleId);
}
}
#endregion
#region Hierarchy Events
private void OnNewFile(object sender, HierarchyEventArgs args) {
IVsHierarchy hierarchy = sender as IVsHierarchy;
if (null == hierarchy || IsNonMemberItem(hierarchy, args.ItemID)) {
return;
}
ITextBuffer buffer = null;
if (null != args.TextBuffer) {
buffer = _adapterFactory.GetDocumentBuffer(args.TextBuffer);
}
var id = new ModuleId(hierarchy, args.ItemID);
OnNewFile(new LibraryTask(args.CanonicalName, buffer, new ModuleId(hierarchy, args.ItemID)));
}
/// <summary>
/// Handles the delete event, checking to see if this is a project item.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnDeleteFile(object sender, HierarchyEventArgs args) {
IVsHierarchy hierarchy = sender as IVsHierarchy;
if (null == hierarchy || IsNonMemberItem(hierarchy, args.ItemID)) {
return;
}
OnDeleteFile(hierarchy, args);
}
/// <summary>
/// Does a delete w/o checking if it's a non-meber item, for handling the
/// transition from member item to non-member item.
/// </summary>
private void OnDeleteFile(IVsHierarchy hierarchy, HierarchyEventArgs args) {
ModuleId id = new ModuleId(hierarchy, args.ItemID);
LibraryNode node = null;
lock (_files) {
if (_files.TryGetValue(id, out node)) {
_files.Remove(id);
}
}
if (null != node) {
_library.RemoveNode(node);
}
}
private void IsNonMemberItemChanged(object sender, HierarchyEventArgs args) {
IVsHierarchy hierarchy = sender as IVsHierarchy;
if (null == hierarchy) {
return;
}
if (!IsNonMemberItem(hierarchy, args.ItemID)) {
OnNewFile(hierarchy, args);
} else {
OnDeleteFile(hierarchy, args);
}
}
/// <summary>
/// Checks whether this hierarchy item is a project member (on disk items from show all
/// files aren't considered project members).
/// </summary>
protected bool IsNonMemberItem(IVsHierarchy hierarchy, uint itemId) {
object val;
int hr = hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_IsNonMemberItem, out val);
return ErrorHandler.Succeeded(hr) && (bool)val;
}
#endregion
#region IVsRunningDocTableEvents Members
public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) {
if ((grfAttribs & (uint)(__VSRDTATTRIB.RDTA_MkDocument)) == (uint)__VSRDTATTRIB.RDTA_MkDocument) {
IVsRunningDocumentTable rdt = GetPackageService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (rdt != null) {
uint flags, readLocks, editLocks, itemid;
IVsHierarchy hier;
IntPtr docData = IntPtr.Zero;
string moniker;
int hr;
try {
hr = rdt.GetDocumentInfo(docCookie, out flags, out readLocks, out editLocks, out moniker, out hier, out itemid, out docData);
TextLineEventListener listner;
if (_documents.TryGetValue(docCookie, out listner)) {
listner.FileName = moniker;
}
} finally {
if (IntPtr.Zero != docData) {
Marshal.Release(docData);
}
}
}
}
return VSConstants.S_OK;
}
public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) {
return VSConstants.S_OK;
}
public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) {
return VSConstants.S_OK;
}
public int OnAfterSave(uint docCookie) {
return VSConstants.S_OK;
}
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) {
// Check if this document is in the list of the documents.
if (_documents.ContainsKey(docCookie)) {
return VSConstants.S_OK;
}
// Get the information about this document from the RDT.
IVsRunningDocumentTable rdt = GetPackageService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (null != rdt) {
// Note that here we don't want to throw in case of error.
uint flags;
uint readLocks;
uint writeLoks;
string documentMoniker;
IVsHierarchy hierarchy;
uint itemId;
IntPtr unkDocData;
int hr = rdt.GetDocumentInfo(docCookie, out flags, out readLocks, out writeLoks,
out documentMoniker, out hierarchy, out itemId, out unkDocData);
try {
if (Microsoft.VisualStudio.ErrorHandler.Failed(hr) || (IntPtr.Zero == unkDocData)) {
return VSConstants.S_OK;
}
// Check if the herarchy is one of the hierarchies this service is monitoring.
if (!_hierarchies.ContainsKey(hierarchy)) {
// This hierarchy is not monitored, we can exit now.
return VSConstants.S_OK;
}
// Check the file to see if a listener is required.
if (_package.IsRecognizedFile(documentMoniker)) {
return VSConstants.S_OK;
}
// Create the module id for this document.
ModuleId docId = new ModuleId(hierarchy, itemId);
// Try to get the text buffer.
IVsTextLines buffer = Marshal.GetObjectForIUnknown(unkDocData) as IVsTextLines;
// Create the listener.
TextLineEventListener listener = new TextLineEventListener(buffer, documentMoniker, docId);
// Set the event handler for the change event. Note that there is no difference
// between the AddFile and FileChanged operation, so we can use the same handler.
listener.OnFileChanged += new EventHandler<HierarchyEventArgs>(OnNewFile);
// Add the listener to the dictionary, so we will not create it anymore.
_documents.Add(docCookie, listener);
} finally {
if (IntPtr.Zero != unkDocData) {
Marshal.Release(unkDocData);
}
}
}
// Always return success.
return VSConstants.S_OK;
}
public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) {
if ((0 != dwEditLocksRemaining) || (0 != dwReadLocksRemaining)) {
return VSConstants.S_OK;
}
TextLineEventListener listener;
if (!_documents.TryGetValue(docCookie, out listener) || (null == listener)) {
return VSConstants.S_OK;
}
using (listener) {
_documents.Remove(docCookie);
// Now make sure that the information about this file are up to date (e.g. it is
// possible that Class View shows something strange if the file was closed without
// saving the changes).
HierarchyEventArgs args = new HierarchyEventArgs(listener.FileID.ItemID, listener.FileName);
OnNewFile(listener.FileID.Hierarchy, args);
}
return VSConstants.S_OK;
}
#endregion
public void OnIdle(IOleComponentManager compMgr) {
foreach (TextLineEventListener listener in _documents.Values) {
if (compMgr.FContinueIdle() == 0) {
break;
}
listener.OnIdle();
}
}
#region IDisposable Members
public void Dispose() {
// Dispose all the listeners.
foreach (HierarchyListener listener in _hierarchies.Values) {
listener.Dispose();
}
_hierarchies.Clear();
foreach (TextLineEventListener textListener in _documents.Values) {
textListener.Dispose();
}
_documents.Clear();
// Remove this library from the object manager.
if (0 != _objectManagerCookie) {
IVsObjectManager2 mgr = GetPackageService(typeof(SVsObjectManager)) as IVsObjectManager2;
if (null != mgr) {
mgr.UnregisterLibrary(_objectManagerCookie);
}
_objectManagerCookie = 0;
}
// Unregister this object from the RDT events.
UnregisterRDTEvents();
}
#endregion
}
}