1151 lines
51 KiB
C#
1151 lines
51 KiB
C#
|
using System;
|
|||
|
using System.Collections;
|
|||
|
using System.ComponentModel;
|
|||
|
using System.ComponentModel.Design;
|
|||
|
using System.Diagnostics;
|
|||
|
using System.IO;
|
|||
|
using System.Drawing;
|
|||
|
using System.Globalization;
|
|||
|
using System.Windows.Forms;
|
|||
|
using System.Runtime.InteropServices;
|
|||
|
using System.Security.Permissions;
|
|||
|
using Microsoft.VisualStudio;
|
|||
|
using Microsoft.VisualStudio.Shell.Interop;
|
|||
|
using Microsoft.VisualStudio.OLE.Interop;
|
|||
|
using Microsoft.VisualStudio.TextManager.Interop;
|
|||
|
using Microsoft.VisualStudio.Shell;
|
|||
|
using EnvDTE;
|
|||
|
|
|||
|
using ISysServiceProvider = System.IServiceProvider;
|
|||
|
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
|
|||
|
using VSStd97CmdID = Microsoft.VisualStudio.VSConstants.VSStd97CmdID;
|
|||
|
using BismNormalizer.TabularCompare.UI;
|
|||
|
|
|||
|
namespace BismNormalizer
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// This control host the editor (an extended RichTextBox) and is responsible for
|
|||
|
/// handling the commands targeted to the editor as well as saving and loading
|
|||
|
/// the document. This control also implement the search and replace functionalities.
|
|||
|
/// </summary>
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// Having an entry in the new file dialog.
|
|||
|
//
|
|||
|
// For our file type should appear under "General" in the new files dialog, we need the following:-
|
|||
|
// - A .vsdir file in the same directory as NewFileItems.vsdir (generally under Common7\IDE\NewFileItems).
|
|||
|
// In our case the file name is Editor.vsdir but we only require a file with .vsdir extension.
|
|||
|
// - An empty bsmn file in the same directory as NewFileItems.vsdir. In
|
|||
|
// our case we chose TabularCompare.bsmn. Note this file name appears in Editor.vsdir
|
|||
|
// (see vsdir file format below)
|
|||
|
// - Three text strings in our language specific resource. File Resources.resx :-
|
|||
|
// - "Rich Text file" - this is shown next to our icon.
|
|||
|
// - "A blank rich text file" - shown in the description window
|
|||
|
// in the new file dialog.
|
|||
|
// - "TabularCompare" - This is the base file name. New files will initially
|
|||
|
// be named as TabularCompare1.bsmn, TabularCompare2.bsmn... etc.
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// Editor.vsdir contents:-
|
|||
|
// TabularCompare.bsmn|{3085E1D6-A938-478e-BE49-3546C09A1AB1}|#106|80|#109|0|401|0|#107
|
|||
|
//
|
|||
|
// The fields in order are as follows:-
|
|||
|
// - TabularCompare.bsmn - our empty bsmn file
|
|||
|
// - {db16ff5e-400a-4cb7-9fde-cb3eab9d22d2} - our Editor package guid
|
|||
|
// - #106 - the ID of "Rich Text file" in the resource
|
|||
|
// - 80 - the display ordering priority
|
|||
|
// - #109 - the ID of "A blank rich text file" in the resource
|
|||
|
// - 0 - resource dll string (we don't use this)
|
|||
|
// - 401 - the ID of our icon
|
|||
|
// - 0 - various flags (we don't use this - se vsshell.idl)
|
|||
|
// - #107 - the ID of "bsmn"
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
//This is required for Find In files scenario to work properly. This provides a connection point
|
|||
|
//to the event interface
|
|||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
|
|||
|
[ComSourceInterfaces(typeof(IVsTextViewEvents))]
|
|||
|
[ComVisible(true)]
|
|||
|
public sealed class EditorPane : Microsoft.VisualStudio.Shell.WindowPane,
|
|||
|
IVsPersistDocData, //to Enable persistence functionality for document data
|
|||
|
IPersistFileFormat, //to enable the programmatic loading or saving of an object in a format specified by the user.
|
|||
|
IVsFileChangeEvents,//to notify the client when file changes on disk
|
|||
|
IVsDocDataFileChangeControl //to Determine whether changes to files made outside of the editor should be ignored
|
|||
|
{
|
|||
|
private const uint MyFormat = 0;
|
|||
|
private const string MyExtension = ".bsmn";
|
|||
|
|
|||
|
private class EditorProperties
|
|||
|
{
|
|||
|
private EditorPane editor;
|
|||
|
public EditorProperties(EditorPane Editor)
|
|||
|
{
|
|||
|
editor = Editor;
|
|||
|
}
|
|||
|
|
|||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
|||
|
public string FileName => editor.FileName;
|
|||
|
|
|||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
|||
|
public bool DataChanged => editor.DataChanged;
|
|||
|
}
|
|||
|
|
|||
|
#region Fields
|
|||
|
private BismNormalizerPackage bismNormalizerPackage;
|
|||
|
|
|||
|
private string fileName = string.Empty;
|
|||
|
private bool isDirty;
|
|||
|
// Flag true when we are loading the file. It is used to avoid to change the isDirty flag
|
|||
|
// when the changes are related to the load operation.
|
|||
|
private bool loading;
|
|||
|
// This flag is true when we are asking the QueryEditQuerySave service if we can edit the
|
|||
|
// file. It is used to avoid to have more than one request queued.
|
|||
|
private bool gettingCheckoutStatus;
|
|||
|
private ComparisonControl bismNormalizerForm;
|
|||
|
private string name;
|
|||
|
|
|||
|
private Microsoft.VisualStudio.Shell.SelectionContainer selContainer;
|
|||
|
private ITrackSelection trackSel;
|
|||
|
private IVsFileChangeEx vsFileChangeEx;
|
|||
|
|
|||
|
private Timer FileChangeTrigger = new Timer();
|
|||
|
|
|||
|
private Timer FNFStatusbarTrigger = new Timer();
|
|||
|
|
|||
|
private bool fileChangedTimerSet;
|
|||
|
private int ignoreFileChangeLevel;
|
|||
|
//private bool backupObsolete = true;
|
|||
|
private uint vsFileChangeCookie;
|
|||
|
//private string[] fontListArray;
|
|||
|
|
|||
|
//private object findState;
|
|||
|
//private bool lockImage;
|
|||
|
private ArrayList textSpanArray = new ArrayList();
|
|||
|
//private IVsTextImage spTextImage;
|
|||
|
|
|||
|
//private IExtensibleObjectSite extensibleObjectSite;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
public ComparisonControl BismNormalizerForm => bismNormalizerForm;
|
|||
|
|
|||
|
#region "Window.Pane Overrides"
|
|||
|
/// <summary>
|
|||
|
/// Constructor that calls the Microsoft.VisualStudio.Shell.WindowPane constructor then
|
|||
|
/// our initialization functions.
|
|||
|
/// </summary>
|
|||
|
/// <param name="package">Our Package instance.</param>
|
|||
|
public EditorPane(BismNormalizerPackage package)
|
|||
|
: base(null)
|
|||
|
{
|
|||
|
PrivateInit(package);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This is a required override from the Microsoft.VisualStudio.Shell.WindowPane class.
|
|||
|
/// It returns the extended rich text box that we host.
|
|||
|
/// </summary>
|
|||
|
public override IWin32Window Window => this.bismNormalizerForm;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initialization routine for the Editor. Loads the list of properties for the bsmn document
|
|||
|
/// which will show up in the properties window
|
|||
|
/// </summary>
|
|||
|
/// <param name="package"></param>
|
|||
|
private void PrivateInit(BismNormalizerPackage package)
|
|||
|
{
|
|||
|
bismNormalizerPackage = package;
|
|||
|
loading = false;
|
|||
|
gettingCheckoutStatus = false;
|
|||
|
trackSel = null;
|
|||
|
|
|||
|
Control.CheckForIllegalCrossThreadCalls = false;
|
|||
|
// Create an ArrayList to store the objects that can be selected
|
|||
|
ArrayList listObjects = new ArrayList();
|
|||
|
|
|||
|
// Create the object that will show the document's properties
|
|||
|
// on the properties window.
|
|||
|
EditorProperties prop = new EditorProperties(this);
|
|||
|
listObjects.Add(prop);
|
|||
|
|
|||
|
// Create the SelectionContainer object.
|
|||
|
selContainer = new Microsoft.VisualStudio.Shell.SelectionContainer(true, false);
|
|||
|
selContainer.SelectableObjects = listObjects;
|
|||
|
selContainer.SelectedObjects = listObjects;
|
|||
|
|
|||
|
// Create and initialize the editor
|
|||
|
|
|||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EditorPane));
|
|||
|
this.bismNormalizerForm = new ComparisonControl();
|
|||
|
this.bismNormalizerForm.BismNormalizerPackage = bismNormalizerPackage;
|
|||
|
this.bismNormalizerForm.ComparisonEditorPane = this;
|
|||
|
bismNormalizerPackage.EditorPanes.Add(this);
|
|||
|
|
|||
|
resources.ApplyResources(this.bismNormalizerForm, "editorControl", CultureInfo.CurrentUICulture);
|
|||
|
|
|||
|
// To show when user can save file
|
|||
|
this.bismNormalizerForm.ComparisonChanged += new System.EventHandler(this.OnComparisonChanged);
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// returns the name of the file currently loaded
|
|||
|
/// </summary>
|
|||
|
public string FileName => fileName;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// returns the name of the window
|
|||
|
/// </summary>
|
|||
|
public string Name
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(name))
|
|||
|
{
|
|||
|
int lastSlash = fileName.LastIndexOf("\\") + 1;
|
|||
|
return fileName.Substring(lastSlash);
|
|||
|
}
|
|||
|
else
|
|||
|
return name;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// returns whether the contents of file have changed since the last save
|
|||
|
/// </summary>
|
|||
|
public bool DataChanged => isDirty;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// returns an instance of the ITrackSelection service object
|
|||
|
/// </summary>
|
|||
|
private ITrackSelection TrackSelection
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (trackSel == null)
|
|||
|
{
|
|||
|
trackSel = (ITrackSelection)GetService(typeof(ITrackSelection));
|
|||
|
}
|
|||
|
return trackSel;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Clean up any resources being used.
|
|||
|
/// </summary>
|
|||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "FileChangeTrigger")]
|
|||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "FNFStatusbarTrigger")]
|
|||
|
protected override void Dispose(bool disposing)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if (disposing)
|
|||
|
{
|
|||
|
if (bismNormalizerForm != null)
|
|||
|
{
|
|||
|
bismNormalizerForm.Dispose();
|
|||
|
bismNormalizerForm = null;
|
|||
|
}
|
|||
|
GC.SuppressFinalize(this);
|
|||
|
}
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
base.Dispose(disposing);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets an instance of the RunningDocumentTable (RDT) service which manages the set of currently open
|
|||
|
/// documents in the environment and then notifies the client that an open document has changed
|
|||
|
/// </summary>
|
|||
|
private void NotifyDocChanged()
|
|||
|
{
|
|||
|
// Make sure that we have a file name
|
|||
|
if (fileName.Length == 0)
|
|||
|
return;
|
|||
|
|
|||
|
// Get a reference to the Running Document Table
|
|||
|
IVsRunningDocumentTable runningDocTable = (IVsRunningDocumentTable)GetService(typeof(SVsRunningDocumentTable));
|
|||
|
|
|||
|
uint docCookie;
|
|||
|
IVsHierarchy hierarchy;
|
|||
|
uint itemID;
|
|||
|
IntPtr docData = IntPtr.Zero;
|
|||
|
|
|||
|
try {
|
|||
|
// Lock the document
|
|||
|
int hr = runningDocTable.FindAndLockDocument(
|
|||
|
(uint)_VSRDTFLAGS.RDT_ReadLock,
|
|||
|
fileName,
|
|||
|
out hierarchy,
|
|||
|
out itemID,
|
|||
|
out docData,
|
|||
|
out docCookie
|
|||
|
);
|
|||
|
|
|||
|
ErrorHandler.ThrowOnFailure(hr);
|
|||
|
|
|||
|
// Send the notification
|
|||
|
hr = runningDocTable.NotifyDocumentChanged(docCookie, (uint)__VSRDTATTRIB.RDTA_DocDataReloaded);
|
|||
|
|
|||
|
// Unlock the document.
|
|||
|
// Note that we have to unlock the document even if the previous call failed.
|
|||
|
ErrorHandler.ThrowOnFailure(runningDocTable.UnlockDocument((uint)_VSRDTFLAGS.RDT_ReadLock, docCookie));
|
|||
|
|
|||
|
// Check ff the call to NotifyDocChanged failed.
|
|||
|
ErrorHandler.ThrowOnFailure(hr);
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
if (docData != IntPtr.Zero)
|
|||
|
Marshal.Release(docData);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
int Microsoft.VisualStudio.OLE.Interop.IPersist.GetClassID(out Guid pClassID)
|
|||
|
{
|
|||
|
pClassID = GuidList.guidBismNormalizerEditorFactory;
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
#region IPersistFileFormat Members
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Notifies the object that it has concluded the Save transaction
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszFilename">Pointer to the file name</param>
|
|||
|
/// <returns>S_OK if the function succeeds</returns>
|
|||
|
// TODO: Add Editor.SaveCompleted implementation
|
|||
|
int IPersistFileFormat.SaveCompleted(string pszFilename) => VSConstants.S_OK;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the path to the object's current working file
|
|||
|
/// </summary>
|
|||
|
/// <param name="ppszFilename">Pointer to the file name</param>
|
|||
|
/// <param name="pnFormatIndex">Value that indicates the current format of the file as a zero based index
|
|||
|
/// into the list of formats. Since we support only a single format, we need to return zero.
|
|||
|
/// Subsequently, we will return a single element in the format list through a call to GetFormatList.</param>
|
|||
|
/// <returns></returns>
|
|||
|
int IPersistFileFormat.GetCurFile(out string ppszFilename, out uint pnFormatIndex)
|
|||
|
{
|
|||
|
// We only support 1 format so return its index
|
|||
|
pnFormatIndex = MyFormat;
|
|||
|
ppszFilename = fileName;
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Initialization for the object
|
|||
|
/// </summary>
|
|||
|
/// <param name="nFormatIndex">Zero based index into the list of formats that indicates the current format
|
|||
|
/// of the file</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IPersistFileFormat.InitNew(uint nFormatIndex)
|
|||
|
{
|
|||
|
if (nFormatIndex != MyFormat)
|
|||
|
{
|
|||
|
return VSConstants.E_INVALIDARG;
|
|||
|
}
|
|||
|
// until someone change the file, we can consider it not dirty as
|
|||
|
// the user would be annoyed if we prompt him to save an empty file
|
|||
|
isDirty = false;
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the class identifier of the editor type
|
|||
|
/// </summary>
|
|||
|
/// <param name="pClassID">pointer to the class identifier</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IPersistFileFormat.GetClassID(out Guid pClassID)
|
|||
|
{
|
|||
|
ErrorHandler.ThrowOnFailure(((Microsoft.VisualStudio.OLE.Interop.IPersist)this).GetClassID(out pClassID));
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Provides the caller with the information necessary to open the standard common "Save As" dialog box.
|
|||
|
/// This returns an enumeration of supported formats, from which the caller selects the appropriate format.
|
|||
|
/// Each string for the format is terminated with a newline (\n) character.
|
|||
|
/// The last string in the buffer must be terminated with the newline character as well.
|
|||
|
/// The first string in each pair is a display string that describes the filter, such as "Text Only
|
|||
|
/// (*.txt)". The second string specifies the filter pattern, such as "*.txt". To specify multiple filter
|
|||
|
/// patterns for a single display string, use a semicolon to separate the patterns: "*.htm;*.html;*.asp".
|
|||
|
/// A pattern string can be a combination of valid file name characters and the asterisk (*) wildcard character.
|
|||
|
/// Do not include spaces in the pattern string. The following string is an example of a file pattern string:
|
|||
|
/// "HTML File (*.htm; *.html; *.asp)\n*.htm;*.html;*.asp\nText File (*.txt)\n*.txt\n."
|
|||
|
/// </summary>
|
|||
|
/// <param name="ppszFormatList">Pointer to a string that contains pairs of format filter strings</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IPersistFileFormat.GetFormatList(out string ppszFormatList)
|
|||
|
{
|
|||
|
char Endline = (char)'\n';
|
|||
|
string FormatList = string.Format(CultureInfo.InvariantCulture, "My Editor (*{0}){1}*{0}{1}{1}", MyExtension, Endline);
|
|||
|
ppszFormatList = FormatList;
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Loads the file content into the textbox
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszFilename">Pointer to the full path name of the file to load</param>
|
|||
|
/// <param name="grfMode">file format mode</param>
|
|||
|
/// <param name="fReadOnly">determines if the file should be opened as read only</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IPersistFileFormat.Load(string pszFilename, uint grfMode, int fReadOnly)
|
|||
|
{
|
|||
|
if (pszFilename == null)
|
|||
|
{
|
|||
|
return VSConstants.E_INVALIDARG;
|
|||
|
}
|
|||
|
|
|||
|
loading = true;
|
|||
|
int hr = VSConstants.S_OK;
|
|||
|
try
|
|||
|
{
|
|||
|
// Show the wait cursor while loading the file
|
|||
|
IVsUIShell VsUiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
|
|||
|
if (VsUiShell != null)
|
|||
|
{
|
|||
|
// Note: we don't want to throw or exit if this call fails, so
|
|||
|
// don't check the return code.
|
|||
|
hr = VsUiShell.SetWaitCursor();
|
|||
|
}
|
|||
|
|
|||
|
// Load the file
|
|||
|
//cbw commented out
|
|||
|
//StreamReader str = new StreamReader(pszFilename);
|
|||
|
//string rtfSignature = "{\\rtf";
|
|||
|
//string lineRead = null;
|
|||
|
//try
|
|||
|
//{
|
|||
|
// lineRead = str.ReadLine();
|
|||
|
//}
|
|||
|
//finally
|
|||
|
//{
|
|||
|
// str.Close();
|
|||
|
//}
|
|||
|
//if (lineRead != null && lineRead.Contains(rtfSignature))
|
|||
|
//{
|
|||
|
// //try loading with Rich Text initially
|
|||
|
// bismNormalizerForm.LoadFile(pszFilename);
|
|||
|
//}
|
|||
|
//else
|
|||
|
//{
|
|||
|
// bismNormalizerForm.LoadFile(pszFilename);
|
|||
|
//}
|
|||
|
|
|||
|
bismNormalizerForm.LoadFile(pszFilename);
|
|||
|
|
|||
|
|
|||
|
isDirty = false;
|
|||
|
|
|||
|
//Determine if the file is read only on the file system
|
|||
|
FileAttributes fileAttrs = File.GetAttributes(pszFilename);
|
|||
|
|
|||
|
int isReadOnly = (int)fileAttrs & (int)FileAttributes.ReadOnly;
|
|||
|
|
|||
|
//Set readonly if either the file is readonly for the user or on the file system
|
|||
|
if (0 == isReadOnly && 0 == fReadOnly)
|
|||
|
SetReadOnly(false);
|
|||
|
else
|
|||
|
SetReadOnly(true);
|
|||
|
|
|||
|
|
|||
|
// Notify to the property window that some of the selected objects are changed
|
|||
|
ITrackSelection track = TrackSelection;
|
|||
|
if (null != track)
|
|||
|
{
|
|||
|
hr = track.OnSelectChange((ISelectionContainer)selContainer);
|
|||
|
if (ErrorHandler.Failed(hr))
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
// Hook up to file change notifications
|
|||
|
if (String.IsNullOrEmpty(fileName) || 0 != String.Compare(fileName, pszFilename, true, CultureInfo.CurrentCulture))
|
|||
|
{
|
|||
|
fileName = pszFilename;
|
|||
|
SetFileChangeNotification(pszFilename, true);
|
|||
|
|
|||
|
// Notify the load or reload
|
|||
|
NotifyDocChanged();
|
|||
|
}
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
loading = false;
|
|||
|
}
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Determines whether an object has changed since being saved to its current file
|
|||
|
/// </summary>
|
|||
|
/// <param name="pfIsDirty">true if the document has changed</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IPersistFileFormat.IsDirty(out int pfIsDirty)
|
|||
|
{
|
|||
|
if (isDirty)
|
|||
|
{
|
|||
|
pfIsDirty = 1;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
pfIsDirty = 0;
|
|||
|
}
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Save the contents of the textbox into the specified file. If doing the save on the same file, we need to
|
|||
|
/// suspend notifications for file changes during the save operation.
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszFilename">Pointer to the file name. If the pszFilename parameter is a null reference
|
|||
|
/// we need to save using the current file
|
|||
|
/// </param>
|
|||
|
/// <param name="remember">Boolean value that indicates whether the pszFileName parameter is to be used
|
|||
|
/// as the current working file.
|
|||
|
/// If remember != 0, pszFileName needs to be made the current file and the dirty flag needs to be cleared after the save.
|
|||
|
/// Also, file notifications need to be enabled for the new file and disabled for the old file
|
|||
|
/// If remember == 0, this save operation is a Save a Copy As operation. In this case,
|
|||
|
/// the current file is unchanged and dirty flag is not cleared
|
|||
|
/// </param>
|
|||
|
/// <param name="nFormatIndex">Zero based index into the list of formats that indicates the format in which
|
|||
|
/// the file will be saved</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IPersistFileFormat.Save(string pszFilename, int fRemember, uint nFormatIndex)
|
|||
|
{
|
|||
|
int hr = VSConstants.S_OK;
|
|||
|
bool doingSaveOnSameFile = false;
|
|||
|
// If file is null or same --> SAVE
|
|||
|
if (pszFilename == null || pszFilename == fileName)
|
|||
|
{
|
|||
|
fRemember = 1;
|
|||
|
doingSaveOnSameFile = true;
|
|||
|
}
|
|||
|
|
|||
|
//Suspend file change notifications for only Save since we don't have notifications setup
|
|||
|
//for SaveAs and SaveCopyAs (as they are different files)
|
|||
|
if (doingSaveOnSameFile)
|
|||
|
this.SuspendFileChangeNotification(pszFilename, 1);
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
bismNormalizerForm.SaveFile(pszFilename);
|
|||
|
}
|
|||
|
catch (ArgumentException)
|
|||
|
{
|
|||
|
hr = VSConstants.E_FAIL;
|
|||
|
}
|
|||
|
catch (IOException)
|
|||
|
{
|
|||
|
hr = VSConstants.E_FAIL;
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
//restore the file change notifications
|
|||
|
if (doingSaveOnSameFile)
|
|||
|
this.SuspendFileChangeNotification(pszFilename, 0);
|
|||
|
}
|
|||
|
|
|||
|
if (VSConstants.E_FAIL == hr)
|
|||
|
return hr;
|
|||
|
|
|||
|
//Save and Save as
|
|||
|
if (fRemember != 0)
|
|||
|
{
|
|||
|
//Save as
|
|||
|
if (null != pszFilename && !fileName.Equals(pszFilename))
|
|||
|
{
|
|||
|
SetFileChangeNotification(fileName, false); //remove notification from old file
|
|||
|
SetFileChangeNotification(pszFilename, true); //add notification for new file
|
|||
|
fileName = pszFilename; //cache the new file name
|
|||
|
}
|
|||
|
isDirty = false;
|
|||
|
SetReadOnly(false); //set read only to false since you were successfully able
|
|||
|
//to save to the new file
|
|||
|
}
|
|||
|
|
|||
|
ITrackSelection track = TrackSelection;
|
|||
|
if (null != track)
|
|||
|
{
|
|||
|
hr = track.OnSelectChange((ISelectionContainer)selContainer);
|
|||
|
}
|
|||
|
|
|||
|
// Since all changes are now saved properly to disk, there's no need for a backup.
|
|||
|
//backupObsolete = false;
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region IVsPersistDocData Members
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used to determine if the document data has changed since the last time it was saved
|
|||
|
/// </summary>
|
|||
|
/// <param name="pfDirty">Will be set to 1 if the data has changed</param>
|
|||
|
/// <returns>S_OK if the function succeeds</returns>
|
|||
|
int IVsPersistDocData.IsDocDataDirty(out int pfDirty) => ((IPersistFileFormat)this).IsDirty(out pfDirty);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Saves the document data. Before actually saving the file, we first need to indicate to the environment
|
|||
|
/// that a file is about to be saved. This is done through the "SVsQueryEditQuerySave" service. We call the
|
|||
|
/// "QuerySaveFile" function on the service instance and then proceed depending on the result returned as follows:
|
|||
|
/// If result is QSR_SaveOK - We go ahead and save the file and the file is not read only at this point.
|
|||
|
/// If result is QSR_ForceSaveAs - We invoke the "Save As" functionality which will bring up the Save file name
|
|||
|
/// dialog
|
|||
|
/// If result is QSR_NoSave_Cancel - We cancel the save operation and indicate that the document could not be saved
|
|||
|
/// by setting the "pfSaveCanceled" flag
|
|||
|
/// If result is QSR_NoSave_Continue - Nothing to do here as the file need not be saved
|
|||
|
/// </summary>
|
|||
|
/// <param name="dwSave">Flags which specify the file save options:
|
|||
|
/// VSSAVE_Save - Saves the current file to itself.
|
|||
|
/// VSSAVE_SaveAs - Prompts the User for a filename and saves the file to the file specified.
|
|||
|
/// VSSAVE_SaveCopyAs - Prompts the user for a filename and saves a copy of the file with a name specified.
|
|||
|
/// VSSAVE_SilentSave - Saves the file without prompting for a name or confirmation.
|
|||
|
/// </param>
|
|||
|
/// <param name="pbstrMkDocumentNew">Pointer to the path to the new document</param>
|
|||
|
/// <param name="pfSaveCanceled">value 1 if the document could not be saved</param>
|
|||
|
/// <returns></returns>
|
|||
|
int IVsPersistDocData.SaveDocData(Microsoft.VisualStudio.Shell.Interop.VSSAVEFLAGS dwSave, out string pbstrMkDocumentNew, out int pfSaveCanceled)
|
|||
|
{
|
|||
|
pbstrMkDocumentNew = null;
|
|||
|
pfSaveCanceled = 0;
|
|||
|
int hr = VSConstants.S_OK;
|
|||
|
|
|||
|
switch (dwSave)
|
|||
|
{
|
|||
|
case VSSAVEFLAGS.VSSAVE_Save:
|
|||
|
case VSSAVEFLAGS.VSSAVE_SilentSave:
|
|||
|
{
|
|||
|
IVsQueryEditQuerySave2 queryEditQuerySave = (IVsQueryEditQuerySave2)GetService(typeof(SVsQueryEditQuerySave));
|
|||
|
|
|||
|
// Call QueryEditQuerySave
|
|||
|
uint result = 0;
|
|||
|
hr = queryEditQuerySave.QuerySaveFile(
|
|||
|
fileName, // filename
|
|||
|
0, // flags
|
|||
|
null, // file attributes
|
|||
|
out result); // result
|
|||
|
if (ErrorHandler.Failed(hr))
|
|||
|
return hr;
|
|||
|
|
|||
|
// Process according to result from QuerySave
|
|||
|
switch ((tagVSQuerySaveResult)result)
|
|||
|
{
|
|||
|
case tagVSQuerySaveResult.QSR_NoSave_Cancel:
|
|||
|
// Note that this is also case tagVSQuerySaveResult.QSR_NoSave_UserCanceled because these
|
|||
|
// two tags have the same value.
|
|||
|
pfSaveCanceled = ~0;
|
|||
|
break;
|
|||
|
|
|||
|
case tagVSQuerySaveResult.QSR_SaveOK:
|
|||
|
{
|
|||
|
// Call the shell to do the save for us
|
|||
|
IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
|
|||
|
hr = uiShell.SaveDocDataToFile(dwSave, (IPersistFileFormat)this, fileName, out pbstrMkDocumentNew, out pfSaveCanceled);
|
|||
|
if (ErrorHandler.Failed(hr))
|
|||
|
return hr;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case tagVSQuerySaveResult.QSR_ForceSaveAs:
|
|||
|
{
|
|||
|
// Call the shell to do the SaveAS for us
|
|||
|
IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
|
|||
|
hr = uiShell.SaveDocDataToFile(VSSAVEFLAGS.VSSAVE_SaveAs, (IPersistFileFormat)this, fileName, out pbstrMkDocumentNew, out pfSaveCanceled);
|
|||
|
if (ErrorHandler.Failed(hr))
|
|||
|
return hr;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case tagVSQuerySaveResult.QSR_NoSave_Continue:
|
|||
|
// In this case there is nothing to do.
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
throw new NotSupportedException("Unsupported result from QEQS");
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
case VSSAVEFLAGS.VSSAVE_SaveAs:
|
|||
|
case VSSAVEFLAGS.VSSAVE_SaveCopyAs:
|
|||
|
{
|
|||
|
// Make sure the file name as the right extension
|
|||
|
if (String.Compare(MyExtension, System.IO.Path.GetExtension(fileName), true, CultureInfo.CurrentCulture) != 0)
|
|||
|
{
|
|||
|
fileName += MyExtension;
|
|||
|
}
|
|||
|
// Call the shell to do the save for us
|
|||
|
IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
|
|||
|
hr = uiShell.SaveDocDataToFile(dwSave, (IPersistFileFormat)this, fileName, out pbstrMkDocumentNew, out pfSaveCanceled);
|
|||
|
if (ErrorHandler.Failed(hr))
|
|||
|
return hr;
|
|||
|
break;
|
|||
|
}
|
|||
|
default:
|
|||
|
throw new ArgumentException("Unsupported Save flag");
|
|||
|
};
|
|||
|
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Loads the document data from the file specified
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszMkDocument">Path to the document file which needs to be loaded</param>
|
|||
|
/// <returns>S_Ok if the method succeeds</returns>
|
|||
|
int IVsPersistDocData.LoadDocData(string pszMkDocument) => ((IPersistFileFormat)this).Load(pszMkDocument, 0, 0);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used to set the initial name for unsaved, newly created document data
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszDocDataPath">String containing the path to the document. We need to ignore this parameter
|
|||
|
/// </param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IVsPersistDocData.SetUntitledDocPath(string pszDocDataPath)
|
|||
|
{
|
|||
|
name = pszDocDataPath;
|
|||
|
return ((IPersistFileFormat)this).InitNew(MyFormat);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the Guid of the editor factory that created the IVsPersistDocData object
|
|||
|
/// </summary>
|
|||
|
/// <param name="pClassID">Pointer to the class identifier of the editor type</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IVsPersistDocData.GetGuidEditorType(out Guid pClassID) => ((IPersistFileFormat)this).GetClassID(out pClassID);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Close the IVsPersistDocData object
|
|||
|
/// </summary>
|
|||
|
/// <returns>S_OK if the function succeeds</returns>
|
|||
|
int IVsPersistDocData.Close()
|
|||
|
{
|
|||
|
if (bismNormalizerPackage.EditorPanes.Contains(this))
|
|||
|
{
|
|||
|
bismNormalizerPackage.EditorPanes.Remove(this);
|
|||
|
}
|
|||
|
//doing from ComparisonControl.OnHandleDestroyed instead
|
|||
|
//if (bismNormalizerForm != null)
|
|||
|
//{
|
|||
|
// bismNormalizerForm.ClearMessages();
|
|||
|
//}
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Determines if it is possible to reload the document data
|
|||
|
/// </summary>
|
|||
|
/// <param name="pfReloadable">set to 1 if the document can be reloaded</param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IVsPersistDocData.IsDocDataReloadable(out int pfReloadable)
|
|||
|
{
|
|||
|
// Allow file to be reloaded
|
|||
|
pfReloadable = 1;
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Renames the document data
|
|||
|
/// </summary>
|
|||
|
/// <param name="grfAttribs"></param>
|
|||
|
/// <param name="pHierNew"></param>
|
|||
|
/// <param name="itemidNew"></param>
|
|||
|
/// <param name="pszMkDocumentNew"></param>
|
|||
|
/// <returns></returns>
|
|||
|
// TODO: Add EditorPane.RenameDocData implementation
|
|||
|
int IVsPersistDocData.RenameDocData(uint grfAttribs, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) => VSConstants.S_OK;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reloads the document data
|
|||
|
/// </summary>
|
|||
|
/// <param name="grfFlags">Flag indicating whether to ignore the next file change when reloading the document data.
|
|||
|
/// This flag should not be set for us since we implement the "IVsDocDataFileChangeControl" interface in order to
|
|||
|
/// indicate ignoring of file changes
|
|||
|
/// </param>
|
|||
|
/// <returns>S_OK if the method succeeds</returns>
|
|||
|
int IVsPersistDocData.ReloadDocData(uint grfFlags) => ((IPersistFileFormat)this).Load(fileName, grfFlags, 0);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Called by the Running Document Table when it registers the document data.
|
|||
|
/// </summary>
|
|||
|
/// <param name="docCookie">Handle for the document to be registered</param>
|
|||
|
/// <param name="pHierNew">Pointer to the IVsHierarchy interface</param>
|
|||
|
/// <param name="itemidNew">Item identifier of the document to be registered from VSITEM</param>
|
|||
|
/// <returns></returns>
|
|||
|
//Nothing to do here
|
|||
|
int IVsPersistDocData.OnRegisterDocData(uint docCookie, IVsHierarchy pHierNew, uint itemidNew) => VSConstants.S_OK;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region IVsFileChangeEvents Members
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Notify the editor of the changes made to one or more files
|
|||
|
/// </summary>
|
|||
|
/// <param name="cChanges">Number of files that have changed</param>
|
|||
|
/// <param name="rgpszFile">array of the files names that have changed</param>
|
|||
|
/// <param name="rggrfChange">Array of the flags indicating the type of changes</param>
|
|||
|
/// <returns></returns>
|
|||
|
int IVsFileChangeEvents.FilesChanged(uint cChanges, string[] rgpszFile, uint[] rggrfChange)
|
|||
|
{
|
|||
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "\t**** Inside FilesChanged ****"));
|
|||
|
|
|||
|
//check the different parameters
|
|||
|
if (0 == cChanges || null == rgpszFile || null == rggrfChange)
|
|||
|
return VSConstants.E_INVALIDARG;
|
|||
|
|
|||
|
//ignore file changes if we are in that mode
|
|||
|
if (ignoreFileChangeLevel != 0)
|
|||
|
return VSConstants.S_OK;
|
|||
|
|
|||
|
for (uint i = 0; i < cChanges; i++)
|
|||
|
{
|
|||
|
if (!String.IsNullOrEmpty(rgpszFile[i]) && String.Compare(rgpszFile[i], fileName, true, CultureInfo.CurrentCulture) == 0)
|
|||
|
{
|
|||
|
// if the readonly state (file attributes) have changed we can immediately update
|
|||
|
// the editor to match the new state (either readonly or not readonly) immediately
|
|||
|
// without prompting the user.
|
|||
|
if (0 != (rggrfChange[i] & (int)_VSFILECHANGEFLAGS.VSFILECHG_Attr))
|
|||
|
{
|
|||
|
FileAttributes fileAttrs = File.GetAttributes(fileName);
|
|||
|
int isReadOnly = (int)fileAttrs & (int)FileAttributes.ReadOnly;
|
|||
|
SetReadOnly(isReadOnly != 0);
|
|||
|
}
|
|||
|
// if it looks like the file contents have changed (either the size or the modified
|
|||
|
// time has changed) then we need to prompt the user to see if we should reload the
|
|||
|
// file. it is important to not synchronously reload the file inside of this FilesChanged
|
|||
|
// notification. first it is possible that there will be more than one FilesChanged
|
|||
|
// notification being sent (sometimes you get separate notifications for file attribute
|
|||
|
// changing and file size/time changing). also it is the preferred UI style to not
|
|||
|
// prompt the user until the user re-activates the environment application window.
|
|||
|
// this is why we use a timer to delay prompting the user.
|
|||
|
if (0 != (rggrfChange[i] & (int)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Size)))
|
|||
|
{
|
|||
|
if (!fileChangedTimerSet)
|
|||
|
{
|
|||
|
FileChangeTrigger = new Timer();
|
|||
|
fileChangedTimerSet = true;
|
|||
|
FileChangeTrigger.Interval = 1000;
|
|||
|
FileChangeTrigger.Tick += new EventHandler(this.OnFileChangeEvent);
|
|||
|
FileChangeTrigger.Enabled = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Notify the editor of the changes made to a directory
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszDirectory">Name of the directory that has changed</param>
|
|||
|
/// <returns></returns>
|
|||
|
//Nothing to do here
|
|||
|
int IVsFileChangeEvents.DirectoryChanged(string pszDirectory) => VSConstants.S_OK;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region IVsDocDataFileChangeControl Members
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used to determine whether changes to DocData in files should be ignored or not
|
|||
|
/// </summary>
|
|||
|
/// <param name="fIgnore">a non zero value indicates that the file changes should be ignored
|
|||
|
/// </param>
|
|||
|
/// <returns></returns>
|
|||
|
int IVsDocDataFileChangeControl.IgnoreFileChanges(int fIgnore)
|
|||
|
{
|
|||
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "\t **** Inside IgnoreFileChanges ****"));
|
|||
|
|
|||
|
if (fIgnore != 0)
|
|||
|
{
|
|||
|
ignoreFileChangeLevel++;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (ignoreFileChangeLevel > 0)
|
|||
|
ignoreFileChangeLevel--;
|
|||
|
|
|||
|
// We need to check here if our file has changed from "Read Only"
|
|||
|
// to "Read/Write" or vice versa while the ignore level was non-zero.
|
|||
|
// This may happen when a file is checked in or out under source
|
|||
|
// code control. We need to check here so we can update our caption.
|
|||
|
if (File.Exists(fileName))
|
|||
|
{
|
|||
|
FileAttributes fileAttrs = File.GetAttributes(fileName);
|
|||
|
int isReadOnly = (int)fileAttrs & (int)FileAttributes.ReadOnly;
|
|||
|
SetReadOnly(isReadOnly != 0);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (this.bismNormalizerPackage != null)
|
|||
|
{
|
|||
|
this.bismNormalizerPackage.ShowMessage("Changing file name while editor is open is not supported. Please close the file and reopen.", OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGICON.OLEMSGICON_WARNING);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return VSConstants.S_OK;
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region File Change Notification Helpers
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// In this function we inform the shell when we wish to receive
|
|||
|
/// events when our file is changed or we inform the shell when
|
|||
|
/// we wish not to receive events anymore.
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszFileName">File name string</param>
|
|||
|
/// <param name="fStart">TRUE indicates advise, FALSE indicates unadvise.</param>
|
|||
|
/// <returns>Result of the operation</returns>
|
|||
|
private int SetFileChangeNotification(string pszFileName, bool fStart)
|
|||
|
{
|
|||
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "\t **** Inside SetFileChangeNotification ****"));
|
|||
|
|
|||
|
int result = VSConstants.E_FAIL;
|
|||
|
|
|||
|
//Get the File Change service
|
|||
|
if (null == vsFileChangeEx)
|
|||
|
vsFileChangeEx = (IVsFileChangeEx)GetService(typeof(SVsFileChangeEx));
|
|||
|
if (null == vsFileChangeEx)
|
|||
|
return VSConstants.E_UNEXPECTED;
|
|||
|
|
|||
|
// Setup Notification if fStart is TRUE, Remove if fStart is FALSE.
|
|||
|
if (fStart)
|
|||
|
{
|
|||
|
if (vsFileChangeCookie == VSConstants.VSCOOKIE_NIL)
|
|||
|
{
|
|||
|
//Receive notifications if either the attributes of the file change or
|
|||
|
//if the size of the file changes or if the last modified time of the file changes
|
|||
|
result = vsFileChangeEx.AdviseFileChange(pszFileName,
|
|||
|
(uint)(_VSFILECHANGEFLAGS.VSFILECHG_Attr | _VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Time),
|
|||
|
(IVsFileChangeEvents)this,
|
|||
|
out vsFileChangeCookie);
|
|||
|
if (vsFileChangeCookie == VSConstants.VSCOOKIE_NIL)
|
|||
|
return VSConstants.E_FAIL;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (vsFileChangeCookie != VSConstants.VSCOOKIE_NIL)
|
|||
|
{
|
|||
|
result = vsFileChangeEx.UnadviseFileChange(vsFileChangeCookie);
|
|||
|
vsFileChangeCookie = VSConstants.VSCOOKIE_NIL;
|
|||
|
}
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// In this function we suspend receiving file change events for
|
|||
|
/// a file or we reinstate a previously suspended file depending
|
|||
|
/// on the value of the given fSuspend flag.
|
|||
|
/// </summary>
|
|||
|
/// <param name="pszFileName">File name string</param>
|
|||
|
/// <param name="fSuspend">TRUE indicates that the events needs to be suspended</param>
|
|||
|
/// <returns></returns>
|
|||
|
|
|||
|
private int SuspendFileChangeNotification(string pszFileName, int fSuspend)
|
|||
|
{
|
|||
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "\t **** Inside SuspendFileChangeNotification ****"));
|
|||
|
|
|||
|
if (null == vsFileChangeEx)
|
|||
|
vsFileChangeEx = (IVsFileChangeEx)GetService(typeof(SVsFileChangeEx));
|
|||
|
if (null == vsFileChangeEx)
|
|||
|
return VSConstants.E_UNEXPECTED;
|
|||
|
|
|||
|
if (0 == fSuspend)
|
|||
|
{
|
|||
|
// we are transitioning from suspended to non-suspended state - so force a
|
|||
|
// sync first to avoid asynchronous notifications of our own change
|
|||
|
if (vsFileChangeEx.SyncFile(pszFileName) == VSConstants.E_FAIL)
|
|||
|
return VSConstants.E_FAIL;
|
|||
|
}
|
|||
|
|
|||
|
//If we use the VSCOOKIE parameter to specify the file, then pszMkDocument parameter
|
|||
|
//must be set to a null reference and vice versa
|
|||
|
return vsFileChangeEx.IgnoreFile(vsFileChangeCookie, null, fSuspend);
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used to ReadOnly property for the Rich TextBox and correspondingly update the editor caption
|
|||
|
/// </summary>
|
|||
|
/// <param name="_isFileReadOnly">Indicates whether the file loaded is Read Only or not</param>
|
|||
|
private void SetReadOnly(bool _isFileReadOnly)
|
|||
|
{
|
|||
|
//this.bismNormForm.ReadOnly = _isFileReadOnly;
|
|||
|
////update editor caption with "[Read Only]" or "" as necessary
|
|||
|
//IVsWindowFrame frame = (IVsWindowFrame)GetService(typeof(SVsWindowFrame));
|
|||
|
//string editorCaption = "";
|
|||
|
//if (_isFileReadOnly)
|
|||
|
// editorCaption = this.GetResourceString("@100");
|
|||
|
//ErrorHandler.ThrowOnFailure(frame.SetProperty((int)__VSFPROPID.VSFPROPID_EditorCaption, editorCaption));
|
|||
|
//backupObsolete = true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This event is triggered when one of the files loaded into the environment has changed outside of the
|
|||
|
/// editor
|
|||
|
/// </summary>
|
|||
|
/// <param name="sender"></param>
|
|||
|
/// <param name="e"></param>
|
|||
|
private void OnFileChangeEvent(object sender, System.EventArgs e)
|
|||
|
{
|
|||
|
//Disable the timer
|
|||
|
FileChangeTrigger.Enabled = false;
|
|||
|
|
|||
|
|
|||
|
//----------------------
|
|||
|
//cbw, had to do this to avoid error that package is not sited
|
|||
|
IVsResourceManager resourceManager = (IVsResourceManager)GetService(typeof(SVsResourceManager));
|
|||
|
if (resourceManager == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
//----------------------
|
|||
|
|
|||
|
|
|||
|
|
|||
|
string message = this.GetResourceString("@101"); //get the message string from the resource
|
|||
|
IVsUIShell VsUiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
|
|||
|
int result = 0;
|
|||
|
Guid tempGuid = Guid.Empty;
|
|||
|
if (VsUiShell != null)
|
|||
|
{
|
|||
|
//Show up a message box indicating that the file has changed outside of VS environment
|
|||
|
ErrorHandler.ThrowOnFailure(VsUiShell.ShowMessageBox(0, ref tempGuid, fileName, message, null, 0,
|
|||
|
OLEMSGBUTTON.OLEMSGBUTTON_YESNOCANCEL, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
|
|||
|
OLEMSGICON.OLEMSGICON_QUERY, 0, out result));
|
|||
|
}
|
|||
|
//if the user selects "Yes", reload the current file
|
|||
|
if (result == (int)DialogResult.Yes)
|
|||
|
{
|
|||
|
ErrorHandler.ThrowOnFailure(((IVsPersistDocData)this).ReloadDocData(0));
|
|||
|
}
|
|||
|
|
|||
|
fileChangedTimerSet = false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This method loads a localized string based on the specified resource.
|
|||
|
/// </summary>
|
|||
|
/// <param name="resourceName">Resource to load</param>
|
|||
|
/// <returns>String loaded for the specified resource</returns>
|
|||
|
internal string GetResourceString(string resourceName)
|
|||
|
{
|
|||
|
string resourceValue;
|
|||
|
IVsResourceManager resourceManager = (IVsResourceManager)GetService(typeof(SVsResourceManager));
|
|||
|
if (resourceManager == null)
|
|||
|
{
|
|||
|
throw new InvalidOperationException("Could not get SVsResourceManager service. Make sure the package is Sited before calling this method");
|
|||
|
}
|
|||
|
Guid packageGuid = bismNormalizerPackage.GetType().GUID;
|
|||
|
int hr = resourceManager.LoadResourceString(ref packageGuid, -1, resourceName, out resourceValue);
|
|||
|
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr);
|
|||
|
return resourceValue;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This function asks to the QueryEditQuerySave service if it is possible to
|
|||
|
/// edit the file.
|
|||
|
/// </summary>
|
|||
|
private bool CanEditFile()
|
|||
|
{
|
|||
|
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "\t**** CanEditFile called ****"));
|
|||
|
|
|||
|
// Check the status of the recursion guard
|
|||
|
if (gettingCheckoutStatus)
|
|||
|
return false;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
// Set the recursion guard
|
|||
|
gettingCheckoutStatus = true;
|
|||
|
|
|||
|
// Get the QueryEditQuerySave service
|
|||
|
IVsQueryEditQuerySave2 queryEditQuerySave = (IVsQueryEditQuerySave2)GetService(typeof(SVsQueryEditQuerySave));
|
|||
|
|
|||
|
// Now call the QueryEdit method to find the edit status of this file
|
|||
|
string[] documents = { this.fileName };
|
|||
|
uint result;
|
|||
|
uint outFlags;
|
|||
|
|
|||
|
// Note that this function can popup a dialog to ask the user to checkout the file.
|
|||
|
// When this dialog is visible, it is possible to receive other request to change
|
|||
|
// the file and this is the reason for the recursion guard.
|
|||
|
int hr = queryEditQuerySave.QueryEditFiles(
|
|||
|
0, // Flags
|
|||
|
1, // Number of elements in the array
|
|||
|
documents, // Files to edit
|
|||
|
null, // Input flags
|
|||
|
null, // Input array of VSQEQS_FILE_ATTRIBUTE_DATA
|
|||
|
out result, // result of the checkout
|
|||
|
out outFlags // Additional flags
|
|||
|
);
|
|||
|
if (ErrorHandler.Succeeded(hr) && (result == (uint)tagVSQueryEditResult.QER_EditOK))
|
|||
|
{
|
|||
|
// In this case (and only in this case) we can return true from this function.
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
finally
|
|||
|
{
|
|||
|
gettingCheckoutStatus = false;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// This event is triggered when there contents of the file are changed inside the editor
|
|||
|
/// </summary>
|
|||
|
/// <param name="sender"></param>
|
|||
|
/// <param name="e"></param>
|
|||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Microsoft.VisualStudio.Shell.Interop.ITrackSelection.OnSelectChange(Microsoft.VisualStudio.Shell.Interop.ISelectionContainer)")]
|
|||
|
private void OnComparisonChanged(object sender, System.EventArgs e)
|
|||
|
{
|
|||
|
// During the load operation the text of the control will change, but
|
|||
|
// this change must not be stored in the status of the document.
|
|||
|
if (!loading)
|
|||
|
{
|
|||
|
// The only interesting case is when we are changing the document
|
|||
|
// for the first time
|
|||
|
if (!isDirty)
|
|||
|
{
|
|||
|
// Check if the QueryEditQuerySave service allow us to change the file
|
|||
|
if (!CanEditFile())
|
|||
|
{
|
|||
|
// We can not change the file (e.g. a checkout operation failed),
|
|||
|
// so undo the change and exit.
|
|||
|
|
|||
|
//cbw todo :bismNormForm.RichTextBoxControl.Undo();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// It is possible to change the file, so update the status.
|
|||
|
isDirty = true;
|
|||
|
ITrackSelection track = TrackSelection;
|
|||
|
if (null != track)
|
|||
|
{
|
|||
|
// Note: here we don't need to check the return code.
|
|||
|
track.OnSelectChange((ISelectionContainer)selContainer);
|
|||
|
}
|
|||
|
//backupObsolete = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|