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