-//===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===//\r
-//\r
-// The LLVM Compiler Infrastructure\r
-//\r
-// This file is distributed under the University of Illinois Open Source\r
-// License. See LICENSE.TXT for details.\r
-//\r
-//===----------------------------------------------------------------------===//\r
-//\r
-// This class contains a VS extension package that runs clang-format over a\r
-// selection in a VS text editor.\r
-//\r
-//===----------------------------------------------------------------------===//\r
-\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 System;\r
-using System.Collections;\r
-using System.ComponentModel;\r
-using System.ComponentModel.Design;\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
- [ClassInterface(ClassInterfaceType.AutoDual)]\r
- [CLSCompliant(false), ComVisible(true)]\r
- public class OptionPageGrid : DialogPage\r
- {\r
- private string assumeFilename = "";\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
- protected ArrayList values;\r
- public StyleConverter()\r
- {\r
- // Initializes the standard values list with defaults.\r
- values = new ArrayList(new string[] { "file", "Chromium", "Google", "LLVM", "Mozilla", "WebKit" });\r
- }\r
-\r
- public override bool GetStandardValuesSupported(ITypeDescriptorContext context)\r
- {\r
- return true;\r
- }\r
-\r
- public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)\r
- {\r
- return new StandardValuesCollection(values);\r
- }\r
-\r
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\r
- {\r
- if (sourceType == typeof(string))\r
- return true;\r
-\r
- return base.CanConvertFrom(context, sourceType);\r
- }\r
-\r
- public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)\r
- {\r
- string s = value as string;\r
- if (s == null)\r
- return base.ConvertFrom(context, culture, value);\r
-\r
- return value;\r
- }\r
- }\r
-\r
- [Category("Format Options")]\r
- [DisplayName("Style")]\r
- [Description("Coding style, currently supports:\n" +\r
- " - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +\r
- " - 'file' to search for a YAML .clang-format or _clang-format\n" +\r
- " configuration file.\n" +\r
- " - A YAML configuration snippet.\n\n" +\r
- "'File':\n" +\r
- " Searches for a .clang-format or _clang-format configuration file\n" +\r
- " in the source file's directory and its parents.\n\n" +\r
- "YAML configuration snippet:\n" +\r
- " The content of a .clang-format configuration file, as string.\n" +\r
- " Example: '{BasedOnStyle: \"LLVM\", IndentWidth: 8}'\n\n" +\r
- "See also: http://clang.llvm.org/docs/ClangFormatStyleOptions.html.")]\r
- [TypeConverter(typeof(StyleConverter))]\r
- public string Style\r
- {\r
- get { return style; }\r
- set { style = value; }\r
- }\r
-\r
- public sealed class FilenameConverter : TypeConverter\r
- {\r
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\r
- {\r
- if (sourceType == typeof(string))\r
- return true;\r
-\r
- return base.CanConvertFrom(context, sourceType);\r
- }\r
-\r
- public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)\r
- {\r
- string s = value as string;\r
- if (s == null)\r
- return base.ConvertFrom(context, culture, value);\r
-\r
- // Check if string contains quotes. On Windows, file names cannot contain quotes.\r
- // We do not accept them however to avoid hard-to-debug problems.\r
- // A quote in user input would end the parameter quote and so break the command invocation.\r
- if (s.IndexOf('\"') != -1)\r
- throw new NotSupportedException("Filename cannot contain quotes");\r
-\r
- return value;\r
- }\r
- }\r
-\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
- "and to determine the language.")]\r
- [TypeConverter(typeof(FilenameConverter))]\r
- public string AssumeFilename\r
- {\r
- get { return assumeFilename; }\r
- set { assumeFilename = value; }\r
- }\r
-\r
- public sealed class FallbackStyleConverter : StyleConverter\r
- {\r
- public FallbackStyleConverter()\r
- {\r
- // Add "none" to the list of styles.\r
- values.Insert(0, "none");\r
- }\r
- }\r
-\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
- "Use 'none' fallback style to skip formatting.")]\r
- [TypeConverter(typeof(FallbackStyleConverter))]\r
- public string FallbackStyle\r
- {\r
- get { return fallbackStyle; }\r
- set { fallbackStyle = value; }\r
- }\r
-\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
- public bool SortIncludes\r
- {\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
- var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatSelection);\r
- var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);\r
- commandService.AddCommand(menuItem);\r
- }\r
-\r
- {\r
- var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatDocument);\r
- var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);\r
- commandService.AddCommand(menuItem);\r
- }\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
- if (mc == null)\r
- return;\r
-\r
- switch (mc.CommandID.ID)\r
- {\r
- case (int)PkgCmdIDList.cmdidClangFormatSelection:\r
- FormatSelection(GetUserOptions());\r
- break;\r
-\r
- case (int)PkgCmdIDList.cmdidClangFormatDocument:\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(OptionPageGrid options)\r
- {\r
- IWpfTextView view = Vsix.GetCurrentView();\r
- if (view == null)\r
- // We're not in a text view.\r
- return;\r
- string text = view.TextBuffer.CurrentSnapshot.GetText();\r
- int start = view.Selection.Start.Position.GetContainingLine().Start.Position;\r
- int end = view.Selection.End.Position.GetContainingLine().End.Position;\r
- int length = end - start;\r
- \r
- // clang-format doesn't support formatting a range that starts at the end\r
- // of the file.\r
- if (start >= text.Length && text.Length > 0)\r
- start = text.Length - 1;\r
- string path = Vsix.GetDocumentParent(view);\r
- string filePath = Vsix.GetDocumentPath(view);\r
-\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(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
- if (view == null)\r
- // We're not in a text view.\r
- return;\r
-\r
- string filePath = Vsix.GetDocumentPath(view);\r
- var path = Path.GetDirectoryName(filePath);\r
-\r
- string text = view.TextBuffer.CurrentSnapshot.GetText();\r
- if (!text.EndsWith(Environment.NewLine))\r
- {\r
- view.TextBuffer.Insert(view.TextBuffer.CurrentSnapshot.Length, Environment.NewLine);\r
- text += Environment.NewLine;\r
- }\r
-\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, OptionPageGrid options, IWpfTextView view)\r
- {\r
- try\r
- {\r
- string replacements = RunClangFormat(text, offset, length, path, filePath, options);\r
- ApplyClangFormatReplacements(replacements, view);\r
- }\r
- catch (Exception e)\r
- {\r
- var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));\r
- var id = Guid.Empty;\r
- int result;\r
- uiShell.ShowMessageBox(\r
- 0, ref id,\r
- "Error while running clang-format:",\r
- e.Message,\r
- string.Empty, 0,\r
- OLEMSGBUTTON.OLEMSGBUTTON_OK,\r
- OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,\r
- OLEMSGICON.OLEMSGICON_INFO,\r
- 0, out result);\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Runs the given text through clang-format and returns the replacements as XML.\r
- /// \r
- /// Formats the text range starting at offset of the given length.\r
- /// </summary>\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
-\r
- System.Diagnostics.Process process = new System.Diagnostics.Process();\r
- process.StartInfo.UseShellExecute = false;\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 = 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 (options.SortIncludes)\r
- process.StartInfo.Arguments += " -sort-includes ";\r
- string assumeFilename = options.AssumeFilename;\r
- if (string.IsNullOrEmpty(assumeFilename))\r
- assumeFilename = filePath;\r
- if (!string.IsNullOrEmpty(assumeFilename))\r
- process.StartInfo.Arguments += " -assume-filename \"" + assumeFilename + "\"";\r
- process.StartInfo.CreateNoWindow = true;\r
- process.StartInfo.RedirectStandardInput = true;\r
- process.StartInfo.RedirectStandardOutput = true;\r
- process.StartInfo.RedirectStandardError = true;\r
- if (path != null)\r
- process.StartInfo.WorkingDirectory = path;\r
- // We have to be careful when communicating via standard input / output,\r
- // as writes to the buffers will block until they are read from the other side.\r
- // Thus, we:\r
- // 1. Start the process - clang-format.exe will start to read the input from the\r
- // standard input.\r
- try\r
- {\r
- process.Start();\r
- }\r
- catch (Exception e)\r
- {\r
- throw new Exception(\r
- "Cannot execute " + process.StartInfo.FileName + ".\n\"" + \r
- e.Message + "\".\nPlease make sure it is on the PATH.");\r
- }\r
- // 2. We write everything to the standard output - this cannot block, as clang-format\r
- // reads the full standard input before analyzing it without writing anything to the\r
- // standard output.\r
- process.StandardInput.Write(text);\r
- // 3. We notify clang-format that the input is done - after this point clang-format\r
- // will start analyzing the input and eventually write the output.\r
- process.StandardInput.Close();\r
- // 4. We must read clang-format's output before waiting for it to exit; clang-format\r
- // will close the channel by exiting.\r
- string output = process.StandardOutput.ReadToEnd();\r
- // 5. clang-format is done, wait until it is fully shut down.\r
- process.WaitForExit();\r
- if (process.ExitCode != 0)\r
- {\r
- // FIXME: If clang-format writes enough to the standard error stream to block,\r
- // we will never reach this point; instead, read the standard error asynchronously.\r
- throw new Exception(process.StandardError.ReadToEnd());\r
- }\r
- return output;\r
- }\r
-\r
- /// <summary>\r
- /// Applies the clang-format replacements (xml) to the current view\r
- /// </summary>\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
- return;\r
-\r
- var root = XElement.Parse(replacements);\r
- var edit = view.TextBuffer.CreateEdit();\r
- foreach (XElement replacement in root.Descendants("replacement"))\r
- {\r
- var span = new Span(\r
- int.Parse(replacement.Attribute("offset").Value),\r
- int.Parse(replacement.Attribute("length").Value));\r
- edit.Replace(span, replacement.Value);\r
- }\r
- edit.Apply();\r
- }\r
- }\r
-}\r
+//===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This class contains a VS extension package that runs clang-format over a
+// selection in a VS text editor.
+//
+//===----------------------------------------------------------------------===//
+
+using EnvDTE;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+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
+{
+ [ClassInterface(ClassInterfaceType.AutoDual)]
+ [CLSCompliant(false), ComVisible(true)]
+ public class OptionPageGrid : DialogPage
+ {
+ private string assumeFilename = "";
+ 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
+ {
+ protected ArrayList values;
+ public StyleConverter()
+ {
+ // Initializes the standard values list with defaults.
+ values = new ArrayList(new string[] { "file", "Chromium", "Google", "LLVM", "Mozilla", "WebKit" });
+ }
+
+ public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+
+ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
+ {
+ return new StandardValuesCollection(values);
+ }
+
+ 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 s = value as string;
+ if (s == null)
+ return base.ConvertFrom(context, culture, value);
+
+ return value;
+ }
+ }
+
+ [Category("Format Options")]
+ [DisplayName("Style")]
+ [Description("Coding style, currently supports:\n" +
+ " - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +
+ " - 'file' to search for a YAML .clang-format or _clang-format\n" +
+ " configuration file.\n" +
+ " - A YAML configuration snippet.\n\n" +
+ "'File':\n" +
+ " Searches for a .clang-format or _clang-format configuration file\n" +
+ " in the source file's directory and its parents.\n\n" +
+ "YAML configuration snippet:\n" +
+ " The content of a .clang-format configuration file, as string.\n" +
+ " Example: '{BasedOnStyle: \"LLVM\", IndentWidth: 8}'\n\n" +
+ "See also: http://clang.llvm.org/docs/ClangFormatStyleOptions.html.")]
+ [TypeConverter(typeof(StyleConverter))]
+ public string Style
+ {
+ get { return style; }
+ set { style = value; }
+ }
+
+ public sealed class FilenameConverter : TypeConverter
+ {
+ 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 s = value as string;
+ if (s == null)
+ return base.ConvertFrom(context, culture, value);
+
+ // Check if string contains quotes. On Windows, file names cannot contain quotes.
+ // We do not accept them however to avoid hard-to-debug problems.
+ // A quote in user input would end the parameter quote and so break the command invocation.
+ if (s.IndexOf('\"') != -1)
+ throw new NotSupportedException("Filename cannot contain quotes");
+
+ return value;
+ }
+ }
+
+ [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) " +
+ "and to determine the language.")]
+ [TypeConverter(typeof(FilenameConverter))]
+ public string AssumeFilename
+ {
+ get { return assumeFilename; }
+ set { assumeFilename = value; }
+ }
+
+ public sealed class FallbackStyleConverter : StyleConverter
+ {
+ public FallbackStyleConverter()
+ {
+ // Add "none" to the list of styles.
+ values.Insert(0, "none");
+ }
+ }
+
+ [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" +
+ "Use 'none' fallback style to skip formatting.")]
+ [TypeConverter(typeof(FallbackStyleConverter))]
+ public string FallbackStyle
+ {
+ get { return fallbackStyle; }
+ set { fallbackStyle = value; }
+ }
+
+ [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)
+ {
+ {
+ 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 menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
+ commandService.AddCommand(menuItem);
+ }
+ }
+ }
+ #endregion
+
+ OptionPageGrid GetUserOptions()
+ {
+ return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
+ }
+
+ private void MenuItemCallback(object sender, EventArgs args)
+ {
+ var mc = sender as System.ComponentModel.Design.MenuCommand;
+ if (mc == null)
+ return;
+
+ switch (mc.CommandID.ID)
+ {
+ case (int)PkgCmdIDList.cmdidClangFormatSelection:
+ FormatSelection(GetUserOptions());
+ break;
+
+ case (int)PkgCmdIDList.cmdidClangFormatDocument:
+ 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(OptionPageGrid options)
+ {
+ IWpfTextView view = Vsix.GetCurrentView();
+ 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;
+ int end = view.Selection.End.Position.GetContainingLine().End.Position;
+ int length = end - start;
+
+ // clang-format doesn't support formatting a range that starts at the end
+ // of the file.
+ if (start >= text.Length && text.Length > 0)
+ start = text.Length - 1;
+ string path = Vsix.GetDocumentParent(view);
+ string filePath = Vsix.GetDocumentPath(view);
+
+ RunClangFormatAndApplyReplacements(text, start, length, path, filePath, options, view);
+ }
+
+ /// <summary>
+ /// Runs clang-format on the current document
+ /// </summary>
+ 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)
+ {
+ if (view == null)
+ // We're not in a text view.
+ return;
+
+ string filePath = Vsix.GetDocumentPath(view);
+ var path = Path.GetDirectoryName(filePath);
+
+ string text = view.TextBuffer.CurrentSnapshot.GetText();
+ if (!text.EndsWith(Environment.NewLine))
+ {
+ view.TextBuffer.Insert(view.TextBuffer.CurrentSnapshot.Length, Environment.NewLine);
+ text += Environment.NewLine;
+ }
+
+ RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, options, 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, options);
+ ApplyClangFormatReplacements(replacements, view);
+ }
+ catch (Exception e)
+ {
+ var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
+ var id = Guid.Empty;
+ int result;
+ uiShell.ShowMessageBox(
+ 0, ref id,
+ "Error while running clang-format:",
+ e.Message,
+ string.Empty, 0,
+ OLEMSGBUTTON.OLEMSGBUTTON_OK,
+ OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
+ OLEMSGICON.OLEMSGICON_INFO,
+ 0, out result);
+ }
+ }
+
+ /// <summary>
+ /// Runs the given text through clang-format and returns the replacements as XML.
+ ///
+ /// Formats the text range starting at offset of the given length.
+ /// </summary>
+ private static string RunClangFormat(string text, int offset, int length, string path, string filePath, OptionPageGrid options)
+ {
+ string vsixPath = Path.GetDirectoryName(
+ typeof(ClangFormatPackage).Assembly.Location);
+
+ System.Diagnostics.Process process = new System.Diagnostics.Process();
+ process.StartInfo.UseShellExecute = false;
+ 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 = options.Style.Replace("\"", "\\\"");
+ string fallbackStyle = options.FallbackStyle.Replace("\"", "\\\"");
+ process.StartInfo.Arguments = " -offset " + offset +
+ " -length " + length +
+ " -output-replacements-xml " +
+ " -style \"" + style + "\"" +
+ " -fallback-style \"" + fallbackStyle + "\"";
+ if (options.SortIncludes)
+ process.StartInfo.Arguments += " -sort-includes ";
+ string assumeFilename = options.AssumeFilename;
+ if (string.IsNullOrEmpty(assumeFilename))
+ assumeFilename = filePath;
+ if (!string.IsNullOrEmpty(assumeFilename))
+ process.StartInfo.Arguments += " -assume-filename \"" + assumeFilename + "\"";
+ process.StartInfo.CreateNoWindow = true;
+ process.StartInfo.RedirectStandardInput = true;
+ process.StartInfo.RedirectStandardOutput = true;
+ process.StartInfo.RedirectStandardError = true;
+ if (path != null)
+ process.StartInfo.WorkingDirectory = path;
+ // We have to be careful when communicating via standard input / output,
+ // as writes to the buffers will block until they are read from the other side.
+ // Thus, we:
+ // 1. Start the process - clang-format.exe will start to read the input from the
+ // standard input.
+ try
+ {
+ process.Start();
+ }
+ catch (Exception e)
+ {
+ throw new Exception(
+ "Cannot execute " + process.StartInfo.FileName + ".\n\"" +
+ e.Message + "\".\nPlease make sure it is on the PATH.");
+ }
+ // 2. We write everything to the standard output - this cannot block, as clang-format
+ // reads the full standard input before analyzing it without writing anything to the
+ // standard output.
+ process.StandardInput.Write(text);
+ // 3. We notify clang-format that the input is done - after this point clang-format
+ // will start analyzing the input and eventually write the output.
+ process.StandardInput.Close();
+ // 4. We must read clang-format's output before waiting for it to exit; clang-format
+ // will close the channel by exiting.
+ string output = process.StandardOutput.ReadToEnd();
+ // 5. clang-format is done, wait until it is fully shut down.
+ process.WaitForExit();
+ if (process.ExitCode != 0)
+ {
+ // FIXME: If clang-format writes enough to the standard error stream to block,
+ // we will never reach this point; instead, read the standard error asynchronously.
+ throw new Exception(process.StandardError.ReadToEnd());
+ }
+ return output;
+ }
+
+ /// <summary>
+ /// Applies the clang-format replacements (xml) to the current view
+ /// </summary>
+ private static void ApplyClangFormatReplacements(string replacements, IWpfTextView view)
+ {
+ // clang-format returns no replacements if input text is empty
+ if (replacements.Length == 0)
+ return;
+
+ var root = XElement.Parse(replacements);
+ var edit = view.TextBuffer.CreateEdit();
+ foreach (XElement replacement in root.Descendants("replacement"))
+ {
+ var span = new Span(
+ int.Parse(replacement.Attribute("offset").Value),
+ int.Parse(replacement.Attribute("length").Value));
+ edit.Replace(span, replacement.Value);
+ }
+ edit.Apply();
+ }
+ }
+}