// VsPkg.cs : Implementation of P4Vs // using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel.Design; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using DevDriven.P4Vs.Common; using DevDriven.P4Vs.External; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Constants=EnvDTE.Constants; namespace DevDriven.P4Vs { /// /// This is the class that implements the package exposed by this assembly. /// /// The minimum requirement for a class to be considered a valid package for Visual Studio /// is to implement the IVsPackage interface and register itself with the shell. /// This package uses the helper classes defined inside the Managed Package Framework (MPF) /// to do it: it derives from the Package class that provides the implementation of the /// IVsPackage interface and uses the registration attributes defined in the framework to /// register itself and its components with the shell. /// // This attribute tells the registration utility (regpkg.exe) that this class needs // to be registered as package. [PackageRegistration(UseManagedResourcesOnly = true)] // A Visual Studio component can be registered under different regitry roots; for instance // when you debug your package you want to register it in the experimental hive. This // attribute specifies the registry root to use if no one is provided to regpkg.exe with // the /root switch. [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")] // This attribute is used to register the informations needed to show the this package // in the Help/About dialog of Visual Studio. [InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)] // In order be loaded inside Visual Studio in a machine that has not the VS SDK installed, // package needs to have a valid load key (it can be requested at // http://msdn.microsoft.com/vstudio/extend/). This attributes tells the shell that this // package has a load key embedded in its resources. [ProvideLoadKey("Standard", "9.0", "P4Vs", "devDriven", 104)] // This attribute is needed to let the shell know that this package exposes some menus. [ProvideMenuResource(1000, 1)] [ProvideAutoLoad("{f1536ef8-92ec-443c-9ed7-fdadf150da82}")] [Guid(GuidList.guidP4VsPkgString)] public sealed class P4VsPackage : Package, IVsRunningDocTableEvents3, IVsTrackProjectDocumentsEvents2 { private IVsRunningDocumentTable docTable; private readonly Guid outputPaneGuid = new Guid("{D79A528E-656F-432e-BAD8-C5A360A90699}"); private readonly Guid errorPaneGuid = new Guid("{F661E4F6-2317-448a-A792-291F7EE4DFF5}"); private IVsOutputWindowPane outputPane; private MultiLogger logger; private CommandExec exec; private PerforceCmds wrapper; private uint docTableCookie; private IVsTrackProjectDocuments2 trackDocs; private uint trackDocsCookie; private DTE2 dte; private Events2 events; private SolutionEvents solutionEvents; private ProjectItemsEvents projectEvents; private IVsOutputWindowPane errorPane; /// /// Default constructor of the package. /// Inside this method you can place any initialization code that does not require /// any Visual Studio service because at this point the package object is created but /// not sited yet inside Visual Studio environment. The place to do all the other /// initialization is the Initialize method. /// public P4VsPackage() { Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString())); } ///////////////////////////////////////////////////////////////////////////// // Overriden Package Implementation #region Package Members /// /// Initialization of the package; this method is called right after the package is sited, so this is the place /// where you can put all the initilaization code that rely on services provided by VisualStudio. /// protected override void Initialize() { Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString())); base.Initialize(); IMenuCommandService mcs = GetService(typeof (IMenuCommandService)) as IMenuCommandService; if (null != mcs) { OleCommandHandlerGenerator.GenerateCommands(mcs, this); } errorPane = GetOutputPane(errorPaneGuid, "Perforce Errors"); errorPane.Hide(); outputPane = GetOutputPane(outputPaneGuid, "Perforce"); OutputWindowLogger normallogger = new OutputWindowLogger(outputPane, 1, false); OutputWindowLogger errorlogger = new OutputWindowLogger(errorPane, 5, true); logger = new MultiLogger(); logger.Add(normallogger); logger.Add(errorlogger); exec = new CommandExec(logger); wrapper = new PerforceCmds(exec, logger); trackDocs = (IVsTrackProjectDocuments2) GetService(typeof (SVsTrackProjectDocuments)); trackDocs.AdviseTrackProjectDocumentsEvents(this, out trackDocsCookie); docTable = (IVsRunningDocumentTable) GetService(typeof (SVsRunningDocumentTable)); docTable.AdviseRunningDocTableEvents(this, out docTableCookie); dte = (DTE2) GetService(typeof (DTE)); events = (Events2) dte.Events; solutionEvents = events.SolutionEvents; solutionEvents.ProjectAdded += solutionEvents_ProjectAdded; projectEvents = events.ProjectItemsEvents; projectEvents.ItemAdded += projectEvents_ItemAdded; wrapper.CheckInstallation(); } #endregion private void projectEvents_ItemAdded(ProjectItem ProjectItem) { AddItem(ProjectItem); } private void solutionEvents_ProjectAdded(Project project) { wrapper.AddFile(project.FileName); ProjectItems items = project.ProjectItems; AddItems(items); } private void AddItems(ProjectItems items) { foreach (ProjectItem item in items) { AddItem(item); AddItems(item.ProjectItems); } } private void AddItem(ProjectItem item) { for (short i = 0; i < item.FileCount; ++i) { string filename = item.get_FileNames(i); wrapper.AddFile(filename); } } /// /// This function is the callback used to execute a command when the a menu item is clicked. /// See the Initialize method to see how the menu item is associated to this function using /// the OleMenuCommandService service and the MenuCommand class. /// #region IVsRunningDocTableEvents3 Members public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) { return VSConstants.S_OK; } public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) { return VSConstants.S_OK; } public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) { return VSConstants.S_OK; } public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) { return VSConstants.S_OK; } public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) { return VSConstants.S_OK; } public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) { return VSConstants.S_OK; } private string fileSavedAs = null; private uint fileCookie = 0; public int OnAfterSave(uint docCookie) { if (docCookie == fileCookie) { uint flags; uint locks; uint editlocks; string filename; IVsHierarchy hier; IntPtr unk; uint id; docTable.GetDocumentInfo(docCookie, out flags, out locks, out editlocks, out filename, out hier, out id, out unk); if (fileSavedAs == null || fileSavedAs != filename) { wrapper.CheckAfterSave(filename); } } fileSavedAs = null; fileCookie = 0; return VSConstants.S_OK; } public int OnBeforeSave(uint docCookie) { // This is what we are interested in. uint flags; uint locks; uint editlocks; string filename; IVsHierarchy hier; IntPtr unk; uint id; docTable.GetDocumentInfo(docCookie, out flags, out locks, out editlocks, out filename, out hier, out id, out unk); FileInfo fi = new FileInfo(filename); if (fi.Exists) { wrapper.CheckBeforeSave(filename); fileSavedAs = filename; } else { fileSavedAs = null; } fileCookie = docCookie; return VSConstants.S_OK; } #endregion #region IVsTrackProjectDocumentsEvents2 Members public int OnQueryAddFiles(IVsProject pProject, int cFiles, string[] rgpszMkDocuments, VSQUERYADDFILEFLAGS[] rgFlags, VSQUERYADDFILERESULTS[] pSummaryResult, VSQUERYADDFILERESULTS[] rgResults) { return VSConstants.S_OK; } public int OnAfterAddFilesEx(int cProjects, int cFiles, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgpszMkDocuments, VSADDFILEFLAGS[] rgFlags) { return VSConstants.S_OK; } public int OnAfterAddDirectoriesEx(int cProjects, int cDirectories, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgpszMkDocuments, VSADDDIRECTORYFLAGS[] rgFlags) { return VSConstants.S_OK; } public int OnAfterRemoveFiles(int cProjects, int cFiles, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgpszMkDocuments, VSREMOVEFILEFLAGS[] rgFlags) { logger.WriteLine(0, "Got remove for {0} files.", cFiles); StringBuilder sb = new StringBuilder(); sb.AppendLine("Do you wish to delete the following files from perforce?"); List files = new List(); for (int i = 0; i < cFiles; ++i) { string filename = rgpszMkDocuments[i]; files.Add(filename); sb.AppendFormat(" {0}\n", filename); } DialogResult dr = MessageBox.Show(sb.ToString(), "Delete Files?", MessageBoxButtons.YesNo); if (dr != DialogResult.Yes) return VSConstants.S_OK; foreach (string s in files) { wrapper.DeleteFile(s); } return VSConstants.S_OK; } public int OnAfterRemoveDirectories(int cProjects, int cDirectories, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgpszMkDocuments, VSREMOVEDIRECTORYFLAGS[] rgFlags) { return VSConstants.S_OK; } public int OnQueryRenameFiles(IVsProject pProject, int cFiles, string[] rgszMkOldNames, string[] rgszMkNewNames, VSQUERYRENAMEFILEFLAGS[] rgFlags, VSQUERYRENAMEFILERESULTS[] pSummaryResult, VSQUERYRENAMEFILERESULTS[] rgResults) { return VSConstants.S_OK; } public int OnAfterRenameFiles(int cProjects, int cFiles, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgszMkOldNames, string[] rgszMkNewNames, VSRENAMEFILEFLAGS[] rgFlags) { logger.WriteLine(0, "Got rename for {0} files.", cFiles); for (int i = 0; i < cFiles; ++i) { wrapper.RenameFile(rgszMkOldNames[i], rgszMkNewNames[i]); } return VSConstants.S_OK; } public int OnQueryRenameDirectories(IVsProject pProject, int cDirs, string[] rgszMkOldNames, string[] rgszMkNewNames, VSQUERYRENAMEDIRECTORYFLAGS[] rgFlags, VSQUERYRENAMEDIRECTORYRESULTS[] pSummaryResult, VSQUERYRENAMEDIRECTORYRESULTS[] rgResults) { return VSConstants.S_OK; } public int OnAfterRenameDirectories(int cProjects, int cDirs, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgszMkOldNames, string[] rgszMkNewNames, VSRENAMEDIRECTORYFLAGS[] rgFlags) { return VSConstants.S_OK; } public int OnQueryAddDirectories(IVsProject pProject, int cDirectories, string[] rgpszMkDocuments, VSQUERYADDDIRECTORYFLAGS[] rgFlags, VSQUERYADDDIRECTORYRESULTS[] pSummaryResult, VSQUERYADDDIRECTORYRESULTS[] rgResults) { return VSConstants.S_OK; } public int OnQueryRemoveFiles(IVsProject pProject, int cFiles, string[] rgpszMkDocuments, VSQUERYREMOVEFILEFLAGS[] rgFlags, VSQUERYREMOVEFILERESULTS[] pSummaryResult, VSQUERYREMOVEFILERESULTS[] rgResults) { return VSConstants.S_OK; } public int OnQueryRemoveDirectories(IVsProject pProject, int cDirectories, string[] rgpszMkDocuments, VSQUERYREMOVEDIRECTORYFLAGS[] rgFlags, VSQUERYREMOVEDIRECTORYRESULTS[] pSummaryResult, VSQUERYREMOVEDIRECTORYRESULTS[] rgResults) { return VSConstants.S_OK; } public int OnAfterSccStatusChanged(int cProjects, int cFiles, IVsProject[] rgpProjects, int[] rgFirstIndices, string[] rgpszMkDocuments, uint[] rgdwSccStatus) { return VSConstants.S_OK; } #endregion #region Command Handlers [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4Diff)] private void DiffItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(false); if (CheckItemCount(items, "diff {0} files", 2)) { items.ForEach(wrapper.DisplayDiff); } } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4History)] private void HistoryItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(false); if (CheckItemCount(items, "get history for {0} files", 2)) { items.ForEach(wrapper.DisplayHistory); } } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4Win)] private void WinItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(false); if (CheckItemCount(items, "open p4win for {0} files", 2)) { items.ForEach(wrapper.DisplayBrowser); } } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4Revert)] private void RevertItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(false); if (CheckItemCount(items, "revert {0} files", 1)) { items.ForEach(wrapper.Revert); } } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4Delete)] private void DeleteItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(false); if (CheckItemCount(items, "delete {0} files", 1)) { items.ForEach(wrapper.Revert); } } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4Edit)] private void EditItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(true); items.ForEach(wrapper.OpenForEdit); } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4EditTree)] private void EditTreeItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelectionTree(); items.ForEach(wrapper.OpenForEdit); } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4AddTree)] private void AddTreeItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelectionTree(); items.ForEach(wrapper.AddFile); } [OleCommandHandler(GuidList.guidP4VsCmdSetString, PkgCmdIDList.cmdidRunP4Add)] private void AddItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here List items = GetSolutionSelection(true); items.ForEach(wrapper.AddFile); } private bool CheckItemCount(List items, string what, int threshold) { if (items.Count >= threshold) { string txt = string.Format(what, items.Count); IVsUIShell uiShell = (IVsUIShell) GetService(typeof (SVsUIShell)); Guid clsid = Guid.Empty; int result; uiShell.ShowMessageBox( 0, ref clsid, "P4VsPkg", string.Format(CultureInfo.CurrentCulture, "Request to {0}. Press OK to proceed.", txt), string.Empty, 0, OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_SECOND, OLEMSGICON.OLEMSGICON_WARNING, 0, // false out result); return result == 1; } return true; } #endregion #region UIHierarchy helpers private List GetSolutionSelection(bool includeSubItems) { Window solutionExplorer = dte.Windows.Item(Constants.vsWindowKindSolutionExplorer); UIHierarchy hierarchy = (UIHierarchy) solutionExplorer.Object; IEnumerable selected = hierarchy.SelectedItems as IEnumerable; List items = new List(); foreach (UIHierarchyItem item in selected) { Project p = item.Object as Project; if (p != null) { items.Add(p.FileName); continue; } ProjectItem pi = item.Object as ProjectItem; if (pi != null) { if (pi.SubProject != null) { items.Add(pi.SubProject.FileName); } else { AddFilesFromItem(items, pi, includeSubItems); } continue; } Solution2 soln = item.Object as Solution2; if (soln != null) { items.Add(soln.FileName); continue; } } return items; } private List GetSolutionSelectionTree() { Window solutionExplorer = dte.Windows.Item(Constants.vsWindowKindSolutionExplorer); UIHierarchy hierarchy = (UIHierarchy) solutionExplorer.Object; IEnumerable selected = hierarchy.SelectedItems as IEnumerable; List items = new List(); foreach (UIHierarchyItem item in selected) { ProjectItem pi = item.Object as ProjectItem; if (pi != null) { if (pi.SubProject != null) { AddProject(items, pi.SubProject); } else { AddFilesFromItem(items, pi, true); } continue; } Solution2 soln = item.Object as Solution2; if (soln != null) { items.Add(soln.FileName); AddProjects(items, soln.Projects); continue; } Project p = item.Object as Project; if (p != null) { items.Add(p.FileName); AddProjectItems(items, p.ProjectItems); continue; } } Dictionary unique = new Dictionary(); foreach (string s in items) { unique[s] = s; } items.Clear(); foreach (string s in unique.Values) { items.Add(s); } return items; } private void AddProjects(ICollection items, Projects projects) { foreach (Project project in projects) { AddProject(items, project); } } private void AddProject(ICollection items, Project project) { items.Add(project.FileName); AddProjectItems(items, project.ProjectItems); } private void AddFilesFromItem(ICollection items, ProjectItem pi, bool includeSubItems) { for (short i = 0; i < pi.FileCount; ++i) { if (pi.SubProject != null) { AddProject(items, pi.SubProject); } else { items.Add(pi.get_FileNames(i)); if (includeSubItems) { ProjectItems projectItems = pi.ProjectItems; AddProjectItems(items, projectItems); } } } } private void AddProjectItems(ICollection items, ProjectItems projectItems) { foreach (ProjectItem item in projectItems) { AddFilesFromItem(items, item, true); } } #endregion } }