1//===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This class contains a VS extension package that runs clang-format over a 11// selection in a VS text editor. 12// 13//===----------------------------------------------------------------------===// 14 15using Microsoft.VisualStudio.Editor; 16using Microsoft.VisualStudio.Shell; 17using Microsoft.VisualStudio.Shell.Interop; 18using Microsoft.VisualStudio.Text; 19using Microsoft.VisualStudio.Text.Editor; 20using Microsoft.VisualStudio.TextManager.Interop; 21using System; 22using System.ComponentModel; 23using System.ComponentModel.Design; 24using System.IO; 25using System.Reflection; 26using System.Runtime.InteropServices; 27using System.Xml.Linq; 28 29namespace LLVM.ClangFormat 30{ 31 [ClassInterface(ClassInterfaceType.AutoDual)] 32 [CLSCompliant(false), ComVisible(true)] 33 public class OptionPageGrid : DialogPage 34 { 35 private string style = "File"; 36 37 [Category("LLVM/Clang")] 38 [DisplayName("Style")] 39 [Description("Coding style, currently supports:\n" + 40 " - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla').\n" + 41 " - 'File' to search for a YAML .clang-format or _clang-format\n" + 42 " configuration file.\n" + 43 " - A YAML configuration snippet.\n\n" + 44 "'File':\n" + 45 " Searches for a .clang-format or _clang-format configuration file\n" + 46 " in the source file's directory and its parents.\n\n" + 47 "YAML configuration snippet:\n" + 48 " The content of a .clang-format configuration file, as string.\n" + 49 " Example: '{BasedOnStyle: \"LLVM\", IndentWidth: 8}'\n\n" + 50 "See also: http://clang.llvm.org/docs/ClangFormatStyleOptions.html.")] 51 public string Style 52 { 53 get { return style; } 54 set { style = value; } 55 } 56 } 57 58 [PackageRegistration(UseManagedResourcesOnly = true)] 59 [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 60 [ProvideMenuResource("Menus.ctmenu", 1)] 61 [Guid(GuidList.guidClangFormatPkgString)] 62 [ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)] 63 public sealed class ClangFormatPackage : Package 64 { 65 #region Package Members 66 protected override void Initialize() 67 { 68 base.Initialize(); 69 70 var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 71 if (commandService != null) 72 { 73 var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormat); 74 var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); 75 commandService.AddCommand(menuItem); 76 } 77 } 78 #endregion 79 80 private void MenuItemCallback(object sender, EventArgs args) 81 { 82 IWpfTextView view = GetCurrentView(); 83 if (view == null) 84 // We're not in a text view. 85 return; 86 string text = view.TextBuffer.CurrentSnapshot.GetText(); 87 int start = view.Selection.Start.Position.GetContainingLine().Start.Position; 88 int end = view.Selection.End.Position.GetContainingLine().End.Position; 89 int length = end - start; 90 // clang-format doesn't support formatting a range that starts at the end 91 // of the file. 92 if (start >= text.Length && text.Length > 0) 93 start = text.Length - 1; 94 string path = GetDocumentParent(view); 95 try 96 { 97 var root = XElement.Parse(RunClangFormat(text, start, length, path)); 98 var edit = view.TextBuffer.CreateEdit(); 99 foreach (XElement replacement in root.Descendants("replacement")) 100 { 101 var span = new Span( 102 int.Parse(replacement.Attribute("offset").Value), 103 int.Parse(replacement.Attribute("length").Value)); 104 edit.Replace(span, replacement.Value); 105 } 106 edit.Apply(); 107 } 108 catch (Exception e) 109 { 110 var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); 111 var id = Guid.Empty; 112 int result; 113 uiShell.ShowMessageBox( 114 0, ref id, 115 "Error while running clang-format:", 116 e.Message, 117 string.Empty, 0, 118 OLEMSGBUTTON.OLEMSGBUTTON_OK, 119 OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, 120 OLEMSGICON.OLEMSGICON_INFO, 121 0, out result); 122 } 123 } 124 125 /// <summary> 126 /// Runs the given text through clang-format and returns the replacements as XML. 127 /// 128 /// Formats the text range starting at offset of the given length. 129 /// </summary> 130 private string RunClangFormat(string text, int offset, int length, string path) 131 { 132 string vsixPath = Path.GetDirectoryName( 133 typeof(ClangFormatPackage).Assembly.Location); 134 135 System.Diagnostics.Process process = new System.Diagnostics.Process(); 136 process.StartInfo.UseShellExecute = false; 137 process.StartInfo.FileName = vsixPath + "\\clang-format.exe"; 138 // Poor man's escaping - this will not work when quotes are already escaped 139 // in the input (but we don't need more). 140 string style = GetStyle().Replace("\"", "\\\""); 141 process.StartInfo.Arguments = " -offset " + offset + 142 " -length " + length + 143 " -output-replacements-xml " + 144 " -style \"" + style + "\""; 145 process.StartInfo.CreateNoWindow = true; 146 process.StartInfo.RedirectStandardInput = true; 147 process.StartInfo.RedirectStandardOutput = true; 148 process.StartInfo.RedirectStandardError = true; 149 if (path != null) 150 process.StartInfo.WorkingDirectory = path; 151 // We have to be careful when communicating via standard input / output, 152 // as writes to the buffers will block until they are read from the other side. 153 // Thus, we: 154 // 1. Start the process - clang-format.exe will start to read the input from the 155 // standard input. 156 try 157 { 158 process.Start(); 159 } 160 catch (Exception e) 161 { 162 throw new Exception( 163 "Cannot execute " + process.StartInfo.FileName + ".\n\"" + 164 e.Message + "\".\nPlease make sure it is on the PATH."); 165 } 166 // 2. We write everything to the standard output - this cannot block, as clang-format 167 // reads the full standard input before analyzing it without writing anything to the 168 // standard output. 169 process.StandardInput.Write(text); 170 // 3. We notify clang-format that the input is done - after this point clang-format 171 // will start analyzing the input and eventually write the output. 172 process.StandardInput.Close(); 173 // 4. We must read clang-format's output before waiting for it to exit; clang-format 174 // will close the channel by exiting. 175 string output = process.StandardOutput.ReadToEnd(); 176 // 5. clang-format is done, wait until it is fully shut down. 177 process.WaitForExit(); 178 if (process.ExitCode != 0) 179 { 180 // FIXME: If clang-format writes enough to the standard error stream to block, 181 // we will never reach this point; instead, read the standard error asynchronously. 182 throw new Exception(process.StandardError.ReadToEnd()); 183 } 184 return output; 185 } 186 187 /// <summary> 188 /// Returns the currently active view if it is a IWpfTextView. 189 /// </summary> 190 private IWpfTextView GetCurrentView() 191 { 192 // The SVsTextManager is a service through which we can get the active view. 193 var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager)); 194 IVsTextView textView; 195 textManager.GetActiveView(1, null, out textView); 196 197 // Now we have the active view as IVsTextView, but the text interfaces we need 198 // are in the IWpfTextView. 199 var userData = (IVsUserData)textView; 200 if (userData == null) 201 return null; 202 Guid guidWpfViewHost = DefGuidList.guidIWpfTextViewHost; 203 object host; 204 userData.GetData(ref guidWpfViewHost, out host); 205 return ((IWpfTextViewHost)host).TextView; 206 } 207 208 private string GetStyle() 209 { 210 var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid)); 211 return page.Style; 212 } 213 214 private string GetDocumentParent(IWpfTextView view) 215 { 216 ITextDocument document; 217 if (view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document)) 218 { 219 return Directory.GetParent(document.FilePath).ToString(); 220 } 221 return null; 222 } 223 } 224} 225