Index: tools/clang-format-vs/.gitignore =================================================================== --- tools/clang-format-vs/.gitignore +++ tools/clang-format-vs/.gitignore @@ -1,5 +1,6 @@ # Visual Studio files .vs/ +*.user /packages/ /ClangFormat/obj/ /ClangFormat/bin/ Index: tools/clang-format-vs/ClangFormat/ClangFormat.vsct =================================================================== --- tools/clang-format-vs/ClangFormat/ClangFormat.vsct +++ tools/clang-format-vs/ClangFormat/ClangFormat.vsct @@ -61,15 +61,21 @@ DynamicVisibility If you do not want an image next to your command, remove the Icon node /> --> - - + @@ -88,7 +94,8 @@ - + + @@ -101,7 +108,8 @@ - + + Index: tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs =================================================================== --- tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs +++ tools/clang-format-vs/ClangFormat/ClangFormatPackage.cs @@ -180,15 +180,44 @@ 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); + { + var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatSelection); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + } + + { + var menuCommandID = new CommandID(GuidList.guidClangFormatCmdSet, (int)PkgCmdIDList.cmdidClangFormatDocument); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + } } } #endregion private void MenuItemCallback(object sender, EventArgs args) { + var mc = sender as System.ComponentModel.Design.MenuCommand; + if (mc == null) + return; + + switch (mc.CommandID.ID) + { + case (int)PkgCmdIDList.cmdidClangFormatSelection: + FormatSelection(); + break; + + case (int)PkgCmdIDList.cmdidClangFormatDocument: + FormatDocument(); + break; + } + } + + /// + /// Runs clang-format on the current selection + /// + private void FormatSelection() + { IWpfTextView view = GetCurrentView(); if (view == null) // We're not in a text view. @@ -197,24 +226,40 @@ 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); string filePath = GetDocumentPath(view); + + RunClangFormatAndApplyReplacements(text, start, length, path, filePath, view); + } + + /// + /// Runs clang-format on the current document + /// + private void FormatDocument() + { + IWpfTextView view = GetCurrentView(); + if (view == null) + // We're not in a text view. + return; + + string filePath = GetDocumentPath(view); + var path = Path.GetDirectoryName(filePath); + string text = view.TextBuffer.CurrentSnapshot.GetText(); + + RunClangFormatAndApplyReplacements(text, 0, text.Length, path, filePath, view); + } + + private void RunClangFormatAndApplyReplacements(string text, int offset, int length, string path, string filePath, IWpfTextView view) + { try { - var root = XElement.Parse(RunClangFormat(text, start, length, path, filePath)); - 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(); + string replacements = RunClangFormat(text, offset, length, path, filePath); + ApplyClangFormatReplacements(replacements, view); } catch (Exception e) { @@ -295,16 +340,43 @@ 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. + string stdErr = process.StandardError.ReadToEnd(); + + if (process.ExitCode != 0 || stdErr.Length > 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()); + throw new Exception( + (stdErr.Length > 0 ? stdErr + "\n\n" : "") + + "(Exit Code: " + process.ExitCode + ")"); } + return output; } /// + /// Applies the clang-format replacements (xml) to the current view + /// + private void ApplyClangFormatReplacements(string replacements, IWpfTextView view) + { + // clang-format returns no replacements if input text is empty + if (replacements.Length == 0) + return; + + var root = XElement.Parse(replacements); + 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(); + } + + /// /// Returns the currently active view if it is a IWpfTextView. /// private IWpfTextView GetCurrentView() Index: tools/clang-format-vs/ClangFormat/PkgCmdID.cs =================================================================== --- tools/clang-format-vs/ClangFormat/PkgCmdID.cs +++ tools/clang-format-vs/ClangFormat/PkgCmdID.cs @@ -2,6 +2,7 @@ { static class PkgCmdIDList { - public const uint cmdidClangFormat = 0x100; + public const uint cmdidClangFormatSelection = 0x100; + public const uint cmdidClangFormatDocument = 0x101; }; } \ No newline at end of file Index: tools/clang-format-vs/README.txt =================================================================== --- tools/clang-format-vs/README.txt +++ tools/clang-format-vs/README.txt @@ -25,3 +25,27 @@ ClangFormat/source.extension.vsixmanifest. Once the plug-in has been built with CMake once, it can be built manually from the ClangFormat.sln solution in Visual Studio. + +=========== + Debugging +=========== + +Once you've built the clang_format_vsix project from LLVM.sln at least once, +open ClangFormat.sln in Visual Studio, then: + +- Make sure the "Debug" target is selected +- Open the ClangFormat project properties +- Select the Debug tab +- Set "Start external program:" to where your devenv.exe is installed. Typically + it's "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" +- Set "Command line arguments" to: /rootsuffix Exp +- You can now set breakpoints if you like +- Press F5 to build and run with debugger + +If all goes well, a new instance of Visual Studio will be launched in a special +mode where it uses the experimental hive instead of the normal configuration hive. +By default, when you build a VSIX project in Visual Studio, it auto-registers the +extension in the experimental hive, allowing you to test it. In the new Visual Studio +instance, open or create a C++ solution, and you should now see the Clang Format +entries in the Tool menu. You can test it out, and any breakpoints you set will be +hit where you can debug as usual.