summaryrefslogtreecommitdiffstats
path: root/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs')
-rw-r--r--tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs911
1 files changed, 455 insertions, 456 deletions
diff --git a/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs b/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
index 1071b680ae..7443405efa 100644
--- a/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
+++ b/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs
@@ -1,456 +1,455 @@
-//===-- 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();
- }
- }
-}
+//===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// 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();
+ }
+ }
+}