private void RegisterProject(IReloadableProject project) { uint filechangeCookie; lock (_registeredProjects) { _registeredProjects.TryGetValue(project, out filechangeCookie); } System.Diagnostics.Debug.Assert(filechangeCookie == VSConstants.VSCOOKIE_NIL); if (filechangeCookie == VSConstants.VSCOOKIE_NIL) { IVsFileChangeEx fileChangeService = _serviceProvider.GetService <IVsFileChangeEx, SVsFileChangeEx>(); if (fileChangeService != null) { int hr = fileChangeService.AdviseFileChange(project.ProjectFile, (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Size), this, out filechangeCookie); System.Diagnostics.Debug.Assert(ErrorHandler.Succeeded(hr) && filechangeCookie != VSConstants.VSCOOKIE_NIL); if (ErrorHandler.Succeeded(hr) && filechangeCookie != VSConstants.VSCOOKIE_NIL) { lock (_registeredProjects) { _registeredProjects.Add(project, filechangeCookie); } } else { throw new COMException(string.Format(VSResources.FailedToWatchProject, project.ProjectFile), hr); } } } }
private void UnregisterProject(IReloadableProject project) { uint filechangeCookie; lock (_registeredProjects) { _registeredProjects.TryGetValue(project, out filechangeCookie); } if (filechangeCookie != VSConstants.VSCOOKIE_NIL) { // Remove watch IVsFileChangeEx fileChangeService = _serviceProvider.GetService <IVsFileChangeEx, SVsFileChangeEx>(); if (fileChangeService != null) { int hr = fileChangeService.UnadviseFileChange(filechangeCookie); System.Diagnostics.Debug.Assert(ErrorHandler.Succeeded(hr)); } // Always remove the watcher from our list lock (_registeredProjects) { _registeredProjects.Remove(project); } } }
/// <summary> /// Called when one of the project files changes. In our case since only one file is watched with each cookie so the list of files /// should be one. /// </summary> public int FilesChanged(uint cChanges, string[] rgpszFile, uint[] grfChange) { if (cChanges == 1 && (grfChange[0] & (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Time)) != 0) { IReloadableProject changedProject = null; lock (_registeredProjects) { changedProject = _registeredProjects.FirstOrDefault(kv => kv.Key.ProjectFile.Equals(rgpszFile[0], StringComparison.OrdinalIgnoreCase)).Key; } if (changedProject != null) { lock (_changedProjects) { if (!_changedProjects.Contains(changedProject)) { _changedProjects.Add(changedProject); } ReloadDelayScheduler.ScheduleAsyncTask(async(ct) => { // Grab the UI thread so that we block until the reload of this set of // projects completes. await _threadHandling.SwitchToUIThread(); if (ct.IsCancellationRequested) { return; } // Get the list of projects and create a new empty list to put new requests List <IReloadableProject> changedProjects; lock (_changedProjects) { changedProjects = _changedProjects; _changedProjects = new List <IReloadableProject>(); } var failedProjects = new List <Tuple <IReloadableProject, ProjectReloadResult> >(); _threadHandling.ExecuteSynchronously(async() => { foreach (var project in changedProjects) { ProjectReloadResult result = await project.ReloadProjectAsync().ConfigureAwait(true); if (result == ProjectReloadResult.ReloadFailed || result == ProjectReloadResult.ReloadFailedProjectDirty) { failedProjects.Add(new Tuple <IReloadableProject, ProjectReloadResult>(project, result)); } } }); ProcessProjectReloadFailures(failedProjects); }); } } } return(VSConstants.S_OK); }
/// <summary> /// Called by reloadable projects upon close go unregister themselves /// Removes the file change watch on the project file. /// </summary> public async Task UnregisterProjectAsync(IReloadableProject project) { Requires.NotNull(project, nameof(project)); await _threadHandling.SwitchToUIThread(); UnregisterProject(project); }
/// <summary> /// Puts up UI to allow the user to decide on the appropriate action for a project which is dirty /// in memory /// </summary> private void ProcessProjectDirtyInMemory(IReloadableProject project) { var buttons = new string[] { VSResources.Ignore, VSResources.Overwrite, VSResources.Discard, VSResources.SaveAs }; var msgText = string.Format(VSResources.ConflictingModificationsPrompt, Path.GetFileNameWithoutExtension(project.ProjectFile)); switch (_dialogServices.ShowMultiChoiceMsgBox(VSResources.ConflictingProjectModificationTitle, msgText, buttons)) { case MultiChoiceMsgBoxResult.Cancel: { break; } case MultiChoiceMsgBoxResult.Button1: { // Ignore break; } case MultiChoiceMsgBoxResult.Button2: { // Overwrite int hr = SaveProject(project); if (ErrorHandler.Failed(hr)) { _userNotificationServices.ReportErrorInfo(hr); } break; } case MultiChoiceMsgBoxResult.Button3: { // Discard ReloadProjectInSolution(project); break; } case MultiChoiceMsgBoxResult.Button4: { // Save as int hr = SaveAsProject(project); if (ErrorHandler.Succeeded(hr)) { ReloadProjectInSolution(project); } else { _userNotificationServices.ReportErrorInfo(hr); } break; } } }
/// <summary> /// Called by reloadable projects to register themselves /// </summary> public async Task RegisterProjectAsync(IReloadableProject project) { Requires.NotNull(project, nameof(project)); await Initialize().ConfigureAwait(false); await _threadHandling.SwitchToUIThread(); RegisterProject(project); }
/// <summary> /// Saves just the project file (does not save dirty documents) /// </summary> int SaveProject(IReloadableProject project) { IVsSolution solution = _serviceProvider.GetService <IVsSolution, SVsSolution>(); __VSSLNSAVEOPTIONS saveOpts = __VSSLNSAVEOPTIONS.SLNSAVEOPT_SkipDocs; VsShellUtilities.GetRDTDocumentInfo(_serviceProvider, project.ProjectFile, out IVsHierarchy hier, out uint itemid, out IVsPersistDocData docData, out uint docCookie); int hr = solution.SaveSolutionElement((uint)saveOpts, project.VsHierarchy, docCookie); return(hr); }
/// <summary> /// Uses SaveAs to save a copy of the project somewhere else. /// </summary> int SaveAsProject(IReloadableProject project) { // Save as needs to go through IPersistFileFormat var persistFileFmt = project.VsHierarchy as IPersistFileFormat; var uishell = _serviceProvider.GetService <IVsUIShell, SVsUIShell>(); int hr = uishell.SaveDocDataToFile(VSSAVEFLAGS.VSSAVE_SaveCopyAs, persistFileFmt, project.ProjectFile, out string newFile, out int canceled); if (ErrorHandler.Succeeded(hr) && canceled == 1) { hr = VSConstants.E_ABORT; } return(hr); }
/// <summary> /// Puts up a prompt appropriate for project which failed the autoload. ignoreAll or reloadAll will be set /// to true if the user selected those options /// </summary> private void ProcessProjectWhichFailedReload(IReloadableProject project, out bool ignoreAll, out bool reloadAll) { ignoreAll = false; reloadAll = false; var buttons = new string[] { VSResources.IgnoreAll, VSResources.Ignore, VSResources.ReloadAll, VSResources.Reload }; // TODO: Consider showing different messages for reload failed and needs forced reload. var msgText = string.Format(VSResources.ProjectModificationsPrompt, Path.GetFileNameWithoutExtension(project.ProjectFile)); switch (_dialogServices.ShowMultiChoiceMsgBox(VSResources.ProjectModificationDlgTitle, msgText, buttons)) { case MultiChoiceMsgBoxResult.Cancel: { break; } case MultiChoiceMsgBoxResult.Button1: { // Ignore All ignoreAll = true; break; } case MultiChoiceMsgBoxResult.Button2: { // Ignore break; } case MultiChoiceMsgBoxResult.Button3: { // Reload all reloadAll = true; ReloadProjectInSolution(project); break; } case MultiChoiceMsgBoxResult.Button4: { // Reload ReloadProjectInSolution(project); break; } } }
/// <summary> /// Helper to use the solution to reload the project. /// Reloading is managed via the ReloadItem() method of our parent hierarchy (solution /// or solution folder). So first we get our parent hierarchy and our itemid in the parent /// hierarchy. /// </summary> void ReloadProjectInSolution(IReloadableProject project) { // Get our parent hierarchy and our itemid in the parent hierarchy. IVsHierarchy parentHier = project.VsHierarchy.GetProperty <IVsHierarchy>(VsHierarchyPropID.ParentHierarchy, null); if (parentHier == null) { ErrorHandler.ThrowOnFailure(VSConstants.E_UNEXPECTED); } uint parentItemid = (uint)project.VsHierarchy.GetProperty <int>(VsHierarchyPropID.ParentHierarchyItemid, unchecked ((int)VSConstants.VSITEMID_NIL)); if (parentItemid == VSConstants.VSITEMID_NIL) { ErrorHandler.ThrowOnFailure(VSConstants.E_UNEXPECTED); } // Now using IVsPersistHierarchyItem2 we reload the project. int hr = ((IVsPersistHierarchyItem2)parentHier).ReloadItem((uint)parentItemid, dwReserved: 0); ErrorHandler.ThrowOnFailure(hr); }