xref: /minix3/external/bsd/llvm/dist/clang/tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
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 
15 using Microsoft.VisualStudio.Editor;
16 using Microsoft.VisualStudio.Shell;
17 using Microsoft.VisualStudio.Shell.Interop;
18 using Microsoft.VisualStudio.Text;
19 using Microsoft.VisualStudio.Text.Editor;
20 using Microsoft.VisualStudio.TextManager.Interop;
21 using System;
22 using System.ComponentModel;
23 using System.ComponentModel.Design;
24 using System.IO;
25 using System.Reflection;
26 using System.Runtime.InteropServices;
27 using System.Xml.Linq;
28 
29 namespace 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
Initialize()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 
MenuItemCallback(object sender, EventArgs args)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>
RunClangFormat(string text, int offset, int length, string path)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>
GetCurrentView()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 
GetStyle()208         private string GetStyle()
209         {
210             var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
211             return page.Style;
212         }
213 
GetDocumentParent(IWpfTextView view)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