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