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,9 @@
+
+
+
Index: tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
===================================================================
--- tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
+++ tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
@@ -12,12 +12,11 @@
//
//===----------------------------------------------------------------------===//
-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;
@@ -25,9 +24,16 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Xml.Linq;
+using System.Linq;
namespace LLVM.ClangFormat
{
+ public enum FormatOnSaveMode
+ {
+ AllDocuments,
+ CurrentDocument
+ }
+
[ClassInterface(ClassInterfaceType.AutoDual)]
[CLSCompliant(false), ComVisible(true)]
public class OptionPageGrid : DialogPage
@@ -36,6 +42,18 @@
private string fallbackStyle = "LLVM";
private bool sortIncludes = false;
private string style = "file";
+ private bool formatOnSaveEnabled = false;
+ private FormatOnSaveMode formatOnSaveMode = FormatOnSaveMode.AllDocuments;
+ 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 +92,14 @@
}
}
- [Category("LLVM/Clang")]
+ public class FormatOnSaveModeConverter : TypeConverterUtils.EnumStringTypeConverter
+ {
+ public FormatOnSaveModeConverter() : base(typeof(FormatOnSaveMode))
+ {
+ }
+ }
+
+ [Category("Format Options")]
[DisplayName("Style")]
[Description("Coding style, currently supports:\n" +
" - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +
@@ -121,7 +146,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 +167,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,7 +179,7 @@
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.")]
@@ -163,20 +188,60 @@
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 FormatOnSaveEnabled
+ {
+ get { return formatOnSaveEnabled; }
+ set { formatOnSaveEnabled = value; }
+ }
+
+ [Category("Format On Save")]
+ [DisplayName("Mode")]
+ [Description("How to run clang-format upon saving:\n" +
+ "- All Documents: format all modified documents\n" +
+ "- Current Document: format current document, if modified")]
+ [TypeConverter(typeof(FormatOnSaveModeConverter))]
+ public FormatOnSaveMode FormatOnSaveMode
+ {
+ get { return formatOnSaveMode; }
+ set { formatOnSaveMode = 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 +260,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 +274,61 @@
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.FormatOnSaveEnabled)
+ return;
+
+ if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions))
+ return;
+
+ switch (options.FormatOnSaveMode)
+ {
+ case FormatOnSaveMode.CurrentDocument:
+ IWpfTextView view = VsixUtils.GetCurrentView();
+ if (view == null)
+ // We're not in a text view.
+ return;
+ if (VsixUtils.GetDocumentView(document) != view)
+ // Not the current document
+ return;
+ break;
+
+ case FormatOnSaveMode.AllDocuments:
break;
}
+
+ if (VsixUtils.IsDocumentDirty(document))
+ {
+ var optionsWithNoFallbackStyle = GetUserOptions().Clone();
+ optionsWithNoFallbackStyle.FallbackStyle = "none";
+ FormatDocument(document, optionsWithNoFallbackStyle);
+ }
}
///
/// Runs clang-format on the current selection
///
- private void FormatSelection()
+ private void FormatSelection(OptionPageGrid options)
{
- IWpfTextView view = GetCurrentView();
+ IWpfTextView view = VsixUtils.GetCurrentView();
if (view == null)
// We're not in a text view.
return;
@@ -231,34 +341,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 = VsixUtils.GetDocumentParent(view);
+ string filePath = VsixUtils.GetDocumentPath(view);
- RunClangFormatAndApplyReplacements(text, start, length, path, filePath, view);
+ RunClangFormatAndApplyReplacements(text, start, length, path, filePath, options, view);
}
///
/// Runs clang-format on the current document
///
- private void FormatDocument()
+ private void FormatDocument(OptionPageGrid options)
+ {
+ FormatView(VsixUtils.GetCurrentView(), options);
+ }
+
+ private void FormatDocument(Document document, OptionPageGrid options)
+ {
+ FormatView(VsixUtils.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 = VsixUtils.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 +402,7 @@
///
/// Formats the text range starting at offset of the given length.
///
- 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 +412,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 +471,7 @@
///
/// Applies the clang-format replacements (xml) to the current view
///
- 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 +488,5 @@
}
edit.Apply();
}
-
- ///
- /// Returns the currently active view if it is a IWpfTextView.
- ///
- 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/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().FirstOrDefault(doc => doc.FullName == documentInfo.Moniker);
+ }
+ }
+}
Index: tools/clang-format-vs/ClangFormat/TypeConverterUtils.cs
===================================================================
--- /dev/null
+++ tools/clang-format-vs/ClangFormat/TypeConverterUtils.cs
@@ -0,0 +1,54 @@
+using System;
+using System.ComponentModel;
+using System.Text.RegularExpressions;
+
+namespace LLVM.ClangFormat
+{
+ public sealed class TypeConverterUtils
+ {
+ // Generic enum type converter that automatically converts enums <-> strings by
+ // injecting/removing spaces into camel-cased strings. You must derive your own
+ // type and pass the typeof(YourEnum) to the base constructor, rather than provide
+ // a generic type due to limitations of C# attributes.
+ public class EnumStringTypeConverter : EnumConverter
+ {
+ private Type enumType;
+ public EnumStringTypeConverter(Type enumType) : base(enumType)
+ {
+ this.enumType = enumType;
+ }
+
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ return true;
+
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
+ {
+ string str = value as string;
+
+ if (str != null)
+ {
+ str.Replace(" ", "");
+ return Enum.Parse(enumType, str.Replace(" ", ""), false);
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ var enumString = ((Enum)value).ToString();
+ return Regex.Replace(enumString, "([A-Z])", " $1").Trim();
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+ }
+}
Index: tools/clang-format-vs/ClangFormat/VsixUtils.cs
===================================================================
--- /dev/null
+++ tools/clang-format-vs/ClangFormat/VsixUtils.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 VsixUtils
+ {
+ ///
+ /// Returns the currently active view if it is a IWpfTextView.
+ ///
+ 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;
+ }
+ }
+}