106f32e7eSjoerg //===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===//
206f32e7eSjoerg //
306f32e7eSjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
406f32e7eSjoerg // See https://llvm.org/LICENSE.txt for license information.
506f32e7eSjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
606f32e7eSjoerg //
706f32e7eSjoerg //===----------------------------------------------------------------------===//
806f32e7eSjoerg //
906f32e7eSjoerg // This class contains a VS extension package that runs clang-format over a
1006f32e7eSjoerg // selection in a VS text editor.
1106f32e7eSjoerg //
1206f32e7eSjoerg //===----------------------------------------------------------------------===//
1306f32e7eSjoerg 
1406f32e7eSjoerg using EnvDTE;
1506f32e7eSjoerg using Microsoft.VisualStudio.Shell;
1606f32e7eSjoerg using Microsoft.VisualStudio.Shell.Interop;
1706f32e7eSjoerg using Microsoft.VisualStudio.Text;
1806f32e7eSjoerg using Microsoft.VisualStudio.Text.Editor;
1906f32e7eSjoerg using System;
2006f32e7eSjoerg using System.Collections;
2106f32e7eSjoerg using System.ComponentModel;
2206f32e7eSjoerg using System.ComponentModel.Design;
2306f32e7eSjoerg using System.IO;
2406f32e7eSjoerg using System.Runtime.InteropServices;
2506f32e7eSjoerg using System.Xml.Linq;
2606f32e7eSjoerg using System.Linq;
27*13fbcb42Sjoerg using System.Text;
2806f32e7eSjoerg 
2906f32e7eSjoerg namespace LLVM.ClangFormat
3006f32e7eSjoerg {
3106f32e7eSjoerg     [ClassInterface(ClassInterfaceType.AutoDual)]
3206f32e7eSjoerg     [CLSCompliant(false), ComVisible(true)]
3306f32e7eSjoerg     public class OptionPageGrid : DialogPage
3406f32e7eSjoerg     {
3506f32e7eSjoerg         private string assumeFilename = "";
3606f32e7eSjoerg         private string fallbackStyle = "LLVM";
3706f32e7eSjoerg         private bool sortIncludes = false;
3806f32e7eSjoerg         private string style = "file";
3906f32e7eSjoerg         private bool formatOnSave = false;
4006f32e7eSjoerg         private string formatOnSaveFileExtensions =
4106f32e7eSjoerg             ".c;.cpp;.cxx;.cc;.tli;.tlh;.h;.hh;.hpp;.hxx;.hh;.inl;" +
4206f32e7eSjoerg             ".java;.js;.ts;.m;.mm;.proto;.protodevel;.td";
4306f32e7eSjoerg 
Clone()4406f32e7eSjoerg         public OptionPageGrid Clone()
4506f32e7eSjoerg         {
4606f32e7eSjoerg             // Use MemberwiseClone to copy value types.
4706f32e7eSjoerg             var clone = (OptionPageGrid)MemberwiseClone();
4806f32e7eSjoerg             return clone;
4906f32e7eSjoerg         }
5006f32e7eSjoerg 
5106f32e7eSjoerg         public class StyleConverter : TypeConverter
5206f32e7eSjoerg         {
5306f32e7eSjoerg             protected ArrayList values;
StyleConverter()5406f32e7eSjoerg             public StyleConverter()
5506f32e7eSjoerg             {
5606f32e7eSjoerg                 // Initializes the standard values list with defaults.
5706f32e7eSjoerg                 values = new ArrayList(new string[] { "file", "Chromium", "Google", "LLVM", "Mozilla", "WebKit" });
5806f32e7eSjoerg             }
5906f32e7eSjoerg 
GetStandardValuesSupported(ITypeDescriptorContext context)6006f32e7eSjoerg             public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
6106f32e7eSjoerg             {
6206f32e7eSjoerg                 return true;
6306f32e7eSjoerg             }
6406f32e7eSjoerg 
GetStandardValues(ITypeDescriptorContext context)6506f32e7eSjoerg             public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
6606f32e7eSjoerg             {
6706f32e7eSjoerg                 return new StandardValuesCollection(values);
6806f32e7eSjoerg             }
6906f32e7eSjoerg 
CanConvertFrom(ITypeDescriptorContext context, Type sourceType)7006f32e7eSjoerg             public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
7106f32e7eSjoerg             {
7206f32e7eSjoerg                 if (sourceType == typeof(string))
7306f32e7eSjoerg                     return true;
7406f32e7eSjoerg 
7506f32e7eSjoerg                 return base.CanConvertFrom(context, sourceType);
7606f32e7eSjoerg             }
7706f32e7eSjoerg 
ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)7806f32e7eSjoerg             public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
7906f32e7eSjoerg             {
8006f32e7eSjoerg                 string s = value as string;
8106f32e7eSjoerg                 if (s == null)
8206f32e7eSjoerg                     return base.ConvertFrom(context, culture, value);
8306f32e7eSjoerg 
8406f32e7eSjoerg                 return value;
8506f32e7eSjoerg             }
8606f32e7eSjoerg         }
8706f32e7eSjoerg 
8806f32e7eSjoerg         [Category("Format Options")]
8906f32e7eSjoerg         [DisplayName("Style")]
9006f32e7eSjoerg         [Description("Coding style, currently supports:\n" +
9106f32e7eSjoerg                      "  - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +
9206f32e7eSjoerg                      "  - 'file' to search for a YAML .clang-format or _clang-format\n" +
9306f32e7eSjoerg                      "    configuration file.\n" +
9406f32e7eSjoerg                      "  - A YAML configuration snippet.\n\n" +
9506f32e7eSjoerg                      "'File':\n" +
9606f32e7eSjoerg                      "  Searches for a .clang-format or _clang-format configuration file\n" +
9706f32e7eSjoerg                      "  in the source file's directory and its parents.\n\n" +
9806f32e7eSjoerg                      "YAML configuration snippet:\n" +
9906f32e7eSjoerg                      "  The content of a .clang-format configuration file, as string.\n" +
10006f32e7eSjoerg                      "  Example: '{BasedOnStyle: \"LLVM\", IndentWidth: 8}'\n\n" +
10106f32e7eSjoerg                      "See also: http://clang.llvm.org/docs/ClangFormatStyleOptions.html.")]
10206f32e7eSjoerg         [TypeConverter(typeof(StyleConverter))]
10306f32e7eSjoerg         public string Style
10406f32e7eSjoerg         {
10506f32e7eSjoerg             get { return style; }
10606f32e7eSjoerg             set { style = value; }
10706f32e7eSjoerg         }
10806f32e7eSjoerg 
10906f32e7eSjoerg         public sealed class FilenameConverter : TypeConverter
11006f32e7eSjoerg         {
CanConvertFrom(ITypeDescriptorContext context, Type sourceType)11106f32e7eSjoerg             public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
11206f32e7eSjoerg             {
11306f32e7eSjoerg                 if (sourceType == typeof(string))
11406f32e7eSjoerg                     return true;
11506f32e7eSjoerg 
11606f32e7eSjoerg                 return base.CanConvertFrom(context, sourceType);
11706f32e7eSjoerg             }
11806f32e7eSjoerg 
ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)11906f32e7eSjoerg             public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
12006f32e7eSjoerg             {
12106f32e7eSjoerg                 string s = value as string;
12206f32e7eSjoerg                 if (s == null)
12306f32e7eSjoerg                     return base.ConvertFrom(context, culture, value);
12406f32e7eSjoerg 
12506f32e7eSjoerg                 // Check if string contains quotes. On Windows, file names cannot contain quotes.
12606f32e7eSjoerg                 // We do not accept them however to avoid hard-to-debug problems.
12706f32e7eSjoerg                 // A quote in user input would end the parameter quote and so break the command invocation.
12806f32e7eSjoerg                 if (s.IndexOf('\"') != -1)
12906f32e7eSjoerg                     throw new NotSupportedException("Filename cannot contain quotes");
13006f32e7eSjoerg 
13106f32e7eSjoerg                 return value;
13206f32e7eSjoerg             }
13306f32e7eSjoerg         }
13406f32e7eSjoerg 
13506f32e7eSjoerg         [Category("Format Options")]
13606f32e7eSjoerg         [DisplayName("Assume Filename")]
13706f32e7eSjoerg         [Description("When reading from stdin, clang-format assumes this " +
13806f32e7eSjoerg                      "filename to look for a style config file (with 'file' style) " +
13906f32e7eSjoerg                      "and to determine the language.")]
14006f32e7eSjoerg         [TypeConverter(typeof(FilenameConverter))]
14106f32e7eSjoerg         public string AssumeFilename
14206f32e7eSjoerg         {
14306f32e7eSjoerg             get { return assumeFilename; }
14406f32e7eSjoerg             set { assumeFilename = value; }
14506f32e7eSjoerg         }
14606f32e7eSjoerg 
14706f32e7eSjoerg         public sealed class FallbackStyleConverter : StyleConverter
14806f32e7eSjoerg         {
FallbackStyleConverter()14906f32e7eSjoerg             public FallbackStyleConverter()
15006f32e7eSjoerg             {
15106f32e7eSjoerg                 // Add "none" to the list of styles.
15206f32e7eSjoerg                 values.Insert(0, "none");
15306f32e7eSjoerg             }
15406f32e7eSjoerg         }
15506f32e7eSjoerg 
15606f32e7eSjoerg         [Category("Format Options")]
15706f32e7eSjoerg         [DisplayName("Fallback Style")]
15806f32e7eSjoerg         [Description("The name of the predefined style used as a fallback in case clang-format " +
15906f32e7eSjoerg                      "is invoked with 'file' style, but can not find the configuration file.\n" +
16006f32e7eSjoerg                      "Use 'none' fallback style to skip formatting.")]
16106f32e7eSjoerg         [TypeConverter(typeof(FallbackStyleConverter))]
16206f32e7eSjoerg         public string FallbackStyle
16306f32e7eSjoerg         {
16406f32e7eSjoerg             get { return fallbackStyle; }
16506f32e7eSjoerg             set { fallbackStyle = value; }
16606f32e7eSjoerg         }
16706f32e7eSjoerg 
16806f32e7eSjoerg         [Category("Format Options")]
16906f32e7eSjoerg         [DisplayName("Sort includes")]
17006f32e7eSjoerg         [Description("Sort touched include lines.\n\n" +
17106f32e7eSjoerg                      "See also: http://clang.llvm.org/docs/ClangFormat.html.")]
17206f32e7eSjoerg         public bool SortIncludes
17306f32e7eSjoerg         {
17406f32e7eSjoerg             get { return sortIncludes; }
17506f32e7eSjoerg             set { sortIncludes = value; }
17606f32e7eSjoerg         }
17706f32e7eSjoerg 
17806f32e7eSjoerg         [Category("Format On Save")]
17906f32e7eSjoerg         [DisplayName("Enable")]
18006f32e7eSjoerg         [Description("Enable running clang-format when modified files are saved. " +
18106f32e7eSjoerg                      "Will only format if Style is found (ignores Fallback Style)."
18206f32e7eSjoerg             )]
18306f32e7eSjoerg         public bool FormatOnSave
18406f32e7eSjoerg         {
18506f32e7eSjoerg             get { return formatOnSave; }
18606f32e7eSjoerg             set { formatOnSave = value; }
18706f32e7eSjoerg         }
18806f32e7eSjoerg 
18906f32e7eSjoerg         [Category("Format On Save")]
19006f32e7eSjoerg         [DisplayName("File extensions")]
19106f32e7eSjoerg         [Description("When formatting on save, clang-format will be applied only to " +
19206f32e7eSjoerg                      "files with these extensions.")]
19306f32e7eSjoerg         public string FormatOnSaveFileExtensions
19406f32e7eSjoerg         {
19506f32e7eSjoerg             get { return formatOnSaveFileExtensions; }
19606f32e7eSjoerg             set { formatOnSaveFileExtensions = value; }
19706f32e7eSjoerg         }
19806f32e7eSjoerg     }
19906f32e7eSjoerg 
20006f32e7eSjoerg     [PackageRegistration(UseManagedResourcesOnly = true)]
20106f32e7eSjoerg     [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
20206f32e7eSjoerg     [ProvideMenuResource("Menus.ctmenu", 1)]
20306f32e7eSjoerg     [ProvideAutoLoad(UIContextGuids80.SolutionExists)] // Load package on solution load
20406f32e7eSjoerg     [Guid(GuidList.guidClangFormatPkgString)]
20506f32e7eSjoerg     [ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)]
20606f32e7eSjoerg     public sealed class ClangFormatPackage : Package
20706f32e7eSjoerg     {
20806f32e7eSjoerg         #region Package Members
20906f32e7eSjoerg 
21006f32e7eSjoerg         RunningDocTableEventsDispatcher _runningDocTableEventsDispatcher;
21106f32e7eSjoerg 
Initialize()21206f32e7eSjoerg         protected override void Initialize()
21306f32e7eSjoerg         {
21406f32e7eSjoerg             base.Initialize();
21506f32e7eSjoerg 
21606f32e7eSjoerg             _runningDocTableEventsDispatcher = new RunningDocTableEventsDispatcher(this);
21706f32e7eSjoerg             _runningDocTableEventsDispatcher.BeforeSave += OnBeforeSave;
21806f32e7eSjoerg 
21906f32e7eSjoerg             var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
22006f32e7eSjoerg             if (commandService != null)
22106f32e7eSjoerg             {
22206f32e7eSjoerg                 {
22306f32e7eSjoerg                     var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatSelection);
22406f32e7eSjoerg                     var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
22506f32e7eSjoerg                     commandService.AddCommand(menuItem);
22606f32e7eSjoerg                 }
22706f32e7eSjoerg 
22806f32e7eSjoerg                 {
22906f32e7eSjoerg                     var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatDocument);
23006f32e7eSjoerg                     var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
23106f32e7eSjoerg                     commandService.AddCommand(menuItem);
23206f32e7eSjoerg                 }
23306f32e7eSjoerg             }
23406f32e7eSjoerg         }
23506f32e7eSjoerg         #endregion
23606f32e7eSjoerg 
GetUserOptions()23706f32e7eSjoerg         OptionPageGrid GetUserOptions()
23806f32e7eSjoerg         {
23906f32e7eSjoerg             return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
24006f32e7eSjoerg         }
24106f32e7eSjoerg 
MenuItemCallback(object sender, EventArgs args)24206f32e7eSjoerg         private void MenuItemCallback(object sender, EventArgs args)
24306f32e7eSjoerg         {
24406f32e7eSjoerg             var mc = sender as System.ComponentModel.Design.MenuCommand;
24506f32e7eSjoerg             if (mc == null)
24606f32e7eSjoerg                 return;
24706f32e7eSjoerg 
24806f32e7eSjoerg             switch (mc.CommandID.ID)
24906f32e7eSjoerg             {
25006f32e7eSjoerg                 case (int)PkgCmdIDList.cmdidClangFormatSelection:
25106f32e7eSjoerg                     FormatSelection(GetUserOptions());
25206f32e7eSjoerg                     break;
25306f32e7eSjoerg 
25406f32e7eSjoerg                 case (int)PkgCmdIDList.cmdidClangFormatDocument:
25506f32e7eSjoerg                     FormatDocument(GetUserOptions());
25606f32e7eSjoerg                     break;
25706f32e7eSjoerg             }
25806f32e7eSjoerg         }
25906f32e7eSjoerg 
FileHasExtension(string filePath, string fileExtensions)26006f32e7eSjoerg         private static bool FileHasExtension(string filePath, string fileExtensions)
26106f32e7eSjoerg         {
26206f32e7eSjoerg             var extensions = fileExtensions.ToLower().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
26306f32e7eSjoerg             return extensions.Contains(Path.GetExtension(filePath).ToLower());
26406f32e7eSjoerg         }
26506f32e7eSjoerg 
OnBeforeSave(object sender, Document document)26606f32e7eSjoerg         private void OnBeforeSave(object sender, Document document)
26706f32e7eSjoerg         {
26806f32e7eSjoerg             var options = GetUserOptions();
26906f32e7eSjoerg 
27006f32e7eSjoerg             if (!options.FormatOnSave)
27106f32e7eSjoerg                 return;
27206f32e7eSjoerg 
27306f32e7eSjoerg             if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions))
27406f32e7eSjoerg                 return;
27506f32e7eSjoerg 
27606f32e7eSjoerg             if (!Vsix.IsDocumentDirty(document))
27706f32e7eSjoerg                 return;
27806f32e7eSjoerg 
27906f32e7eSjoerg             var optionsWithNoFallbackStyle = GetUserOptions().Clone();
28006f32e7eSjoerg             optionsWithNoFallbackStyle.FallbackStyle = "none";
28106f32e7eSjoerg             FormatDocument(document, optionsWithNoFallbackStyle);
28206f32e7eSjoerg         }
28306f32e7eSjoerg 
28406f32e7eSjoerg         /// <summary>
28506f32e7eSjoerg         /// Runs clang-format on the current selection
28606f32e7eSjoerg         /// </summary>
FormatSelection(OptionPageGrid options)28706f32e7eSjoerg         private void FormatSelection(OptionPageGrid options)
28806f32e7eSjoerg         {
28906f32e7eSjoerg             IWpfTextView view = Vsix.GetCurrentView();
29006f32e7eSjoerg             if (view == null)
29106f32e7eSjoerg                 // We're not in a text view.
29206f32e7eSjoerg                 return;
29306f32e7eSjoerg             string text = view.TextBuffer.CurrentSnapshot.GetText();
29406f32e7eSjoerg             int start = view.Selection.Start.Position.GetContainingLine().Start.Position;
29506f32e7eSjoerg             int end = view.Selection.End.Position.GetContainingLine().End.Position;
29606f32e7eSjoerg 
29706f32e7eSjoerg             // clang-format doesn't support formatting a range that starts at the end
29806f32e7eSjoerg             // of the file.
29906f32e7eSjoerg             if (start >= text.Length && text.Length > 0)
30006f32e7eSjoerg                 start = text.Length - 1;
30106f32e7eSjoerg             string path = Vsix.GetDocumentParent(view);
30206f32e7eSjoerg             string filePath = Vsix.GetDocumentPath(view);
30306f32e7eSjoerg 
304*13fbcb42Sjoerg             RunClangFormatAndApplyReplacements(text, start, end, path, filePath, options, view);
30506f32e7eSjoerg         }
30606f32e7eSjoerg 
30706f32e7eSjoerg         /// <summary>
30806f32e7eSjoerg         /// Runs clang-format on the current document
30906f32e7eSjoerg         /// </summary>
FormatDocument(OptionPageGrid options)31006f32e7eSjoerg         private void FormatDocument(OptionPageGrid options)
31106f32e7eSjoerg         {
31206f32e7eSjoerg             FormatView(Vsix.GetCurrentView(), options);
31306f32e7eSjoerg         }
31406f32e7eSjoerg 
FormatDocument(Document document, OptionPageGrid options)31506f32e7eSjoerg         private void FormatDocument(Document document, OptionPageGrid options)
31606f32e7eSjoerg         {
31706f32e7eSjoerg             FormatView(Vsix.GetDocumentView(document), options);
31806f32e7eSjoerg         }
31906f32e7eSjoerg 
FormatView(IWpfTextView view, OptionPageGrid options)32006f32e7eSjoerg         private void FormatView(IWpfTextView view, OptionPageGrid options)
32106f32e7eSjoerg         {
32206f32e7eSjoerg             if (view == null)
32306f32e7eSjoerg                 // We're not in a text view.
32406f32e7eSjoerg                 return;
32506f32e7eSjoerg 
32606f32e7eSjoerg             string filePath = Vsix.GetDocumentPath(view);
32706f32e7eSjoerg             var path = Path.GetDirectoryName(filePath);
32806f32e7eSjoerg 
32906f32e7eSjoerg             string text = view.TextBuffer.CurrentSnapshot.GetText();
33006f32e7eSjoerg             if (!text.EndsWith(Environment.NewLine))
33106f32e7eSjoerg             {
33206f32e7eSjoerg                 view.TextBuffer.Insert(view.TextBuffer.CurrentSnapshot.Length, Environment.NewLine);
33306f32e7eSjoerg                 text += Environment.NewLine;
33406f32e7eSjoerg             }
33506f32e7eSjoerg 
33606f32e7eSjoerg             RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, options, view);
33706f32e7eSjoerg         }
33806f32e7eSjoerg 
RunClangFormatAndApplyReplacements(string text, int start, int end, string path, string filePath, OptionPageGrid options, IWpfTextView view)339*13fbcb42Sjoerg         private void RunClangFormatAndApplyReplacements(string text, int start, int end, string path, string filePath, OptionPageGrid options, IWpfTextView view)
34006f32e7eSjoerg         {
34106f32e7eSjoerg             try
34206f32e7eSjoerg             {
343*13fbcb42Sjoerg                 string replacements = RunClangFormat(text, start, end, path, filePath, options);
34406f32e7eSjoerg                 ApplyClangFormatReplacements(replacements, view);
34506f32e7eSjoerg             }
34606f32e7eSjoerg             catch (Exception e)
34706f32e7eSjoerg             {
34806f32e7eSjoerg                 var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
34906f32e7eSjoerg                 var id = Guid.Empty;
35006f32e7eSjoerg                 int result;
35106f32e7eSjoerg                 uiShell.ShowMessageBox(
35206f32e7eSjoerg                         0, ref id,
35306f32e7eSjoerg                         "Error while running clang-format:",
35406f32e7eSjoerg                         e.Message,
35506f32e7eSjoerg                         string.Empty, 0,
35606f32e7eSjoerg                         OLEMSGBUTTON.OLEMSGBUTTON_OK,
35706f32e7eSjoerg                         OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
35806f32e7eSjoerg                         OLEMSGICON.OLEMSGICON_INFO,
35906f32e7eSjoerg                         0, out result);
36006f32e7eSjoerg             }
36106f32e7eSjoerg         }
36206f32e7eSjoerg 
36306f32e7eSjoerg         /// <summary>
36406f32e7eSjoerg         /// Runs the given text through clang-format and returns the replacements as XML.
36506f32e7eSjoerg         ///
366*13fbcb42Sjoerg         /// Formats the text in range start and end.
36706f32e7eSjoerg         /// </summary>
RunClangFormat(string text, int start, int end, string path, string filePath, OptionPageGrid options)368*13fbcb42Sjoerg         private static string RunClangFormat(string text, int start, int end, string path, string filePath, OptionPageGrid options)
36906f32e7eSjoerg         {
37006f32e7eSjoerg             string vsixPath = Path.GetDirectoryName(
37106f32e7eSjoerg                 typeof(ClangFormatPackage).Assembly.Location);
37206f32e7eSjoerg 
37306f32e7eSjoerg             System.Diagnostics.Process process = new System.Diagnostics.Process();
37406f32e7eSjoerg             process.StartInfo.UseShellExecute = false;
37506f32e7eSjoerg             process.StartInfo.FileName = vsixPath + "\\clang-format.exe";
376*13fbcb42Sjoerg             char[] chars = text.ToCharArray();
377*13fbcb42Sjoerg             int offset = Encoding.UTF8.GetByteCount(chars, 0, start);
378*13fbcb42Sjoerg             int length = Encoding.UTF8.GetByteCount(chars, 0, end) - offset;
37906f32e7eSjoerg             // Poor man's escaping - this will not work when quotes are already escaped
38006f32e7eSjoerg             // in the input (but we don't need more).
38106f32e7eSjoerg             string style = options.Style.Replace("\"", "\\\"");
38206f32e7eSjoerg             string fallbackStyle = options.FallbackStyle.Replace("\"", "\\\"");
38306f32e7eSjoerg             process.StartInfo.Arguments = " -offset " + offset +
38406f32e7eSjoerg                                           " -length " + length +
38506f32e7eSjoerg                                           " -output-replacements-xml " +
38606f32e7eSjoerg                                           " -style \"" + style + "\"" +
38706f32e7eSjoerg                                           " -fallback-style \"" + fallbackStyle + "\"";
38806f32e7eSjoerg             if (options.SortIncludes)
38906f32e7eSjoerg               process.StartInfo.Arguments += " -sort-includes ";
39006f32e7eSjoerg             string assumeFilename = options.AssumeFilename;
39106f32e7eSjoerg             if (string.IsNullOrEmpty(assumeFilename))
39206f32e7eSjoerg                 assumeFilename = filePath;
39306f32e7eSjoerg             if (!string.IsNullOrEmpty(assumeFilename))
39406f32e7eSjoerg               process.StartInfo.Arguments += " -assume-filename \"" + assumeFilename + "\"";
39506f32e7eSjoerg             process.StartInfo.CreateNoWindow = true;
39606f32e7eSjoerg             process.StartInfo.RedirectStandardInput = true;
39706f32e7eSjoerg             process.StartInfo.RedirectStandardOutput = true;
39806f32e7eSjoerg             process.StartInfo.RedirectStandardError = true;
39906f32e7eSjoerg             if (path != null)
40006f32e7eSjoerg                 process.StartInfo.WorkingDirectory = path;
40106f32e7eSjoerg             // We have to be careful when communicating via standard input / output,
40206f32e7eSjoerg             // as writes to the buffers will block until they are read from the other side.
40306f32e7eSjoerg             // Thus, we:
40406f32e7eSjoerg             // 1. Start the process - clang-format.exe will start to read the input from the
40506f32e7eSjoerg             //    standard input.
40606f32e7eSjoerg             try
40706f32e7eSjoerg             {
40806f32e7eSjoerg                 process.Start();
40906f32e7eSjoerg             }
41006f32e7eSjoerg             catch (Exception e)
41106f32e7eSjoerg             {
41206f32e7eSjoerg                 throw new Exception(
41306f32e7eSjoerg                     "Cannot execute " + process.StartInfo.FileName + ".\n\"" +
41406f32e7eSjoerg                     e.Message + "\".\nPlease make sure it is on the PATH.");
41506f32e7eSjoerg             }
41606f32e7eSjoerg             // 2. We write everything to the standard output - this cannot block, as clang-format
41706f32e7eSjoerg             //    reads the full standard input before analyzing it without writing anything to the
41806f32e7eSjoerg             //    standard output.
419*13fbcb42Sjoerg             StreamWriter utf8Writer = new StreamWriter(process.StandardInput.BaseStream, new UTF8Encoding(false));
420*13fbcb42Sjoerg             utf8Writer.Write(text);
42106f32e7eSjoerg             // 3. We notify clang-format that the input is done - after this point clang-format
42206f32e7eSjoerg             //    will start analyzing the input and eventually write the output.
423*13fbcb42Sjoerg             utf8Writer.Close();
42406f32e7eSjoerg             // 4. We must read clang-format's output before waiting for it to exit; clang-format
42506f32e7eSjoerg             //    will close the channel by exiting.
42606f32e7eSjoerg             string output = process.StandardOutput.ReadToEnd();
42706f32e7eSjoerg             // 5. clang-format is done, wait until it is fully shut down.
42806f32e7eSjoerg             process.WaitForExit();
42906f32e7eSjoerg             if (process.ExitCode != 0)
43006f32e7eSjoerg             {
43106f32e7eSjoerg                 // FIXME: If clang-format writes enough to the standard error stream to block,
43206f32e7eSjoerg                 // we will never reach this point; instead, read the standard error asynchronously.
43306f32e7eSjoerg                 throw new Exception(process.StandardError.ReadToEnd());
43406f32e7eSjoerg             }
43506f32e7eSjoerg             return output;
43606f32e7eSjoerg         }
43706f32e7eSjoerg 
43806f32e7eSjoerg         /// <summary>
43906f32e7eSjoerg         /// Applies the clang-format replacements (xml) to the current view
44006f32e7eSjoerg         /// </summary>
ApplyClangFormatReplacements(string replacements, IWpfTextView view)44106f32e7eSjoerg         private static void ApplyClangFormatReplacements(string replacements, IWpfTextView view)
44206f32e7eSjoerg         {
44306f32e7eSjoerg             // clang-format returns no replacements if input text is empty
44406f32e7eSjoerg             if (replacements.Length == 0)
44506f32e7eSjoerg                 return;
44606f32e7eSjoerg 
447*13fbcb42Sjoerg             string text = view.TextBuffer.CurrentSnapshot.GetText();
448*13fbcb42Sjoerg             byte[] bytes = Encoding.UTF8.GetBytes(text);
449*13fbcb42Sjoerg 
45006f32e7eSjoerg             var root = XElement.Parse(replacements);
45106f32e7eSjoerg             var edit = view.TextBuffer.CreateEdit();
45206f32e7eSjoerg             foreach (XElement replacement in root.Descendants("replacement"))
45306f32e7eSjoerg             {
454*13fbcb42Sjoerg                 int offset = int.Parse(replacement.Attribute("offset").Value);
455*13fbcb42Sjoerg                 int length = int.Parse(replacement.Attribute("length").Value);
45606f32e7eSjoerg                 var span = new Span(
457*13fbcb42Sjoerg                     Encoding.UTF8.GetCharCount(bytes, 0, offset),
458*13fbcb42Sjoerg                     Encoding.UTF8.GetCharCount(bytes, offset, length));
45906f32e7eSjoerg                 edit.Replace(span, replacement.Value);
46006f32e7eSjoerg             }
46106f32e7eSjoerg             edit.Apply();
46206f32e7eSjoerg         }
46306f32e7eSjoerg     }
46406f32e7eSjoerg }
465