diff options
Diffstat (limited to 'tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs')
-rw-r--r-- | tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs | 911 |
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(); + } + } +} |