//\r
//===----------------------------------------------------------------------===//\r
\r
-using Microsoft.VisualStudio.Editor;\r
+using EnvDTE;\r
using Microsoft.VisualStudio.Shell;\r
using Microsoft.VisualStudio.Shell.Interop;\r
using Microsoft.VisualStudio.Text;\r
using Microsoft.VisualStudio.Text.Editor;\r
-using Microsoft.VisualStudio.TextManager.Interop;\r
using System;\r
using System.Collections;\r
using System.ComponentModel;\r
using System.IO;\r
using System.Runtime.InteropServices;\r
using System.Xml.Linq;\r
+using System.Linq;\r
\r
namespace LLVM.ClangFormat\r
{\r
private string fallbackStyle = "LLVM";\r
private bool sortIncludes = false;\r
private string style = "file";\r
+ private bool formatOnSave = false;\r
+ private string formatOnSaveFileExtensions =\r
+ ".c;.cpp;.cxx;.cc;.tli;.tlh;.h;.hh;.hpp;.hxx;.hh;.inl" +\r
+ ".java;.js;.ts;.m;.mm;.proto;.protodevel;.td";\r
+\r
+ public OptionPageGrid Clone()\r
+ {\r
+ // Use MemberwiseClone to copy value types.\r
+ var clone = (OptionPageGrid)MemberwiseClone();\r
+ return clone;\r
+ }\r
\r
public class StyleConverter : TypeConverter\r
{\r
}\r
}\r
\r
- [Category("LLVM/Clang")]\r
+ [Category("Format Options")]\r
[DisplayName("Style")]\r
[Description("Coding style, currently supports:\n" +\r
" - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +\r
}\r
}\r
\r
- [Category("LLVM/Clang")]\r
+ [Category("Format Options")]\r
[DisplayName("Assume Filename")]\r
[Description("When reading from stdin, clang-format assumes this " +\r
"filename to look for a style config file (with 'file' style) " +\r
}\r
}\r
\r
- [Category("LLVM/Clang")]\r
+ [Category("Format Options")]\r
[DisplayName("Fallback Style")]\r
[Description("The name of the predefined style used as a fallback in case clang-format " +\r
"is invoked with 'file' style, but can not find the configuration file.\n" +\r
set { fallbackStyle = value; }\r
}\r
\r
- [Category("LLVM/Clang")]\r
+ [Category("Format Options")]\r
[DisplayName("Sort includes")]\r
[Description("Sort touched include lines.\n\n" +\r
"See also: http://clang.llvm.org/docs/ClangFormat.html.")]\r
get { return sortIncludes; }\r
set { sortIncludes = value; }\r
}\r
+\r
+ [Category("Format On Save")]\r
+ [DisplayName("Enable")]\r
+ [Description("Enable running clang-format when modified files are saved. " +\r
+ "Will only format if Style is found (ignores Fallback Style)."\r
+ )]\r
+ public bool FormatOnSave\r
+ {\r
+ get { return formatOnSave; }\r
+ set { formatOnSave = value; }\r
+ }\r
+\r
+ [Category("Format On Save")]\r
+ [DisplayName("File extensions")]\r
+ [Description("When formatting on save, clang-format will be applied only to " +\r
+ "files with these extensions.")]\r
+ public string FormatOnSaveFileExtensions\r
+ {\r
+ get { return formatOnSaveFileExtensions; }\r
+ set { formatOnSaveFileExtensions = value; }\r
+ }\r
}\r
\r
[PackageRegistration(UseManagedResourcesOnly = true)]\r
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]\r
[ProvideMenuResource("Menus.ctmenu", 1)]\r
+ [ProvideAutoLoad(UIContextGuids80.SolutionExists)] // Load package on solution load\r
[Guid(GuidList.guidClangFormatPkgString)]\r
[ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)]\r
public sealed class ClangFormatPackage : Package\r
{\r
#region Package Members\r
+\r
+ RunningDocTableEventsDispatcher _runningDocTableEventsDispatcher;\r
+\r
protected override void Initialize()\r
{\r
base.Initialize();\r
\r
+ _runningDocTableEventsDispatcher = new RunningDocTableEventsDispatcher(this);\r
+ _runningDocTableEventsDispatcher.BeforeSave += OnBeforeSave;\r
+\r
var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;\r
if (commandService != null)\r
{\r
}\r
#endregion\r
\r
+ OptionPageGrid GetUserOptions()\r
+ {\r
+ return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));\r
+ }\r
+\r
private void MenuItemCallback(object sender, EventArgs args)\r
{\r
var mc = sender as System.ComponentModel.Design.MenuCommand;\r
switch (mc.CommandID.ID)\r
{\r
case (int)PkgCmdIDList.cmdidClangFormatSelection:\r
- FormatSelection();\r
+ FormatSelection(GetUserOptions());\r
break;\r
\r
case (int)PkgCmdIDList.cmdidClangFormatDocument:\r
- FormatDocument();\r
+ FormatDocument(GetUserOptions());\r
break;\r
}\r
}\r
\r
+ private static bool FileHasExtension(string filePath, string fileExtensions)\r
+ {\r
+ var extensions = fileExtensions.ToLower().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);\r
+ return extensions.Contains(Path.GetExtension(filePath).ToLower());\r
+ }\r
+\r
+ private void OnBeforeSave(object sender, Document document)\r
+ {\r
+ var options = GetUserOptions();\r
+\r
+ if (!options.FormatOnSave)\r
+ return;\r
+\r
+ if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions))\r
+ return;\r
+\r
+ if (!Vsix.IsDocumentDirty(document))\r
+ return;\r
+\r
+ var optionsWithNoFallbackStyle = GetUserOptions().Clone();\r
+ optionsWithNoFallbackStyle.FallbackStyle = "none";\r
+ FormatDocument(document, optionsWithNoFallbackStyle);\r
+ }\r
+\r
/// <summary>\r
/// Runs clang-format on the current selection\r
/// </summary>\r
- private void FormatSelection()\r
+ private void FormatSelection(OptionPageGrid options)\r
{\r
- IWpfTextView view = GetCurrentView();\r
+ IWpfTextView view = Vsix.GetCurrentView();\r
if (view == null)\r
// We're not in a text view.\r
return;\r
// of the file.\r
if (start >= text.Length && text.Length > 0)\r
start = text.Length - 1;\r
- string path = GetDocumentParent(view);\r
- string filePath = GetDocumentPath(view);\r
+ string path = Vsix.GetDocumentParent(view);\r
+ string filePath = Vsix.GetDocumentPath(view);\r
\r
- RunClangFormatAndApplyReplacements(text, start, length, path, filePath, view);\r
+ RunClangFormatAndApplyReplacements(text, start, length, path, filePath, options, view);\r
}\r
\r
/// <summary>\r
/// Runs clang-format on the current document\r
/// </summary>\r
- private void FormatDocument()\r
+ private void FormatDocument(OptionPageGrid options)\r
+ {\r
+ FormatView(Vsix.GetCurrentView(), options);\r
+ }\r
+\r
+ private void FormatDocument(Document document, OptionPageGrid options)\r
+ {\r
+ FormatView(Vsix.GetDocumentView(document), options);\r
+ }\r
+\r
+ private void FormatView(IWpfTextView view, OptionPageGrid options)\r
{\r
- IWpfTextView view = GetCurrentView();\r
if (view == null)\r
// We're not in a text view.\r
return;\r
\r
- string filePath = GetDocumentPath(view);\r
+ string filePath = Vsix.GetDocumentPath(view);\r
var path = Path.GetDirectoryName(filePath);\r
string text = view.TextBuffer.CurrentSnapshot.GetText();\r
\r
- RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, view);\r
+ RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, options, view);\r
}\r
\r
- private void RunClangFormatAndApplyReplacements(string text, int offset, int length, string path, string filePath, IWpfTextView view)\r
+ private void RunClangFormatAndApplyReplacements(string text, int offset, int length, string path, string filePath, OptionPageGrid options, IWpfTextView view)\r
{\r
try\r
{\r
- string replacements = RunClangFormat(text, offset, length, path, filePath);\r
+ string replacements = RunClangFormat(text, offset, length, path, filePath, options);\r
ApplyClangFormatReplacements(replacements, view);\r
}\r
catch (Exception e)\r
/// \r
/// Formats the text range starting at offset of the given length.\r
/// </summary>\r
- private string RunClangFormat(string text, int offset, int length, string path, string filePath)\r
+ private static string RunClangFormat(string text, int offset, int length, string path, string filePath, OptionPageGrid options)\r
{\r
string vsixPath = Path.GetDirectoryName(\r
typeof(ClangFormatPackage).Assembly.Location);\r
process.StartInfo.FileName = vsixPath + "\\clang-format.exe";\r
// Poor man's escaping - this will not work when quotes are already escaped\r
// in the input (but we don't need more).\r
- string style = GetStyle().Replace("\"", "\\\"");\r
- string fallbackStyle = GetFallbackStyle().Replace("\"", "\\\"");\r
+ string style = options.Style.Replace("\"", "\\\"");\r
+ string fallbackStyle = options.FallbackStyle.Replace("\"", "\\\"");\r
process.StartInfo.Arguments = " -offset " + offset +\r
" -length " + length +\r
" -output-replacements-xml " +\r
" -style \"" + style + "\"" +\r
" -fallback-style \"" + fallbackStyle + "\"";\r
- if (GetSortIncludes())\r
+ if (options.SortIncludes)\r
process.StartInfo.Arguments += " -sort-includes ";\r
- string assumeFilename = GetAssumeFilename();\r
+ string assumeFilename = options.AssumeFilename;\r
if (string.IsNullOrEmpty(assumeFilename))\r
assumeFilename = filePath;\r
if (!string.IsNullOrEmpty(assumeFilename))\r
/// <summary>\r
/// Applies the clang-format replacements (xml) to the current view\r
/// </summary>\r
- private void ApplyClangFormatReplacements(string replacements, IWpfTextView view)\r
+ private static void ApplyClangFormatReplacements(string replacements, IWpfTextView view)\r
{\r
// clang-format returns no replacements if input text is empty\r
if (replacements.Length == 0)\r
}\r
edit.Apply();\r
}\r
-\r
- /// <summary>\r
- /// Returns the currently active view if it is a IWpfTextView.\r
- /// </summary>\r
- private IWpfTextView GetCurrentView()\r
- {\r
- // The SVsTextManager is a service through which we can get the active view.\r
- var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));\r
- IVsTextView textView;\r
- textManager.GetActiveView(1, null, out textView);\r
-\r
- // Now we have the active view as IVsTextView, but the text interfaces we need\r
- // are in the IWpfTextView.\r
- var userData = (IVsUserData)textView;\r
- if (userData == null)\r
- return null;\r
- Guid guidWpfViewHost = DefGuidList.guidIWpfTextViewHost;\r
- object host;\r
- userData.GetData(ref guidWpfViewHost, out host);\r
- return ((IWpfTextViewHost)host).TextView;\r
- }\r
-\r
- private string GetStyle()\r
- {\r
- var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));\r
- return page.Style;\r
- }\r
-\r
- private string GetAssumeFilename()\r
- {\r
- var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));\r
- return page.AssumeFilename;\r
- }\r
-\r
- private string GetFallbackStyle()\r
- {\r
- var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));\r
- return page.FallbackStyle;\r
- }\r
-\r
- private bool GetSortIncludes()\r
- {\r
- var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));\r
- return page.SortIncludes;\r
- }\r
-\r
- private string GetDocumentParent(IWpfTextView view)\r
- {\r
- ITextDocument document;\r
- if (view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))\r
- {\r
- return Directory.GetParent(document.FilePath).ToString();\r
- }\r
- return null;\r
- }\r
-\r
- private string GetDocumentPath(IWpfTextView view)\r
- {\r
- ITextDocument document;\r
- if (view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))\r
- {\r
- return document.FilePath;\r
- }\r
- return null;\r
- }\r
}\r
}\r
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}