Index: tools/clang-format-vs/ClangFormat/ClangFormat.csproj =================================================================== --- tools/clang-format-vs/ClangFormat/ClangFormat.csproj +++ tools/clang-format-vs/ClangFormat/ClangFormat.csproj @@ -14,7 +14,7 @@ true Key.snk v4.0 - 15.0 + 14.0 @@ -202,18 +202,17 @@ Menus.ctmenu + Designer - - - true true + @@ -251,11 +250,11 @@ if not exist $(ProjectDir)Key.snk ("$(FrameworkSDKDir)Bin\NETFX 4.6 Tools\sn.exe" -k $(ProjectDir)Key.snk) - - + \ No newline at end of file Index: tools/clang-format-vs/ClangFormat/ClangFormat.vsct =================================================================== --- tools/clang-format-vs/ClangFormat/ClangFormat.vsct +++ tools/clang-format-vs/ClangFormat/ClangFormat.vsct @@ -20,12 +20,10 @@ - - - + - + + + + - - - - - + + + + Clang Format + + + - - @@ -61,19 +62,31 @@ DynamicVisibility If you do not want an image next to your command, remove the Icon node /> --> - - + + + + @@ -86,8 +99,7 @@ bitmap strip containing the bitmaps and then there are the numeric ids of the elements used inside a button definition. An important aspect of this declaration is that the element id must be the actual index (1-based) of the bitmap inside the bitmap strip. --> - - + @@ -96,31 +108,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + + + + + - - - - - - + + + + Index: tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs =================================================================== --- tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs +++ tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs @@ -19,6 +19,7 @@ using Microsoft.VisualStudio.Text.Editor; using System; using System.Collections; +using System.Text.RegularExpressions; using System.ComponentModel; using System.ComponentModel.Design; using System.IO; @@ -36,10 +37,13 @@ private string fallbackStyle = "LLVM"; private bool sortIncludes = false; private string style = "file"; + private string altStyle = "file"; private bool formatOnSave = false; - private string formatOnSaveFileExtensions = + private bool altFormatOnOpen = false; + private string formatOnFileExtensions = ".c;.cpp;.cxx;.cc;.tli;.tlh;.h;.hh;.hpp;.hxx;.hh;.inl" + ".java;.js;.ts;.m;.mm;.proto;.protodevel;.td"; + private string formatOnPathFilter = ""; public OptionPageGrid Clone() { @@ -91,6 +95,7 @@ " - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" + " - 'file' to search for a YAML .clang-format or _clang-format\n" + " configuration file.\n" + + " - 'file:' to explicitly specify the configuration file.\n" + " - A YAML configuration snippet.\n\n" + "'File':\n" + " Searches for a .clang-format or _clang-format configuration file\n" + @@ -106,6 +111,16 @@ set { style = value; } } + [Category("Format Options")] + [DisplayName("Style (Alternate)")] + [Description("Alternate Coding style, supports same options as Style.\n")] + [TypeConverter(typeof(StyleConverter))] + public string AltStyle + { + get { return altStyle; } + set { altStyle = value; } + } + public sealed class FilenameConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) @@ -136,7 +151,7 @@ [DisplayName("Assume Filename")] [Description("When reading from stdin, clang-format assumes this " + "filename to look for a style config file (with 'file' style) " + - "and to determine the language.")] + "and to determine the language. Leave blank to use current document's name.")] [TypeConverter(typeof(FilenameConverter))] public string AssumeFilename { @@ -166,8 +181,8 @@ } [Category("Format Options")] - [DisplayName("Sort includes")] - [Description("Sort touched include lines.\n\n" + + [DisplayName("Sort Includes")] + [Description("Sort touched #include lines.\n\n" + "See also: http://clang.llvm.org/docs/ClangFormat.html.")] public bool SortIncludes { @@ -175,8 +190,8 @@ set { sortIncludes = value; } } - [Category("Format On Save")] - [DisplayName("Enable")] + [Category("Format On...")] + [DisplayName("Format On Save")] [Description("Enable running clang-format when modified files are saved. " + "Will only format if Style is found (ignores Fallback Style)." )] @@ -186,14 +201,35 @@ set { formatOnSave = value; } } - [Category("Format On Save")] - [DisplayName("File extensions")] - [Description("When formatting on save, clang-format will be applied only to " + + [Category("Format On...")] + [DisplayName("Format On Open (Alternate)")] + [Description("Enable running clang-format when files are opened. " + + "Will only format if Style (Alternate) is found (ignores Fallback Style)." + )] + public bool AltFormatOnOpen + { + get { return altFormatOnOpen; } + set { altFormatOnOpen = value; } + } + + [Category("Format On...")] + [DisplayName("File Extensions")] + [Description("When formatting on open/save, clang-format will be applied only to " + "files with these extensions.")] - public string FormatOnSaveFileExtensions + public string FormatOnFileExtensions + { + get { return formatOnFileExtensions; } + set { formatOnFileExtensions = value; } + } + + [Category("Format On...")] + [DisplayName("Path Filter")] + [Description("When formatting on open/save, clang-format will be applied only to " + + "paths matching this regular expression.")] + public string FormatOnPathFilter { - get { return formatOnSaveFileExtensions; } - set { formatOnSaveFileExtensions = value; } + get { return formatOnPathFilter; } + set { formatOnPathFilter = value; } } } @@ -201,7 +237,7 @@ [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] [ProvideMenuResource("Menus.ctmenu", 1)] [ProvideAutoLoad(UIContextGuids80.SolutionExists)] // Load package on solution load - [Guid(GuidList.guidClangFormatPkgString)] + [Guid(GuidList.guidClangFormatSubmenuString)] [ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)] public sealed class ClangFormatPackage : Package { @@ -215,6 +251,7 @@ _runningDocTableEventsDispatcher = new RunningDocTableEventsDispatcher(this); _runningDocTableEventsDispatcher.BeforeSave += OnBeforeSave; + _runningDocTableEventsDispatcher.BeforeShow += OnBeforeShow; var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (commandService != null) @@ -230,6 +267,18 @@ var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); commandService.AddCommand(menuItem); } + + { + var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangAltFormatSelection); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + } + + { + var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangAltFormatDocument); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + } } } #endregion @@ -239,6 +288,13 @@ return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid)); } + OptionPageGrid GetAltUserOptions() + { + OptionPageGrid copy = GetUserOptions().Clone(); + copy.Style = copy.AltStyle; + return copy; + } + private void MenuItemCallback(object sender, EventArgs args) { var mc = sender as System.ComponentModel.Design.MenuCommand; @@ -254,13 +310,51 @@ case (int)PkgCmdIDList.cmdidClangFormatDocument: FormatDocument(GetUserOptions()); break; + + case (int)PkgCmdIDList.cmdidClangAltFormatSelection: + FormatSelection(GetAltUserOptions()); + break; + + case (int)PkgCmdIDList.cmdidClangAltFormatDocument: + FormatDocument(GetAltUserOptions()); + break; } } - private static bool FileHasExtension(string filePath, string fileExtensions) + private bool FileMatchesFilter(string filePath, string fileExtensions, string pathFilter) { - var extensions = fileExtensions.ToLower().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - return extensions.Contains(Path.GetExtension(filePath).ToLower()); + if (fileExtensions.Length > 0) + { + var extensions = fileExtensions.ToLower().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + if (!extensions.Contains(Path.GetExtension(filePath).ToLower())) + return false; + } + if (pathFilter.Length > 0) + { + try + { + Regex filter = new Regex(pathFilter); + if (!filter.IsMatch(filePath)) + return false; + } + catch (Exception e) + { + var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); + var id = Guid.Empty; + int result; + uiShell.ShowMessageBox( + 0, ref id, + "Error in path filter regex:", + e.Message, + string.Empty, 0, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, + OLEMSGICON.OLEMSGICON_INFO, + 0, out result); + return false; + } + } + return true; } private void OnBeforeSave(object sender, Document document) @@ -270,17 +364,41 @@ if (!options.FormatOnSave) return; - if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions)) + if (!FileMatchesFilter(document.FullName, options.FormatOnFileExtensions, options.FormatOnPathFilter)) return; if (!Vsix.IsDocumentDirty(document)) return; - var optionsWithNoFallbackStyle = GetUserOptions().Clone(); + var optionsWithNoFallbackStyle = options.Clone(); optionsWithNoFallbackStyle.FallbackStyle = "none"; FormatDocument(document, optionsWithNoFallbackStyle); } + private void OnBeforeShow(object sender, Document document) + { + var options = GetUserOptions(); + + if (!options.AltFormatOnOpen) + return; + + if (!FileMatchesFilter(document.FullName, options.FormatOnFileExtensions, options.FormatOnPathFilter)) + return; + + bool wasDirty = Vsix.IsDocumentDirty(document); + DateTime lastModified = Vsix.LastContentModifiedTime(document); + + var altOptionsWithNoFallbackStyle = options.Clone(); + altOptionsWithNoFallbackStyle.Style = altOptionsWithNoFallbackStyle.AltStyle; + altOptionsWithNoFallbackStyle.FallbackStyle = "none"; + FormatDocument(document, altOptionsWithNoFallbackStyle); + + if (!wasDirty) + { + Vsix.CleanDocument(document, lastModified); + } + } + /// /// Runs clang-format on the current selection /// Index: tools/clang-format-vs/ClangFormat/Guids.cs =================================================================== --- tools/clang-format-vs/ClangFormat/Guids.cs +++ tools/clang-format-vs/ClangFormat/Guids.cs @@ -4,7 +4,7 @@ { static class GuidList { - public const string guidClangFormatPkgString = "c5286038-25d3-4f65-83a8-51fa2df4a146"; + public const string guidClangFormatSubmenuString = "c5286038-25d3-4f65-83a8-51fa2df4a146"; public const string guidClangFormatCmdSetString = "e39cbab1-0f96-4022-a2bc-da5a9db7eb78"; public static readonly Guid guidClangFormatCmdSet = new Guid(guidClangFormatCmdSetString); Index: tools/clang-format-vs/ClangFormat/PkgCmdID.cs =================================================================== --- tools/clang-format-vs/ClangFormat/PkgCmdID.cs +++ tools/clang-format-vs/ClangFormat/PkgCmdID.cs @@ -2,7 +2,9 @@ { static class PkgCmdIDList { - public const uint cmdidClangFormatSelection = 0x100; - public const uint cmdidClangFormatDocument = 0x101; + public const uint cmdidClangFormatSelection = 0x101; + public const uint cmdidClangFormatDocument = 0x102; + public const uint cmdidClangAltFormatSelection = 0x103; + public const uint cmdidClangAltFormatDocument = 0x104; }; } \ No newline at end of file Index: tools/clang-format-vs/ClangFormat/RunningDocTableEventsDispatcher.cs =================================================================== --- tools/clang-format-vs/ClangFormat/RunningDocTableEventsDispatcher.cs +++ tools/clang-format-vs/ClangFormat/RunningDocTableEventsDispatcher.cs @@ -11,15 +11,27 @@ { private RunningDocumentTable _runningDocumentTable; private DTE _dte; + private SolutionEvents _solutionEvents; + private bool _isClosing = false; public delegate void OnBeforeSaveHander(object sender, Document document); public event OnBeforeSaveHander BeforeSave; + public delegate void OnBeforeShowHander(object sender, Document document); + public event OnBeforeShowHander BeforeShow; + + private void SolutionIsClosing() + { + _isClosing = true; + } + public RunningDocTableEventsDispatcher(Package package) { _runningDocumentTable = new RunningDocumentTable(package); _runningDocumentTable.Advise(this); _dte = (DTE)Package.GetGlobalService(typeof(DTE)); + _solutionEvents = _dte.Events.SolutionEvents; + _solutionEvents.BeforeClosing += SolutionIsClosing; } public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) @@ -42,13 +54,19 @@ return VSConstants.S_OK; } - public int OnAfterSave(uint docCookie) - { - return VSConstants.S_OK; - } - public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) { + if (fFirstShow != 0) + { + if (BeforeShow != null) + { + var document = FindDocumentByCookie(docCookie); + if (document != null) // Not sure why this happens sometimes + { + BeforeShow(this, document); + } + } + } return VSConstants.S_OK; } @@ -64,7 +82,25 @@ var document = FindDocumentByCookie(docCookie); if (document != null) // Not sure why this happens sometimes { - BeforeSave(this, FindDocumentByCookie(docCookie)); + BeforeSave(this, document); + } + } + return VSConstants.S_OK; + } + + public int OnAfterSave(uint docCookie) + { + // If we're not closing the solution, re-run the open formatter + // (the idea is to always see your alt format if both format on open/save are enabled) + if (!_isClosing) + { + if (BeforeShow != null) + { + var document = FindDocumentByCookie(docCookie); + if (document != null) // Not sure why this happens sometimes + { + BeforeShow(this, document); + } } } return VSConstants.S_OK; Index: tools/clang-format-vs/ClangFormat/Vsix.cs =================================================================== --- tools/clang-format-vs/ClangFormat/Vsix.cs +++ tools/clang-format-vs/ClangFormat/Vsix.cs @@ -34,6 +34,28 @@ return textDocument?.IsDirty == true; } + public static DateTime LastContentModifiedTime(Document document) + { + DateTime when = DateTime.MinValue; + var textView = GetDocumentView(document); + var textDocument = GetTextDocument(textView); + if (textDocument != null) + { + when = textDocument.LastContentModifiedTime; + } + return when; + } + + public static void CleanDocument(Document document, DateTime lastContentModifiedTime) + { + var textView = GetDocumentView(document); + var textDocument = GetTextDocument(textView); + if (textDocument != null) + { + textDocument.UpdateDirtyState(false, lastContentModifiedTime); + } + } + public static IWpfTextView GetDocumentView(Document document) { var textView = GetVsTextViewFrompPath(document.FullName);