486 lines
20 KiB
C#
Raw Normal View History

/* ****************************************************************************
*
* 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
}
}