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,45 @@ DynamicVisibility If you do not want an image next to your command, remove the Icon node /> --> - - + + + + + + + + @@ -86,8 +113,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. --> - - + @@ -95,32 +121,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + + + + + + + + - - - - - - + + + + + + Index: tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs =================================================================== --- tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs +++ tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +#define USE_ODS + using EnvDTE; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -19,6 +21,8 @@ using Microsoft.VisualStudio.Text.Editor; using System; using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; using System.ComponentModel; using System.ComponentModel.Design; using System.IO; @@ -36,10 +40,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 +98,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 +114,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 +154,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 +184,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 +193,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 +204,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,44 +240,69 @@ [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 { - #region Package Members - RunningDocTableEventsDispatcher _runningDocTableEventsDispatcher; +#if DEBUG + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public static extern void OutputDebugString(string message); + public void DebugLog(string s) + { + OutputDebugString("clang-format-vs: " + s + "\n"); + } +#else + public void DebugLog(string s) {} +#endif + protected override void Initialize() { + DebugLog("Initialize"); base.Initialize(); _runningDocTableEventsDispatcher = new RunningDocTableEventsDispatcher(this); _runningDocTableEventsDispatcher.BeforeSave += OnBeforeSave; + _runningDocTableEventsDispatcher.BeforeShow += OnBeforeShow; + int[] ids = + { + PkgCmdIDList.cmdidClangFormatSelection, + PkgCmdIDList.cmdidClangAltFormatSelection, + PkgCmdIDList.cmdidClangFormatDocument, + PkgCmdIDList.cmdidClangAltFormatDocument, + PkgCmdIDList.cmdidClangFormatOpenDocuments, + PkgCmdIDList.cmdidClangAltFormatOpenDocuments + }; + + DebugLog("Menus"); var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (commandService != null) { + foreach (var id in ids) { - var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatSelection); - var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); - commandService.AddCommand(menuItem); - } - - { - var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatDocument); + var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, id); var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); commandService.AddCommand(menuItem); } } + + DebugLog("Initialized!"); } - #endregion OptionPageGrid GetUserOptions() { 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; @@ -247,20 +311,49 @@ switch (mc.CommandID.ID) { - case (int)PkgCmdIDList.cmdidClangFormatSelection: - FormatSelection(GetUserOptions()); - break; - - case (int)PkgCmdIDList.cmdidClangFormatDocument: - FormatDocument(GetUserOptions()); - break; + case PkgCmdIDList.cmdidClangFormatSelection: FormatSelection(GetUserOptions()); break; + case PkgCmdIDList.cmdidClangAltFormatSelection: FormatSelection(GetAltUserOptions()); break; + case PkgCmdIDList.cmdidClangFormatDocument: FormatDocument(GetUserOptions()); break; + case PkgCmdIDList.cmdidClangAltFormatDocument: FormatDocument(GetAltUserOptions()); break; + case PkgCmdIDList.cmdidClangFormatOpenDocuments: FormatOpenDocuments(); break; + case PkgCmdIDList.cmdidClangAltFormatOpenDocuments: AltFormatOpenDocuments(); 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, RegexOptions.IgnoreCase); + 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,15 +363,48 @@ if (!options.FormatOnSave) return; - if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions)) + if (!FileMatchesFilter(document.FullName, options.FormatOnFileExtensions, options.FormatOnPathFilter)) return; - if (!Vsix.IsDocumentDirty(document)) - return; + DebugLog("OnBeforeSave " + document.FullName); + + bool wasDirty = Vsix.IsDocumentDirty(document); + DateTime lastModified = Vsix.LastContentModifiedTime(document); - var optionsWithNoFallbackStyle = GetUserOptions().Clone(); + var optionsWithNoFallbackStyle = options.Clone(); optionsWithNoFallbackStyle.FallbackStyle = "none"; FormatDocument(document, optionsWithNoFallbackStyle); + + if (!wasDirty) + { + Vsix.CleanDocument(document, lastModified); + } + } + + private void OnBeforeShow(object sender, Document document) + { + var options = GetUserOptions(); + + if (!options.AltFormatOnOpen) + return; + + if (!FileMatchesFilter(document.FullName, options.FormatOnFileExtensions, options.FormatOnPathFilter)) + return; + + DebugLog("OnBeforeShow " + document.FullName); + + 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); + } } /// @@ -287,8 +413,8 @@ private void FormatSelection(OptionPageGrid options) { IWpfTextView view = Vsix.GetCurrentView(); + // Are we in a text view? if (view == null) - // We're not in a text view. return; string text = view.TextBuffer.CurrentSnapshot.GetText(); int start = view.Selection.Start.Position.GetContainingLine().Start.Position; @@ -318,6 +444,22 @@ FormatView(Vsix.GetDocumentView(document), options); } + /// + /// Runs "On Save" clang-format on all open documents + /// + private void FormatOpenDocuments() + { + _runningDocTableEventsDispatcher.FormatOpenDocuments(); + } + + /// + /// Runs "On Open" alternate clang-format on all open documents + /// + private void AltFormatOpenDocuments() + { + _runningDocTableEventsDispatcher.AltFormatOpenDocuments(); + } + private void FormatView(IWpfTextView view, OptionPageGrid options) { if (view == null) 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,11 @@ { static class PkgCmdIDList { - public const uint cmdidClangFormatSelection = 0x100; - public const uint cmdidClangFormatDocument = 0x101; + public const int cmdidClangFormatSelection = 0x101; + public const int cmdidClangAltFormatSelection = 0x102; + public const int cmdidClangFormatDocument = 0x103; + public const int cmdidClangAltFormatDocument = 0x104; + public const int cmdidClangFormatOpenDocuments = 0x105; + public const int cmdidClangAltFormatOpenDocuments = 0x106; }; } \ 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 @@ -3,7 +3,11 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using System.Linq; - +using System.Collections.Generic; +#if DEBUG +using System.Runtime.InteropServices; +#endif + namespace LLVM.ClangFormat { // Exposes event sources for IVsRunningDocTableEvents3 events. @@ -11,15 +15,109 @@ { private RunningDocumentTable _runningDocumentTable; private DTE _dte; + private SolutionEvents _solutionEvents; + private bool _isOpened = false; + private bool _isClosing = false; + private List _docCookiesShownBeforeOpened = new List(); + +#if DEBUG + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public static extern void OutputDebugString(string message); + public void DebugLog(string s) + { + OutputDebugString("clang-format-vs (RDT): " + s + "\n"); + } +#else + public void DebugLog(string s) {} +#endif + + public delegate void DocumentHander(object sender, Document document); + public event DocumentHander BeforeSave; + public event DocumentHander BeforeShow; + + private void SolutionIsClosing() + { + _isOpened = false; + _isClosing = true; + DebugLog("SolutionIsClosing"); + } + + private void SolutionOpened() + { + _isOpened = true; + _isClosing = false; + DebugLog("SolutionOpened"); + foreach (var docCookie in _docCookiesShownBeforeOpened) + { + DebugLog("delay-formatting " + docCookie); + OnAfterSave(docCookie); + } + _docCookiesShownBeforeOpened.Clear(); + } + + private Document FindDocumentByCookie(uint docCookie) + { + var documentInfo = _runningDocumentTable.GetDocumentInfo(docCookie); + return _dte.Documents.Cast().FirstOrDefault(doc => doc.FullName == documentInfo.Moniker); + } - public delegate void OnBeforeSaveHander(object sender, Document document); - public event OnBeforeSaveHander BeforeSave; + private void SavingDocument(uint docCookie) + { + if (BeforeSave != null) + { + var document = FindDocumentByCookie(docCookie); + if (document != null) // Not sure why this happens sometimes + { + BeforeSave(this, document); + } + } + } + + private void ShowingDocument(uint docCookie) + { + if (BeforeShow != null) + { + if (!_isOpened) + { + DebugLog("delaying formatting for " + docCookie); + _docCookiesShownBeforeOpened.Add(docCookie); + } + else + { + var document = FindDocumentByCookie(docCookie); + if (document != null) // Not sure why this happens sometimes + { + BeforeShow(this, document); + } + } + } + } public RunningDocTableEventsDispatcher(Package package) { + DebugLog("RunningDocTableEventsDispatcher ctor"); _runningDocumentTable = new RunningDocumentTable(package); _runningDocumentTable.Advise(this); _dte = (DTE)Package.GetGlobalService(typeof(DTE)); + _solutionEvents = _dte.Events.SolutionEvents; + _solutionEvents.Opened += SolutionOpened; + _solutionEvents.BeforeClosing += SolutionIsClosing; + } + + public void FormatOpenDocuments() + { + foreach (var docInfo in _runningDocumentTable) + { + SavingDocument(docInfo.DocCookie); + } + } + + public void AltFormatOpenDocuments() + { + foreach (var docInfo in _runningDocumentTable) + { + ShowingDocument(docInfo.DocCookie); + } } public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) @@ -42,13 +140,12 @@ 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) + { + ShowingDocument(docCookie); + } return VSConstants.S_OK; } @@ -59,21 +156,19 @@ 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)); - } - } + SavingDocument(docCookie); return VSConstants.S_OK; } - private Document FindDocumentByCookie(uint docCookie) + public int OnAfterSave(uint docCookie) { - var documentInfo = _runningDocumentTable.GetDocumentInfo(docCookie); - return _dte.Documents.Cast().FirstOrDefault(doc => doc.FullName == documentInfo.Moniker); + // 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) + { + ShowingDocument(docCookie); + } + 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);