amaiorano updated this revision to Diff 89815.
amaiorano added a comment.

Made changes based on @hans 's feedback.

I looked again at the categories, and I think it makes sense with my changes. 
Here's an updated screenshot that shows what the options menu in Visual Studio 
looks like with these changes with the top-level category name ("LLVM/Clang") 
and page name ("Clang Format") highlighted, as well as the category names for 
the page items ("Format Options" and "Format On Save") highlighted:

F3117958: pasted_file <https://reviews.llvm.org/F3117958>

It would be nice if "Format On Save" was _below_ "Format Options", but Visual 
Studio always orders the page categories in alphabetical order, so this is 
pretty standard.


https://reviews.llvm.org/D29221

Files:
  tools/clang-format-vs/ClangFormat/ClangFormat.csproj
  tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
  tools/clang-format-vs/ClangFormat/RunningDocTableEventsDispatcher.cs
  tools/clang-format-vs/ClangFormat/Vsix.cs

Index: tools/clang-format-vs/ClangFormat/Vsix.cs
===================================================================
--- /dev/null
+++ tools/clang-format-vs/ClangFormat/Vsix.cs
@@ -0,0 +1,96 @@
+using EnvDTE;
+using Microsoft.VisualStudio.Editor;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.TextManager.Interop;
+using System;
+using System.IO;
+
+namespace LLVM.ClangFormat
+{
+    internal sealed class Vsix
+    {
+        /// <summary>
+        /// Returns the currently active view if it is a IWpfTextView.
+        /// </summary>
+        public static IWpfTextView GetCurrentView()
+        {
+            // The SVsTextManager is a service through which we can get the active view.
+            var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));
+            IVsTextView textView;
+            textManager.GetActiveView(1, null, out textView);
+
+            // Now we have the active view as IVsTextView, but the text interfaces we need
+            // are in the IWpfTextView.
+            return VsToWpfTextView(textView);
+        }
+
+        public static bool IsDocumentDirty(Document document)
+        {
+            var textView = GetDocumentView(document);
+            var textDocument = GetTextDocument(textView);
+            return textDocument?.IsDirty == true;
+        }
+
+        public static IWpfTextView GetDocumentView(Document document)
+        {
+            var textView = GetVsTextViewFrompPath(document.FullName);
+            return VsToWpfTextView(textView);
+        }
+
+        public static IWpfTextView VsToWpfTextView(IVsTextView textView)
+        {
+            var userData = (IVsUserData)textView;
+            if (userData == null)
+                return null;
+            Guid guidWpfViewHost = DefGuidList.guidIWpfTextViewHost;
+            object host;
+            userData.GetData(ref guidWpfViewHost, out host);
+            return ((IWpfTextViewHost)host).TextView;
+        }
+
+        public static IVsTextView GetVsTextViewFrompPath(string filePath)
+        {
+            // From http://stackoverflow.com/a/2427368/4039972
+            var dte2 = (EnvDTE80.DTE2)Package.GetGlobalService(typeof(SDTE));
+            var sp = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte2;
+            var serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(sp);
+
+            IVsUIHierarchy uiHierarchy;
+            uint itemID;
+            IVsWindowFrame windowFrame;
+            if (VsShellUtilities.IsDocumentOpen(serviceProvider, filePath, Guid.Empty,
+                out uiHierarchy, out itemID, out windowFrame))
+            {
+                // Get the IVsTextView from the windowFrame.
+                return VsShellUtilities.GetTextView(windowFrame);
+            }
+            return null;
+        }
+
+        public static ITextDocument GetTextDocument(IWpfTextView view)
+        {
+            ITextDocument document;
+            if (view != null && view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))
+                return document;
+            return null;
+        }
+
+        public static string GetDocumentParent(IWpfTextView view)
+        {
+            ITextDocument document = GetTextDocument(view);
+            if (document != null)
+            {
+                return Directory.GetParent(document.FilePath).ToString();
+            }
+            return null;
+        }
+
+        public static string GetDocumentPath(IWpfTextView view)
+        {
+            return GetTextDocument(view)?.FilePath;
+        }
+    }
+}
Index: tools/clang-format-vs/ClangFormat/RunningDocTableEventsDispatcher.cs
===================================================================
--- /dev/null
+++ tools/clang-format-vs/ClangFormat/RunningDocTableEventsDispatcher.cs
@@ -0,0 +1,79 @@
+using EnvDTE;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System.Linq;
+    
+namespace LLVM.ClangFormat
+{
+    // Exposes event sources for IVsRunningDocTableEvents3 events.
+    internal sealed class RunningDocTableEventsDispatcher : IVsRunningDocTableEvents3
+    {
+        private RunningDocumentTable _runningDocumentTable;
+        private DTE _dte;
+
+        public delegate void OnBeforeSaveHander(object sender, Document document);
+        public event OnBeforeSaveHander BeforeSave;
+
+        public RunningDocTableEventsDispatcher(Package package)
+        {
+            _runningDocumentTable = new RunningDocumentTable(package);
+            _runningDocumentTable.Advise(this);
+            _dte = (DTE)Package.GetGlobalService(typeof(DTE));
+        }
+
+        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
+        {
+            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;
+        }
+
+        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)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
+        {
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeSave(uint docCookie)
+        {
+            if (BeforeSave != null)
+            {
+                var document = FindDocumentByCookie(docCookie);
+                if (document != null) // Not sure why this happens sometimes
+                {
+                    BeforeSave(this, FindDocumentByCookie(docCookie));
+                }
+            }
+            return VSConstants.S_OK;
+        }
+
+        private Document FindDocumentByCookie(uint docCookie)
+        {
+            var documentInfo = _runningDocumentTable.GetDocumentInfo(docCookie);
+            return _dte.Documents.Cast<Document>().FirstOrDefault(doc => doc.FullName == documentInfo.Moniker);
+        }
+    }
+}
Index: tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
===================================================================
--- tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
+++ tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
@@ -12,19 +12,19 @@
 //
 //===----------------------------------------------------------------------===//
 
-using Microsoft.VisualStudio.Editor;
+using EnvDTE;
 using Microsoft.VisualStudio.Shell;
 using Microsoft.VisualStudio.Shell.Interop;
 using Microsoft.VisualStudio.Text;
 using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.TextManager.Interop;
 using System;
 using System.Collections;
 using System.ComponentModel;
 using System.ComponentModel.Design;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Xml.Linq;
+using System.Linq;
 
 namespace LLVM.ClangFormat
 {
@@ -36,6 +36,17 @@
         private string fallbackStyle = "LLVM";
         private bool sortIncludes = false;
         private string style = "file";
+        private bool formatOnSave = false;
+        private string formatOnSaveFileExtensions =
+            ".c;.cpp;.cxx;.cc;.tli;.tlh;.h;.hh;.hpp;.hxx;.hh;.inl" +
+            ".java;.js;.ts;.m;.mm;.proto;.protodevel;.td";
+
+        public OptionPageGrid Clone()
+        {
+            // Use MemberwiseClone to copy value types.
+            var clone = (OptionPageGrid)MemberwiseClone();
+            return clone;
+        }
 
         public class StyleConverter : TypeConverter
         {
@@ -74,7 +85,7 @@
             }
         }
 
-        [Category("LLVM/Clang")]
+        [Category("Format Options")]
         [DisplayName("Style")]
         [Description("Coding style, currently supports:\n" +
                      "  - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +
@@ -121,7 +132,7 @@
             }
         }
 
-        [Category("LLVM/Clang")]
+        [Category("Format Options")]
         [DisplayName("Assume Filename")]
         [Description("When reading from stdin, clang-format assumes this " +
                      "filename to look for a style config file (with 'file' style) " +
@@ -142,7 +153,7 @@
             }
         }
 
-        [Category("LLVM/Clang")]
+        [Category("Format Options")]
         [DisplayName("Fallback Style")]
         [Description("The name of the predefined style used as a fallback in case clang-format " +
                      "is invoked with 'file' style, but can not find the configuration file.\n" +
@@ -154,29 +165,57 @@
             set { fallbackStyle = value; }
         }
 
-        [Category("LLVM/Clang")]
+        [Category("Format Options")]
         [DisplayName("Sort includes")]
         [Description("Sort touched include lines.\n\n" +
                      "See also: http://clang.llvm.org/docs/ClangFormat.html.";)]
         public bool SortIncludes
         {
             get { return sortIncludes; }
             set { sortIncludes = value; }
         }
+
+        [Category("Format On Save")]
+        [DisplayName("Enable")]
+        [Description("Enable running clang-format when modified files are saved. " +
+                     "Will only format if Style is found (ignores Fallback Style)."
+            )]
+        public bool FormatOnSave
+        {
+            get { return formatOnSave; }
+            set { formatOnSave = value; }
+        }
+
+        [Category("Format On Save")]
+        [DisplayName("File extensions")]
+        [Description("When formatting on save, clang-format will be applied only to " +
+                     "files with these extensions.")]
+        public string FormatOnSaveFileExtensions
+        {
+            get { return formatOnSaveFileExtensions; }
+            set { formatOnSaveFileExtensions = value; }
+        }
     }
 
     [PackageRegistration(UseManagedResourcesOnly = true)]
     [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
     [ProvideMenuResource("Menus.ctmenu", 1)]
+    [ProvideAutoLoad(UIContextGuids80.SolutionExists)] // Load package on solution load
     [Guid(GuidList.guidClangFormatPkgString)]
     [ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)]
     public sealed class ClangFormatPackage : Package
     {
         #region Package Members
+
+        RunningDocTableEventsDispatcher _runningDocTableEventsDispatcher;
+
         protected override void Initialize()
         {
             base.Initialize();
 
+            _runningDocTableEventsDispatcher = new RunningDocTableEventsDispatcher(this);
+            _runningDocTableEventsDispatcher.BeforeSave += OnBeforeSave;
+
             var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
             if (commandService != null)
             {
@@ -195,6 +234,11 @@
         }
         #endregion
 
+        OptionPageGrid GetUserOptions()
+        {
+            return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
+        }
+
         private void MenuItemCallback(object sender, EventArgs args)
         {
             var mc = sender as System.ComponentModel.Design.MenuCommand;
@@ -204,21 +248,45 @@
             switch (mc.CommandID.ID)
             {
                 case (int)PkgCmdIDList.cmdidClangFormatSelection:
-                    FormatSelection();
+                    FormatSelection(GetUserOptions());
                     break;
 
                 case (int)PkgCmdIDList.cmdidClangFormatDocument:
-                    FormatDocument();
+                    FormatDocument(GetUserOptions());
                     break;
             }
         }
 
+        private static bool FileHasExtension(string filePath, string fileExtensions)
+        {
+            var extensions = fileExtensions.ToLower().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+            return extensions.Contains(Path.GetExtension(filePath).ToLower());
+        }
+
+        private void OnBeforeSave(object sender, Document document)
+        {
+            var options = GetUserOptions();
+
+            if (!options.FormatOnSave)
+                return;
+
+            if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions))
+                return;
+
+            if (!Vsix.IsDocumentDirty(document))
+                return;
+
+            var optionsWithNoFallbackStyle = GetUserOptions().Clone();
+            optionsWithNoFallbackStyle.FallbackStyle = "none";
+            FormatDocument(document, optionsWithNoFallbackStyle);
+        }
+
         /// <summary>
         /// Runs clang-format on the current selection
         /// </summary>
-        private void FormatSelection()
+        private void FormatSelection(OptionPageGrid options)
         {
-            IWpfTextView view = GetCurrentView();
+            IWpfTextView view = Vsix.GetCurrentView();
             if (view == null)
                 // We're not in a text view.
                 return;
@@ -231,34 +299,43 @@
             // of the file.
             if (start >= text.Length && text.Length > 0)
                 start = text.Length - 1;
-            string path = GetDocumentParent(view);
-            string filePath = GetDocumentPath(view);
+            string path = Vsix.GetDocumentParent(view);
+            string filePath = Vsix.GetDocumentPath(view);
 
-            RunClangFormatAndApplyReplacements(text, start, length, path, filePath, view);
+            RunClangFormatAndApplyReplacements(text, start, length, path, filePath, options, view);
         }
 
         /// <summary>
         /// Runs clang-format on the current document
         /// </summary>
-        private void FormatDocument()
+        private void FormatDocument(OptionPageGrid options)
+        {
+            FormatView(Vsix.GetCurrentView(), options);
+        }
+
+        private void FormatDocument(Document document, OptionPageGrid options)
+        {
+            FormatView(Vsix.GetDocumentView(document), options);
+        }
+
+        private void FormatView(IWpfTextView view, OptionPageGrid options)
         {
-            IWpfTextView view = GetCurrentView();
             if (view == null)
                 // We're not in a text view.
                 return;
 
-            string filePath = GetDocumentPath(view);
+            string filePath = Vsix.GetDocumentPath(view);
             var path = Path.GetDirectoryName(filePath);
             string text = view.TextBuffer.CurrentSnapshot.GetText();
 
-            RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, view);
+            RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, options, view);
         }
 
-        private void RunClangFormatAndApplyReplacements(string text, int offset, int length, string path, string filePath, IWpfTextView view)
+        private void RunClangFormatAndApplyReplacements(string text, int offset, int length, string path, string filePath, OptionPageGrid options, IWpfTextView view)
         {
             try
             {
-                string replacements = RunClangFormat(text, offset, length, path, filePath);
+                string replacements = RunClangFormat(text, offset, length, path, filePath, options);
                 ApplyClangFormatReplacements(replacements, view);
             }
             catch (Exception e)
@@ -283,7 +360,7 @@
         /// 
         /// Formats the text range starting at offset of the given length.
         /// </summary>
-        private string RunClangFormat(string text, int offset, int length, string path, string filePath)
+        private static string RunClangFormat(string text, int offset, int length, string path, string filePath, OptionPageGrid options)
         {
             string vsixPath = Path.GetDirectoryName(
                 typeof(ClangFormatPackage).Assembly.Location);
@@ -293,16 +370,16 @@
             process.StartInfo.FileName = vsixPath + "\\clang-format.exe";
             // Poor man's escaping - this will not work when quotes are already escaped
             // in the input (but we don't need more).
-            string style = GetStyle().Replace("\"", "\\\"");
-            string fallbackStyle = GetFallbackStyle().Replace("\"", "\\\"");
+            string style = options.Style.Replace("\"", "\\\"");
+            string fallbackStyle = options.FallbackStyle.Replace("\"", "\\\"");
             process.StartInfo.Arguments = " -offset " + offset +
                                           " -length " + length +
                                           " -output-replacements-xml " +
                                           " -style \"" + style + "\"" +
                                           " -fallback-style \"" + fallbackStyle + "\"";
-            if (GetSortIncludes())
+            if (options.SortIncludes)
               process.StartInfo.Arguments += " -sort-includes ";
-            string assumeFilename = GetAssumeFilename();
+            string assumeFilename = options.AssumeFilename;
             if (string.IsNullOrEmpty(assumeFilename))
                 assumeFilename = filePath;
             if (!string.IsNullOrEmpty(assumeFilename))
@@ -352,7 +429,7 @@
         /// <summary>
         /// Applies the clang-format replacements (xml) to the current view
         /// </summary>
-        private void ApplyClangFormatReplacements(string replacements, IWpfTextView view)
+        private static void ApplyClangFormatReplacements(string replacements, IWpfTextView view)
         {
             // clang-format returns no replacements if input text is empty
             if (replacements.Length == 0)
@@ -369,70 +446,5 @@
             }
             edit.Apply();
         }
-
-        /// <summary>
-        /// Returns the currently active view if it is a IWpfTextView.
-        /// </summary>
-        private IWpfTextView GetCurrentView()
-        {
-            // The SVsTextManager is a service through which we can get the active view.
-            var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));
-            IVsTextView textView;
-            textManager.GetActiveView(1, null, out textView);
-
-            // Now we have the active view as IVsTextView, but the text interfaces we need
-            // are in the IWpfTextView.
-            var userData = (IVsUserData)textView;
-            if (userData == null)
-                return null;
-            Guid guidWpfViewHost = DefGuidList.guidIWpfTextViewHost;
-            object host;
-            userData.GetData(ref guidWpfViewHost, out host);
-            return ((IWpfTextViewHost)host).TextView;
-        }
-
-        private string GetStyle()
-        {
-            var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
-            return page.Style;
-        }
-
-        private string GetAssumeFilename()
-        {
-            var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
-            return page.AssumeFilename;
-        }
-
-        private string GetFallbackStyle()
-        {
-            var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
-            return page.FallbackStyle;
-        }
-
-        private bool GetSortIncludes()
-        {
-            var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
-            return page.SortIncludes;
-        }
-
-        private string GetDocumentParent(IWpfTextView view)
-        {
-            ITextDocument document;
-            if (view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))
-            {
-                return Directory.GetParent(document.FilePath).ToString();
-            }
-            return null;
-        }
-
-        private string GetDocumentPath(IWpfTextView view)
-        {
-            ITextDocument document;
-            if (view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))
-            {
-                return document.FilePath;
-            }
-            return null;
-        }
     }
 }
Index: tools/clang-format-vs/ClangFormat/ClangFormat.csproj
===================================================================
--- tools/clang-format-vs/ClangFormat/ClangFormat.csproj
+++ tools/clang-format-vs/ClangFormat/ClangFormat.csproj
@@ -214,6 +214,8 @@
     </Compile>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="PkgCmdID.cs" />
+    <Compile Include="RunningDocTableEventsDispatcher.cs" />
+    <Compile Include="Vsix.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Resources.resx">
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to