xref: /openbsd-src/gnu/llvm/clang/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs (revision e5dd70708596ae51455a0ffa086a00c5b29f8583)
1*e5dd7070Spatrick //===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===//
2*e5dd7070Spatrick //
3*e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5*e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*e5dd7070Spatrick //
7*e5dd7070Spatrick //===----------------------------------------------------------------------===//
8*e5dd7070Spatrick //
9*e5dd7070Spatrick // This class contains a VS extension package that runs clang-format over a
10*e5dd7070Spatrick // selection in a VS text editor.
11*e5dd7070Spatrick //
12*e5dd7070Spatrick //===----------------------------------------------------------------------===//
13*e5dd7070Spatrick 
14*e5dd7070Spatrick using EnvDTE;
15*e5dd7070Spatrick using Microsoft.VisualStudio.Shell;
16*e5dd7070Spatrick using Microsoft.VisualStudio.Shell.Interop;
17*e5dd7070Spatrick using Microsoft.VisualStudio.Text;
18*e5dd7070Spatrick using Microsoft.VisualStudio.Text.Editor;
19*e5dd7070Spatrick using System;
20*e5dd7070Spatrick using System.Collections;
21*e5dd7070Spatrick using System.ComponentModel;
22*e5dd7070Spatrick using System.ComponentModel.Design;
23*e5dd7070Spatrick using System.IO;
24*e5dd7070Spatrick using System.Runtime.InteropServices;
25*e5dd7070Spatrick using System.Xml.Linq;
26*e5dd7070Spatrick using System.Linq;
27*e5dd7070Spatrick using System.Text;
28*e5dd7070Spatrick 
29*e5dd7070Spatrick namespace LLVM.ClangFormat
30*e5dd7070Spatrick {
31*e5dd7070Spatrick     [ClassInterface(ClassInterfaceType.AutoDual)]
32*e5dd7070Spatrick     [CLSCompliant(false), ComVisible(true)]
33*e5dd7070Spatrick     public class OptionPageGrid : DialogPage
34*e5dd7070Spatrick     {
35*e5dd7070Spatrick         private string assumeFilename = "";
36*e5dd7070Spatrick         private string fallbackStyle = "LLVM";
37*e5dd7070Spatrick         private bool sortIncludes = false;
38*e5dd7070Spatrick         private string style = "file";
39*e5dd7070Spatrick         private bool formatOnSave = false;
40*e5dd7070Spatrick         private string formatOnSaveFileExtensions =
41*e5dd7070Spatrick             ".c;.cpp;.cxx;.cc;.tli;.tlh;.h;.hh;.hpp;.hxx;.hh;.inl;" +
42*e5dd7070Spatrick             ".java;.js;.ts;.m;.mm;.proto;.protodevel;.td";
43*e5dd7070Spatrick 
Clone()44*e5dd7070Spatrick         public OptionPageGrid Clone()
45*e5dd7070Spatrick         {
46*e5dd7070Spatrick             // Use MemberwiseClone to copy value types.
47*e5dd7070Spatrick             var clone = (OptionPageGrid)MemberwiseClone();
48*e5dd7070Spatrick             return clone;
49*e5dd7070Spatrick         }
50*e5dd7070Spatrick 
51*e5dd7070Spatrick         public class StyleConverter : TypeConverter
52*e5dd7070Spatrick         {
53*e5dd7070Spatrick             protected ArrayList values;
StyleConverter()54*e5dd7070Spatrick             public StyleConverter()
55*e5dd7070Spatrick             {
56*e5dd7070Spatrick                 // Initializes the standard values list with defaults.
57*e5dd7070Spatrick                 values = new ArrayList(new string[] { "file", "Chromium", "Google", "LLVM", "Mozilla", "WebKit" });
58*e5dd7070Spatrick             }
59*e5dd7070Spatrick 
GetStandardValuesSupported(ITypeDescriptorContext context)60*e5dd7070Spatrick             public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
61*e5dd7070Spatrick             {
62*e5dd7070Spatrick                 return true;
63*e5dd7070Spatrick             }
64*e5dd7070Spatrick 
GetStandardValues(ITypeDescriptorContext context)65*e5dd7070Spatrick             public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
66*e5dd7070Spatrick             {
67*e5dd7070Spatrick                 return new StandardValuesCollection(values);
68*e5dd7070Spatrick             }
69*e5dd7070Spatrick 
CanConvertFrom(ITypeDescriptorContext context, Type sourceType)70*e5dd7070Spatrick             public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
71*e5dd7070Spatrick             {
72*e5dd7070Spatrick                 if (sourceType == typeof(string))
73*e5dd7070Spatrick                     return true;
74*e5dd7070Spatrick 
75*e5dd7070Spatrick                 return base.CanConvertFrom(context, sourceType);
76*e5dd7070Spatrick             }
77*e5dd7070Spatrick 
ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)78*e5dd7070Spatrick             public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
79*e5dd7070Spatrick             {
80*e5dd7070Spatrick                 string s = value as string;
81*e5dd7070Spatrick                 if (s == null)
82*e5dd7070Spatrick                     return base.ConvertFrom(context, culture, value);
83*e5dd7070Spatrick 
84*e5dd7070Spatrick                 return value;
85*e5dd7070Spatrick             }
86*e5dd7070Spatrick         }
87*e5dd7070Spatrick 
88*e5dd7070Spatrick         [Category("Format Options")]
89*e5dd7070Spatrick         [DisplayName("Style")]
90*e5dd7070Spatrick         [Description("Coding style, currently supports:\n" +
91*e5dd7070Spatrick                      "  - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +
92*e5dd7070Spatrick                      "  - 'file' to search for a YAML .clang-format or _clang-format\n" +
93*e5dd7070Spatrick                      "    configuration file.\n" +
94*e5dd7070Spatrick                      "  - A YAML configuration snippet.\n\n" +
95*e5dd7070Spatrick                      "'File':\n" +
96*e5dd7070Spatrick                      "  Searches for a .clang-format or _clang-format configuration file\n" +
97*e5dd7070Spatrick                      "  in the source file's directory and its parents.\n\n" +
98*e5dd7070Spatrick                      "YAML configuration snippet:\n" +
99*e5dd7070Spatrick                      "  The content of a .clang-format configuration file, as string.\n" +
100*e5dd7070Spatrick                      "  Example: '{BasedOnStyle: \"LLVM\", IndentWidth: 8}'\n\n" +
101*e5dd7070Spatrick                      "See also: http://clang.llvm.org/docs/ClangFormatStyleOptions.html.")]
102*e5dd7070Spatrick         [TypeConverter(typeof(StyleConverter))]
103*e5dd7070Spatrick         public string Style
104*e5dd7070Spatrick         {
105*e5dd7070Spatrick             get { return style; }
106*e5dd7070Spatrick             set { style = value; }
107*e5dd7070Spatrick         }
108*e5dd7070Spatrick 
109*e5dd7070Spatrick         public sealed class FilenameConverter : TypeConverter
110*e5dd7070Spatrick         {
CanConvertFrom(ITypeDescriptorContext context, Type sourceType)111*e5dd7070Spatrick             public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
112*e5dd7070Spatrick             {
113*e5dd7070Spatrick                 if (sourceType == typeof(string))
114*e5dd7070Spatrick                     return true;
115*e5dd7070Spatrick 
116*e5dd7070Spatrick                 return base.CanConvertFrom(context, sourceType);
117*e5dd7070Spatrick             }
118*e5dd7070Spatrick 
ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)119*e5dd7070Spatrick             public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
120*e5dd7070Spatrick             {
121*e5dd7070Spatrick                 string s = value as string;
122*e5dd7070Spatrick                 if (s == null)
123*e5dd7070Spatrick                     return base.ConvertFrom(context, culture, value);
124*e5dd7070Spatrick 
125*e5dd7070Spatrick                 // Check if string contains quotes. On Windows, file names cannot contain quotes.
126*e5dd7070Spatrick                 // We do not accept them however to avoid hard-to-debug problems.
127*e5dd7070Spatrick                 // A quote in user input would end the parameter quote and so break the command invocation.
128*e5dd7070Spatrick                 if (s.IndexOf('\"') != -1)
129*e5dd7070Spatrick                     throw new NotSupportedException("Filename cannot contain quotes");
130*e5dd7070Spatrick 
131*e5dd7070Spatrick                 return value;
132*e5dd7070Spatrick             }
133*e5dd7070Spatrick         }
134*e5dd7070Spatrick 
135*e5dd7070Spatrick         [Category("Format Options")]
136*e5dd7070Spatrick         [DisplayName("Assume Filename")]
137*e5dd7070Spatrick         [Description("When reading from stdin, clang-format assumes this " +
138*e5dd7070Spatrick                      "filename to look for a style config file (with 'file' style) " +
139*e5dd7070Spatrick                      "and to determine the language.")]
140*e5dd7070Spatrick         [TypeConverter(typeof(FilenameConverter))]
141*e5dd7070Spatrick         public string AssumeFilename
142*e5dd7070Spatrick         {
143*e5dd7070Spatrick             get { return assumeFilename; }
144*e5dd7070Spatrick             set { assumeFilename = value; }
145*e5dd7070Spatrick         }
146*e5dd7070Spatrick 
147*e5dd7070Spatrick         public sealed class FallbackStyleConverter : StyleConverter
148*e5dd7070Spatrick         {
FallbackStyleConverter()149*e5dd7070Spatrick             public FallbackStyleConverter()
150*e5dd7070Spatrick             {
151*e5dd7070Spatrick                 // Add "none" to the list of styles.
152*e5dd7070Spatrick                 values.Insert(0, "none");
153*e5dd7070Spatrick             }
154*e5dd7070Spatrick         }
155*e5dd7070Spatrick 
156*e5dd7070Spatrick         [Category("Format Options")]
157*e5dd7070Spatrick         [DisplayName("Fallback Style")]
158*e5dd7070Spatrick         [Description("The name of the predefined style used as a fallback in case clang-format " +
159*e5dd7070Spatrick                      "is invoked with 'file' style, but can not find the configuration file.\n" +
160*e5dd7070Spatrick                      "Use 'none' fallback style to skip formatting.")]
161*e5dd7070Spatrick         [TypeConverter(typeof(FallbackStyleConverter))]
162*e5dd7070Spatrick         public string FallbackStyle
163*e5dd7070Spatrick         {
164*e5dd7070Spatrick             get { return fallbackStyle; }
165*e5dd7070Spatrick             set { fallbackStyle = value; }
166*e5dd7070Spatrick         }
167*e5dd7070Spatrick 
168*e5dd7070Spatrick         [Category("Format Options")]
169*e5dd7070Spatrick         [DisplayName("Sort includes")]
170*e5dd7070Spatrick         [Description("Sort touched include lines.\n\n" +
171*e5dd7070Spatrick                      "See also: http://clang.llvm.org/docs/ClangFormat.html.")]
172*e5dd7070Spatrick         public bool SortIncludes
173*e5dd7070Spatrick         {
174*e5dd7070Spatrick             get { return sortIncludes; }
175*e5dd7070Spatrick             set { sortIncludes = value; }
176*e5dd7070Spatrick         }
177*e5dd7070Spatrick 
178*e5dd7070Spatrick         [Category("Format On Save")]
179*e5dd7070Spatrick         [DisplayName("Enable")]
180*e5dd7070Spatrick         [Description("Enable running clang-format when modified files are saved. " +
181*e5dd7070Spatrick                      "Will only format if Style is found (ignores Fallback Style)."
182*e5dd7070Spatrick             )]
183*e5dd7070Spatrick         public bool FormatOnSave
184*e5dd7070Spatrick         {
185*e5dd7070Spatrick             get { return formatOnSave; }
186*e5dd7070Spatrick             set { formatOnSave = value; }
187*e5dd7070Spatrick         }
188*e5dd7070Spatrick 
189*e5dd7070Spatrick         [Category("Format On Save")]
190*e5dd7070Spatrick         [DisplayName("File extensions")]
191*e5dd7070Spatrick         [Description("When formatting on save, clang-format will be applied only to " +
192*e5dd7070Spatrick                      "files with these extensions.")]
193*e5dd7070Spatrick         public string FormatOnSaveFileExtensions
194*e5dd7070Spatrick         {
195*e5dd7070Spatrick             get { return formatOnSaveFileExtensions; }
196*e5dd7070Spatrick             set { formatOnSaveFileExtensions = value; }
197*e5dd7070Spatrick         }
198*e5dd7070Spatrick     }
199*e5dd7070Spatrick 
200*e5dd7070Spatrick     [PackageRegistration(UseManagedResourcesOnly = true)]
201*e5dd7070Spatrick     [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
202*e5dd7070Spatrick     [ProvideMenuResource("Menus.ctmenu", 1)]
203*e5dd7070Spatrick     [ProvideAutoLoad(UIContextGuids80.SolutionExists)] // Load package on solution load
204*e5dd7070Spatrick     [Guid(GuidList.guidClangFormatPkgString)]
205*e5dd7070Spatrick     [ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)]
206*e5dd7070Spatrick     public sealed class ClangFormatPackage : Package
207*e5dd7070Spatrick     {
208*e5dd7070Spatrick         #region Package Members
209*e5dd7070Spatrick 
210*e5dd7070Spatrick         RunningDocTableEventsDispatcher _runningDocTableEventsDispatcher;
211*e5dd7070Spatrick 
Initialize()212*e5dd7070Spatrick         protected override void Initialize()
213*e5dd7070Spatrick         {
214*e5dd7070Spatrick             base.Initialize();
215*e5dd7070Spatrick 
216*e5dd7070Spatrick             _runningDocTableEventsDispatcher = new RunningDocTableEventsDispatcher(this);
217*e5dd7070Spatrick             _runningDocTableEventsDispatcher.BeforeSave += OnBeforeSave;
218*e5dd7070Spatrick 
219*e5dd7070Spatrick             var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
220*e5dd7070Spatrick             if (commandService != null)
221*e5dd7070Spatrick             {
222*e5dd7070Spatrick                 {
223*e5dd7070Spatrick                     var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatSelection);
224*e5dd7070Spatrick                     var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
225*e5dd7070Spatrick                     commandService.AddCommand(menuItem);
226*e5dd7070Spatrick                 }
227*e5dd7070Spatrick 
228*e5dd7070Spatrick                 {
229*e5dd7070Spatrick                     var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatDocument);
230*e5dd7070Spatrick                     var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
231*e5dd7070Spatrick                     commandService.AddCommand(menuItem);
232*e5dd7070Spatrick                 }
233*e5dd7070Spatrick             }
234*e5dd7070Spatrick         }
235*e5dd7070Spatrick         #endregion
236*e5dd7070Spatrick 
GetUserOptions()237*e5dd7070Spatrick         OptionPageGrid GetUserOptions()
238*e5dd7070Spatrick         {
239*e5dd7070Spatrick             return (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
240*e5dd7070Spatrick         }
241*e5dd7070Spatrick 
MenuItemCallback(object sender, EventArgs args)242*e5dd7070Spatrick         private void MenuItemCallback(object sender, EventArgs args)
243*e5dd7070Spatrick         {
244*e5dd7070Spatrick             var mc = sender as System.ComponentModel.Design.MenuCommand;
245*e5dd7070Spatrick             if (mc == null)
246*e5dd7070Spatrick                 return;
247*e5dd7070Spatrick 
248*e5dd7070Spatrick             switch (mc.CommandID.ID)
249*e5dd7070Spatrick             {
250*e5dd7070Spatrick                 case (int)PkgCmdIDList.cmdidClangFormatSelection:
251*e5dd7070Spatrick                     FormatSelection(GetUserOptions());
252*e5dd7070Spatrick                     break;
253*e5dd7070Spatrick 
254*e5dd7070Spatrick                 case (int)PkgCmdIDList.cmdidClangFormatDocument:
255*e5dd7070Spatrick                     FormatDocument(GetUserOptions());
256*e5dd7070Spatrick                     break;
257*e5dd7070Spatrick             }
258*e5dd7070Spatrick         }
259*e5dd7070Spatrick 
FileHasExtension(string filePath, string fileExtensions)260*e5dd7070Spatrick         private static bool FileHasExtension(string filePath, string fileExtensions)
261*e5dd7070Spatrick         {
262*e5dd7070Spatrick             var extensions = fileExtensions.ToLower().Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
263*e5dd7070Spatrick             return extensions.Contains(Path.GetExtension(filePath).ToLower());
264*e5dd7070Spatrick         }
265*e5dd7070Spatrick 
OnBeforeSave(object sender, Document document)266*e5dd7070Spatrick         private void OnBeforeSave(object sender, Document document)
267*e5dd7070Spatrick         {
268*e5dd7070Spatrick             var options = GetUserOptions();
269*e5dd7070Spatrick 
270*e5dd7070Spatrick             if (!options.FormatOnSave)
271*e5dd7070Spatrick                 return;
272*e5dd7070Spatrick 
273*e5dd7070Spatrick             if (!FileHasExtension(document.FullName, options.FormatOnSaveFileExtensions))
274*e5dd7070Spatrick                 return;
275*e5dd7070Spatrick 
276*e5dd7070Spatrick             if (!Vsix.IsDocumentDirty(document))
277*e5dd7070Spatrick                 return;
278*e5dd7070Spatrick 
279*e5dd7070Spatrick             var optionsWithNoFallbackStyle = GetUserOptions().Clone();
280*e5dd7070Spatrick             optionsWithNoFallbackStyle.FallbackStyle = "none";
281*e5dd7070Spatrick             FormatDocument(document, optionsWithNoFallbackStyle);
282*e5dd7070Spatrick         }
283*e5dd7070Spatrick 
284*e5dd7070Spatrick         /// <summary>
285*e5dd7070Spatrick         /// Runs clang-format on the current selection
286*e5dd7070Spatrick         /// </summary>
FormatSelection(OptionPageGrid options)287*e5dd7070Spatrick         private void FormatSelection(OptionPageGrid options)
288*e5dd7070Spatrick         {
289*e5dd7070Spatrick             IWpfTextView view = Vsix.GetCurrentView();
290*e5dd7070Spatrick             if (view == null)
291*e5dd7070Spatrick                 // We're not in a text view.
292*e5dd7070Spatrick                 return;
293*e5dd7070Spatrick             string text = view.TextBuffer.CurrentSnapshot.GetText();
294*e5dd7070Spatrick             int start = view.Selection.Start.Position.GetContainingLine().Start.Position;
295*e5dd7070Spatrick             int end = view.Selection.End.Position.GetContainingLine().End.Position;
296*e5dd7070Spatrick 
297*e5dd7070Spatrick             // clang-format doesn't support formatting a range that starts at the end
298*e5dd7070Spatrick             // of the file.
299*e5dd7070Spatrick             if (start >= text.Length && text.Length > 0)
300*e5dd7070Spatrick                 start = text.Length - 1;
301*e5dd7070Spatrick             string path = Vsix.GetDocumentParent(view);
302*e5dd7070Spatrick             string filePath = Vsix.GetDocumentPath(view);
303*e5dd7070Spatrick 
304*e5dd7070Spatrick             RunClangFormatAndApplyReplacements(text, start, end, path, filePath, options, view);
305*e5dd7070Spatrick         }
306*e5dd7070Spatrick 
307*e5dd7070Spatrick         /// <summary>
308*e5dd7070Spatrick         /// Runs clang-format on the current document
309*e5dd7070Spatrick         /// </summary>
FormatDocument(OptionPageGrid options)310*e5dd7070Spatrick         private void FormatDocument(OptionPageGrid options)
311*e5dd7070Spatrick         {
312*e5dd7070Spatrick             FormatView(Vsix.GetCurrentView(), options);
313*e5dd7070Spatrick         }
314*e5dd7070Spatrick 
FormatDocument(Document document, OptionPageGrid options)315*e5dd7070Spatrick         private void FormatDocument(Document document, OptionPageGrid options)
316*e5dd7070Spatrick         {
317*e5dd7070Spatrick             FormatView(Vsix.GetDocumentView(document), options);
318*e5dd7070Spatrick         }
319*e5dd7070Spatrick 
FormatView(IWpfTextView view, OptionPageGrid options)320*e5dd7070Spatrick         private void FormatView(IWpfTextView view, OptionPageGrid options)
321*e5dd7070Spatrick         {
322*e5dd7070Spatrick             if (view == null)
323*e5dd7070Spatrick                 // We're not in a text view.
324*e5dd7070Spatrick                 return;
325*e5dd7070Spatrick 
326*e5dd7070Spatrick             string filePath = Vsix.GetDocumentPath(view);
327*e5dd7070Spatrick             var path = Path.GetDirectoryName(filePath);
328*e5dd7070Spatrick 
329*e5dd7070Spatrick             string text = view.TextBuffer.CurrentSnapshot.GetText();
330*e5dd7070Spatrick             if (!text.EndsWith(Environment.NewLine))
331*e5dd7070Spatrick             {
332*e5dd7070Spatrick                 view.TextBuffer.Insert(view.TextBuffer.CurrentSnapshot.Length, Environment.NewLine);
333*e5dd7070Spatrick                 text += Environment.NewLine;
334*e5dd7070Spatrick             }
335*e5dd7070Spatrick 
336*e5dd7070Spatrick             RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, options, view);
337*e5dd7070Spatrick         }
338*e5dd7070Spatrick 
RunClangFormatAndApplyReplacements(string text, int start, int end, string path, string filePath, OptionPageGrid options, IWpfTextView view)339*e5dd7070Spatrick         private void RunClangFormatAndApplyReplacements(string text, int start, int end, string path, string filePath, OptionPageGrid options, IWpfTextView view)
340*e5dd7070Spatrick         {
341*e5dd7070Spatrick             try
342*e5dd7070Spatrick             {
343*e5dd7070Spatrick                 string replacements = RunClangFormat(text, start, end, path, filePath, options);
344*e5dd7070Spatrick                 ApplyClangFormatReplacements(replacements, view);
345*e5dd7070Spatrick             }
346*e5dd7070Spatrick             catch (Exception e)
347*e5dd7070Spatrick             {
348*e5dd7070Spatrick                 var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
349*e5dd7070Spatrick                 var id = Guid.Empty;
350*e5dd7070Spatrick                 int result;
351*e5dd7070Spatrick                 uiShell.ShowMessageBox(
352*e5dd7070Spatrick                         0, ref id,
353*e5dd7070Spatrick                         "Error while running clang-format:",
354*e5dd7070Spatrick                         e.Message,
355*e5dd7070Spatrick                         string.Empty, 0,
356*e5dd7070Spatrick                         OLEMSGBUTTON.OLEMSGBUTTON_OK,
357*e5dd7070Spatrick                         OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
358*e5dd7070Spatrick                         OLEMSGICON.OLEMSGICON_INFO,
359*e5dd7070Spatrick                         0, out result);
360*e5dd7070Spatrick             }
361*e5dd7070Spatrick         }
362*e5dd7070Spatrick 
363*e5dd7070Spatrick         /// <summary>
364*e5dd7070Spatrick         /// Runs the given text through clang-format and returns the replacements as XML.
365*e5dd7070Spatrick         ///
366*e5dd7070Spatrick         /// Formats the text in range start and end.
367*e5dd7070Spatrick         /// </summary>
RunClangFormat(string text, int start, int end, string path, string filePath, OptionPageGrid options)368*e5dd7070Spatrick         private static string RunClangFormat(string text, int start, int end, string path, string filePath, OptionPageGrid options)
369*e5dd7070Spatrick         {
370*e5dd7070Spatrick             string vsixPath = Path.GetDirectoryName(
371*e5dd7070Spatrick                 typeof(ClangFormatPackage).Assembly.Location);
372*e5dd7070Spatrick 
373*e5dd7070Spatrick             System.Diagnostics.Process process = new System.Diagnostics.Process();
374*e5dd7070Spatrick             process.StartInfo.UseShellExecute = false;
375*e5dd7070Spatrick             process.StartInfo.FileName = vsixPath + "\\clang-format.exe";
376*e5dd7070Spatrick             char[] chars = text.ToCharArray();
377*e5dd7070Spatrick             int offset = Encoding.UTF8.GetByteCount(chars, 0, start);
378*e5dd7070Spatrick             int length = Encoding.UTF8.GetByteCount(chars, 0, end) - offset;
379*e5dd7070Spatrick             // Poor man's escaping - this will not work when quotes are already escaped
380*e5dd7070Spatrick             // in the input (but we don't need more).
381*e5dd7070Spatrick             string style = options.Style.Replace("\"", "\\\"");
382*e5dd7070Spatrick             string fallbackStyle = options.FallbackStyle.Replace("\"", "\\\"");
383*e5dd7070Spatrick             process.StartInfo.Arguments = " -offset " + offset +
384*e5dd7070Spatrick                                           " -length " + length +
385*e5dd7070Spatrick                                           " -output-replacements-xml " +
386*e5dd7070Spatrick                                           " -style \"" + style + "\"" +
387*e5dd7070Spatrick                                           " -fallback-style \"" + fallbackStyle + "\"";
388*e5dd7070Spatrick             if (options.SortIncludes)
389*e5dd7070Spatrick               process.StartInfo.Arguments += " -sort-includes ";
390*e5dd7070Spatrick             string assumeFilename = options.AssumeFilename;
391*e5dd7070Spatrick             if (string.IsNullOrEmpty(assumeFilename))
392*e5dd7070Spatrick                 assumeFilename = filePath;
393*e5dd7070Spatrick             if (!string.IsNullOrEmpty(assumeFilename))
394*e5dd7070Spatrick               process.StartInfo.Arguments += " -assume-filename \"" + assumeFilename + "\"";
395*e5dd7070Spatrick             process.StartInfo.CreateNoWindow = true;
396*e5dd7070Spatrick             process.StartInfo.RedirectStandardInput = true;
397*e5dd7070Spatrick             process.StartInfo.RedirectStandardOutput = true;
398*e5dd7070Spatrick             process.StartInfo.RedirectStandardError = true;
399*e5dd7070Spatrick             if (path != null)
400*e5dd7070Spatrick                 process.StartInfo.WorkingDirectory = path;
401*e5dd7070Spatrick             // We have to be careful when communicating via standard input / output,
402*e5dd7070Spatrick             // as writes to the buffers will block until they are read from the other side.
403*e5dd7070Spatrick             // Thus, we:
404*e5dd7070Spatrick             // 1. Start the process - clang-format.exe will start to read the input from the
405*e5dd7070Spatrick             //    standard input.
406*e5dd7070Spatrick             try
407*e5dd7070Spatrick             {
408*e5dd7070Spatrick                 process.Start();
409*e5dd7070Spatrick             }
410*e5dd7070Spatrick             catch (Exception e)
411*e5dd7070Spatrick             {
412*e5dd7070Spatrick                 throw new Exception(
413*e5dd7070Spatrick                     "Cannot execute " + process.StartInfo.FileName + ".\n\"" +
414*e5dd7070Spatrick                     e.Message + "\".\nPlease make sure it is on the PATH.");
415*e5dd7070Spatrick             }
416*e5dd7070Spatrick             // 2. We write everything to the standard output - this cannot block, as clang-format
417*e5dd7070Spatrick             //    reads the full standard input before analyzing it without writing anything to the
418*e5dd7070Spatrick             //    standard output.
419*e5dd7070Spatrick             StreamWriter utf8Writer = new StreamWriter(process.StandardInput.BaseStream, new UTF8Encoding(false));
420*e5dd7070Spatrick             utf8Writer.Write(text);
421*e5dd7070Spatrick             // 3. We notify clang-format that the input is done - after this point clang-format
422*e5dd7070Spatrick             //    will start analyzing the input and eventually write the output.
423*e5dd7070Spatrick             utf8Writer.Close();
424*e5dd7070Spatrick             // 4. We must read clang-format's output before waiting for it to exit; clang-format
425*e5dd7070Spatrick             //    will close the channel by exiting.
426*e5dd7070Spatrick             string output = process.StandardOutput.ReadToEnd();
427*e5dd7070Spatrick             // 5. clang-format is done, wait until it is fully shut down.
428*e5dd7070Spatrick             process.WaitForExit();
429*e5dd7070Spatrick             if (process.ExitCode != 0)
430*e5dd7070Spatrick             {
431*e5dd7070Spatrick                 // FIXME: If clang-format writes enough to the standard error stream to block,
432*e5dd7070Spatrick                 // we will never reach this point; instead, read the standard error asynchronously.
433*e5dd7070Spatrick                 throw new Exception(process.StandardError.ReadToEnd());
434*e5dd7070Spatrick             }
435*e5dd7070Spatrick             return output;
436*e5dd7070Spatrick         }
437*e5dd7070Spatrick 
438*e5dd7070Spatrick         /// <summary>
439*e5dd7070Spatrick         /// Applies the clang-format replacements (xml) to the current view
440*e5dd7070Spatrick         /// </summary>
ApplyClangFormatReplacements(string replacements, IWpfTextView view)441*e5dd7070Spatrick         private static void ApplyClangFormatReplacements(string replacements, IWpfTextView view)
442*e5dd7070Spatrick         {
443*e5dd7070Spatrick             // clang-format returns no replacements if input text is empty
444*e5dd7070Spatrick             if (replacements.Length == 0)
445*e5dd7070Spatrick                 return;
446*e5dd7070Spatrick 
447*e5dd7070Spatrick             string text = view.TextBuffer.CurrentSnapshot.GetText();
448*e5dd7070Spatrick             byte[] bytes = Encoding.UTF8.GetBytes(text);
449*e5dd7070Spatrick 
450*e5dd7070Spatrick             var root = XElement.Parse(replacements);
451*e5dd7070Spatrick             var edit = view.TextBuffer.CreateEdit();
452*e5dd7070Spatrick             foreach (XElement replacement in root.Descendants("replacement"))
453*e5dd7070Spatrick             {
454*e5dd7070Spatrick                 int offset = int.Parse(replacement.Attribute("offset").Value);
455*e5dd7070Spatrick                 int length = int.Parse(replacement.Attribute("length").Value);
456*e5dd7070Spatrick                 var span = new Span(
457*e5dd7070Spatrick                     Encoding.UTF8.GetCharCount(bytes, 0, offset),
458*e5dd7070Spatrick                     Encoding.UTF8.GetCharCount(bytes, offset, length));
459*e5dd7070Spatrick                 edit.Replace(span, replacement.Value);
460*e5dd7070Spatrick             }
461*e5dd7070Spatrick             edit.Apply();
462*e5dd7070Spatrick         }
463*e5dd7070Spatrick     }
464*e5dd7070Spatrick }
465