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