360 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
| //===-- ClangFormatPackages.cs - VSPackage for clang-format ------*- C# -*-===//
 | |
| //
 | |
| //                     The LLVM Compiler Infrastructure
 | |
| //
 | |
| // This file is distributed under the University of Illinois Open Source
 | |
| // License. See LICENSE.TXT for details.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| //
 | |
| // This class contains a VS extension package that runs clang-format over a
 | |
| // selection in a VS text editor.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| using Microsoft.VisualStudio.Editor;
 | |
| using Microsoft.VisualStudio.Shell;
 | |
| using Microsoft.VisualStudio.Shell.Interop;
 | |
| using Microsoft.VisualStudio.Text;
 | |
| using Microsoft.VisualStudio.Text.Editor;
 | |
| using Microsoft.VisualStudio.TextManager.Interop;
 | |
| using System;
 | |
| using System.Collections;
 | |
| using System.ComponentModel;
 | |
| using System.ComponentModel.Design;
 | |
| using System.IO;
 | |
| using System.Runtime.InteropServices;
 | |
| using System.Xml.Linq;
 | |
| 
 | |
| namespace LLVM.ClangFormat
 | |
| {
 | |
|     [ClassInterface(ClassInterfaceType.AutoDual)]
 | |
|     [CLSCompliant(false), ComVisible(true)]
 | |
|     public class OptionPageGrid : DialogPage
 | |
|     {
 | |
|         private string assumeFilename = "";
 | |
|         private string fallbackStyle = "LLVM";
 | |
|         private bool sortIncludes = false;
 | |
|         private string style = "file";
 | |
| 
 | |
|         public class StyleConverter : TypeConverter
 | |
|         {
 | |
|             protected ArrayList values;
 | |
|             public StyleConverter()
 | |
|             {
 | |
|                 // Initializes the standard values list with defaults.
 | |
|                 values = new ArrayList(new string[] { "file", "Chromium", "Google", "LLVM", "Mozilla", "WebKit" });
 | |
|             }
 | |
| 
 | |
|             public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
 | |
|             {
 | |
|                 return new StandardValuesCollection(values);
 | |
|             }
 | |
| 
 | |
|             public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
 | |
|             {
 | |
|                 if (sourceType == typeof(string))
 | |
|                     return true;
 | |
| 
 | |
|                 return base.CanConvertFrom(context, sourceType);
 | |
|             }
 | |
| 
 | |
|             public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
 | |
|             {
 | |
|                 string s = value as string;
 | |
|                 if (s == null)
 | |
|                     return base.ConvertFrom(context, culture, value);
 | |
| 
 | |
|                 return value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [Category("LLVM/Clang")]
 | |
|         [DisplayName("Style")]
 | |
|         [Description("Coding style, currently supports:\n" +
 | |
|                      "  - Predefined styles ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit').\n" +
 | |
|                      "  - 'file' to search for a YAML .clang-format or _clang-format\n" +
 | |
|                      "    configuration file.\n" +
 | |
|                      "  - A YAML configuration snippet.\n\n" +
 | |
|                      "'File':\n" +
 | |
|                      "  Searches for a .clang-format or _clang-format configuration file\n" +
 | |
|                      "  in the source file's directory and its parents.\n\n" +
 | |
|                      "YAML configuration snippet:\n" +
 | |
|                      "  The content of a .clang-format configuration file, as string.\n" +
 | |
|                      "  Example: '{BasedOnStyle: \"LLVM\", IndentWidth: 8}'\n\n" +
 | |
|                      "See also: http://clang.llvm.org/docs/ClangFormatStyleOptions.html.")]
 | |
|         [TypeConverter(typeof(StyleConverter))]
 | |
|         public string Style
 | |
|         {
 | |
|             get { return style; }
 | |
|             set { style = value; }
 | |
|         }
 | |
| 
 | |
|         public sealed class FilenameConverter : TypeConverter
 | |
|         {
 | |
|             public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
 | |
|             {
 | |
|                 if (sourceType == typeof(string))
 | |
|                     return true;
 | |
| 
 | |
|                 return base.CanConvertFrom(context, sourceType);
 | |
|             }
 | |
| 
 | |
|             public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
 | |
|             {
 | |
|                 string s = value as string;
 | |
|                 if (s == null)
 | |
|                     return base.ConvertFrom(context, culture, value);
 | |
| 
 | |
|                 // Check if string contains quotes. On Windows, file names cannot contain quotes.
 | |
|                 // We do not accept them however to avoid hard-to-debug problems.
 | |
|                 // A quote in user input would end the parameter quote and so break the command invocation.
 | |
|                 if (s.IndexOf('\"') != -1)
 | |
|                     throw new NotSupportedException("Filename cannot contain quotes");
 | |
| 
 | |
|                 return value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [Category("LLVM/Clang")]
 | |
|         [DisplayName("Assume Filename")]
 | |
|         [Description("When reading from stdin, clang-format assumes this " +
 | |
|                      "filename to look for a style config file (with 'file' style) " +
 | |
|                      "and to determine the language.")]
 | |
|         [TypeConverter(typeof(FilenameConverter))]
 | |
|         public string AssumeFilename
 | |
|         {
 | |
|             get { return assumeFilename; }
 | |
|             set { assumeFilename = value; }
 | |
|         }
 | |
| 
 | |
|         public sealed class FallbackStyleConverter : StyleConverter
 | |
|         {
 | |
|             public FallbackStyleConverter()
 | |
|             {
 | |
|                 // Add "none" to the list of styles.
 | |
|                 values.Insert(0, "none");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [Category("LLVM/Clang")]
 | |
|         [DisplayName("Fallback Style")]
 | |
|         [Description("The name of the predefined style used as a fallback in case clang-format " +
 | |
|                      "is invoked with 'file' style, but can not find the configuration file.\n" +
 | |
|                      "Use 'none' fallback style to skip formatting.")]
 | |
|         [TypeConverter(typeof(FallbackStyleConverter))]
 | |
|         public string FallbackStyle
 | |
|         {
 | |
|             get { return fallbackStyle; }
 | |
|             set { fallbackStyle = value; }
 | |
|         }
 | |
| 
 | |
|         [Category("LLVM/Clang")]
 | |
|         [DisplayName("Sort includes")]
 | |
|         [Description("Sort touched include lines.\n\n" +
 | |
|                      "See also: http://clang.llvm.org/docs/ClangFormat.html.")]
 | |
|         public bool SortIncludes
 | |
|         {
 | |
|             get { return sortIncludes; }
 | |
|             set { sortIncludes = value; }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     [PackageRegistration(UseManagedResourcesOnly = true)]
 | |
|     [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
 | |
|     [ProvideMenuResource("Menus.ctmenu", 1)]
 | |
|     [Guid(GuidList.guidClangFormatPkgString)]
 | |
|     [ProvideOptionPage(typeof(OptionPageGrid), "LLVM/Clang", "ClangFormat", 0, 0, true)]
 | |
|     public sealed class ClangFormatPackage : Package
 | |
|     {
 | |
|         #region Package Members
 | |
|         protected override void Initialize()
 | |
|         {
 | |
|             base.Initialize();
 | |
| 
 | |
|             var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
 | |
|             if (commandService != null)
 | |
|             {
 | |
|                 var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormat);
 | |
|                 var menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
 | |
|                 commandService.AddCommand(menuItem);
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         private void MenuItemCallback(object sender, EventArgs args)
 | |
|         {
 | |
|             IWpfTextView view = GetCurrentView();
 | |
|             if (view == null)
 | |
|                 // We're not in a text view.
 | |
|                 return;
 | |
|             string text = view.TextBuffer.CurrentSnapshot.GetText();
 | |
|             int start = view.Selection.Start.Position.GetContainingLine().Start.Position;
 | |
|             int end = view.Selection.End.Position.GetContainingLine().End.Position;
 | |
|             int length = end - start;
 | |
|             // clang-format doesn't support formatting a range that starts at the end
 | |
|             // of the file.
 | |
|             if (start >= text.Length && text.Length > 0)
 | |
|                 start = text.Length - 1;
 | |
|             string path = GetDocumentParent(view);
 | |
|             try
 | |
|             {
 | |
|                 var root = XElement.Parse(RunClangFormat(text, start, length, path));
 | |
|                 var edit = view.TextBuffer.CreateEdit();
 | |
|                 foreach (XElement replacement in root.Descendants("replacement"))
 | |
|                 {
 | |
|                     var span = new Span(
 | |
|                         int.Parse(replacement.Attribute("offset").Value),
 | |
|                         int.Parse(replacement.Attribute("length").Value));
 | |
|                     edit.Replace(span, replacement.Value);
 | |
|                 }
 | |
|                 edit.Apply();
 | |
|             }
 | |
|             catch (Exception e)
 | |
|             {
 | |
|                 var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
 | |
|                 var id = Guid.Empty;
 | |
|                 int result;
 | |
|                 uiShell.ShowMessageBox(
 | |
|                         0, ref id,
 | |
|                         "Error while running clang-format:",
 | |
|                         e.Message,
 | |
|                         string.Empty, 0,
 | |
|                         OLEMSGBUTTON.OLEMSGBUTTON_OK,
 | |
|                         OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
 | |
|                         OLEMSGICON.OLEMSGICON_INFO,
 | |
|                         0, out result);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Runs the given text through clang-format and returns the replacements as XML.
 | |
|         /// 
 | |
|         /// Formats the text range starting at offset of the given length.
 | |
|         /// </summary>
 | |
|         private string RunClangFormat(string text, int offset, int length, string path)
 | |
|         {
 | |
|             string vsixPath = Path.GetDirectoryName(
 | |
|                 typeof(ClangFormatPackage).Assembly.Location);
 | |
| 
 | |
|             System.Diagnostics.Process process = new System.Diagnostics.Process();
 | |
|             process.StartInfo.UseShellExecute = false;
 | |
|             process.StartInfo.FileName = vsixPath + "\\clang-format.exe";
 | |
|             // Poor man's escaping - this will not work when quotes are already escaped
 | |
|             // in the input (but we don't need more).
 | |
|             string style = GetStyle().Replace("\"", "\\\"");
 | |
|             string fallbackStyle = GetFallbackStyle().Replace("\"", "\\\"");
 | |
|             process.StartInfo.Arguments = " -offset " + offset +
 | |
|                                           " -length " + length +
 | |
|                                           " -output-replacements-xml " +
 | |
|                                           " -style \"" + style + "\"" +
 | |
|                                           " -fallback-style \"" + fallbackStyle + "\"";
 | |
|             if (GetSortIncludes())
 | |
|               process.StartInfo.Arguments += " -sort-includes ";
 | |
|             string assumeFilename = GetAssumeFilename();
 | |
|             if (!string.IsNullOrEmpty(assumeFilename))
 | |
|               process.StartInfo.Arguments += " -assume-filename \"" + assumeFilename + "\"";
 | |
|             process.StartInfo.CreateNoWindow = true;
 | |
|             process.StartInfo.RedirectStandardInput = true;
 | |
|             process.StartInfo.RedirectStandardOutput = true;
 | |
|             process.StartInfo.RedirectStandardError = true;
 | |
|             if (path != null)
 | |
|                 process.StartInfo.WorkingDirectory = path;
 | |
|             // We have to be careful when communicating via standard input / output,
 | |
|             // as writes to the buffers will block until they are read from the other side.
 | |
|             // Thus, we:
 | |
|             // 1. Start the process - clang-format.exe will start to read the input from the
 | |
|             //    standard input.
 | |
|             try
 | |
|             {
 | |
|                 process.Start();
 | |
|             }
 | |
|             catch (Exception e)
 | |
|             {
 | |
|                 throw new Exception(
 | |
|                     "Cannot execute " + process.StartInfo.FileName + ".\n\"" + 
 | |
|                     e.Message + "\".\nPlease make sure it is on the PATH.");
 | |
|             }
 | |
|             // 2. We write everything to the standard output - this cannot block, as clang-format
 | |
|             //    reads the full standard input before analyzing it without writing anything to the
 | |
|             //    standard output.
 | |
|             process.StandardInput.Write(text);
 | |
|             // 3. We notify clang-format that the input is done - after this point clang-format
 | |
|             //    will start analyzing the input and eventually write the output.
 | |
|             process.StandardInput.Close();
 | |
|             // 4. We must read clang-format's output before waiting for it to exit; clang-format
 | |
|             //    will close the channel by exiting.
 | |
|             string output = process.StandardOutput.ReadToEnd();
 | |
|             // 5. clang-format is done, wait until it is fully shut down.
 | |
|             process.WaitForExit();
 | |
|             if (process.ExitCode != 0)
 | |
|             {
 | |
|                 // FIXME: If clang-format writes enough to the standard error stream to block,
 | |
|                 // we will never reach this point; instead, read the standard error asynchronously.
 | |
|                 throw new Exception(process.StandardError.ReadToEnd());
 | |
|             }
 | |
|             return output;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns the currently active view if it is a IWpfTextView.
 | |
|         /// </summary>
 | |
|         private IWpfTextView GetCurrentView()
 | |
|         {
 | |
|             // The SVsTextManager is a service through which we can get the active view.
 | |
|             var textManager = (IVsTextManager)Package.GetGlobalService(typeof(SVsTextManager));
 | |
|             IVsTextView textView;
 | |
|             textManager.GetActiveView(1, null, out textView);
 | |
| 
 | |
|             // Now we have the active view as IVsTextView, but the text interfaces we need
 | |
|             // are in the IWpfTextView.
 | |
|             var userData = (IVsUserData)textView;
 | |
|             if (userData == null)
 | |
|                 return null;
 | |
|             Guid guidWpfViewHost = DefGuidList.guidIWpfTextViewHost;
 | |
|             object host;
 | |
|             userData.GetData(ref guidWpfViewHost, out host);
 | |
|             return ((IWpfTextViewHost)host).TextView;
 | |
|         }
 | |
| 
 | |
|         private string GetStyle()
 | |
|         {
 | |
|             var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
 | |
|             return page.Style;
 | |
|         }
 | |
| 
 | |
|         private string GetAssumeFilename()
 | |
|         {
 | |
|             var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
 | |
|             return page.AssumeFilename;
 | |
|         }
 | |
| 
 | |
|         private string GetFallbackStyle()
 | |
|         {
 | |
|             var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
 | |
|             return page.FallbackStyle;
 | |
|         }
 | |
| 
 | |
|         private bool GetSortIncludes()
 | |
|         {
 | |
|             var page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
 | |
|             return page.SortIncludes;
 | |
|         }
 | |
| 
 | |
|         private string GetDocumentParent(IWpfTextView view)
 | |
|         {
 | |
|             ITextDocument document;
 | |
|             if (view.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document))
 | |
|             {
 | |
|                 return Directory.GetParent(document.FilePath).ToString();
 | |
|             }
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| }
 |