├── Plugin ├── logo.png ├── Guids.cs ├── IViewSettingsContainer.cs ├── PluginPackage.cs ├── Properties │ └── AssemblyInfo.cs ├── ListenerBase.cs ├── source.extension.vsixmanifest ├── PluginFactory.cs ├── FileSettings.cs ├── SettingsViewApplier.cs ├── SaveListener.cs ├── Plugin.csproj └── Plugin.cs ├── .gitmodules ├── Wrapper ├── utility.h ├── utility.cpp ├── AssemblyInfo.cpp ├── wrapper.cpp └── Wrapper.vcxproj ├── .gitignore ├── CONTRIBUTORS ├── .editorconfig ├── release-notes.md ├── EditorConfig.VisualStudio.sln ├── LICENSE └── readme.md /Plugin/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/editorconfig-visualstudio/master/Plugin/logo.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Core"] 2 | path = Core 3 | url = https://github.com/editorconfig/editorconfig-core.git 4 | -------------------------------------------------------------------------------- /Wrapper/utility.h: -------------------------------------------------------------------------------- 1 | using namespace System; 2 | 3 | char const *StringToUTF8(String ^string); 4 | String ^UTF8ToString(char const *utf8); 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.opensdf 2 | *.sdf 3 | *.suo 4 | *.user 5 | *.ncrunchsolution 6 | [Dd]ebug 7 | [Rr]elease 8 | bin 9 | obj 10 | _ReSharper*/ 11 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Contributors to EditorConfig Visual Studio plugin: 2 | 3 | William Swanson 4 | Martijn Laarman 5 | Hong Xu 6 | Tom Potts 7 | Ethan J. Brown 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | end_of_line = LF 5 | 6 | [*.cs] 7 | indent_style = space 8 | indent_size = 4 9 | 10 | [*.cpp] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.h] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /Plugin/Guids.cs: -------------------------------------------------------------------------------- 1 | // Guids.cs 2 | // MUST match guids.h 3 | using System; 4 | 5 | namespace EditorConfig.VisualStudio 6 | { 7 | static class GuidList 8 | { 9 | public const string guidPluginPkgString = "ab96e999-0d6d-4d1a-a034-debb50fe72d5"; 10 | public const string guidPluginCmdSetString = "ab7de3bb-ac1d-41f9-9f00-ccc4ad314f5d"; 11 | 12 | public static readonly Guid guidPluginCmdSet = new Guid(guidPluginCmdSetString); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /Plugin/IViewSettingsContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Text.Editor; 3 | 4 | namespace EditorConfig.VisualStudio 5 | { 6 | internal interface IViewSettingsContainer 7 | { 8 | void Register(string filepath, IWpfTextView view, FileSettings settings); 9 | void Unregister(string filepath, IWpfTextView view); 10 | void Update(string oldFilepath, string newFilePath, IWpfTextView view, FileSettings newSettings); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 0.3.2 4 | 5 | * Bug fixes 6 | 7 | ## 0.3.1 8 | 9 | * Visual Studio 2013 support 10 | 11 | ## 0.2.6 12 | 13 | * Visual Studio 2012 support 14 | 15 | ## 0.2.5 16 | 17 | * Ignore certain unsaved files (fix by Karaken12) 18 | 19 | ## 0.2.4 20 | 21 | * Handle config files that have a Unicode BOM mark 22 | * Ignore paths that begin with "http:" 23 | 24 | ## 0.2.3 25 | 26 | * Fully handle the `indent_size=tab` case 27 | 28 | ## 0.2.2 29 | 30 | * Fix bug #56 31 | 32 | ## 0.2.1 33 | 34 | * Display core messages in the Visual Studio error window 35 | 36 | ## 0.2 37 | 38 | * Initial release after re-writing the plugin from scratch 39 | -------------------------------------------------------------------------------- /Wrapper/utility.cpp: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include 6 | #include 7 | using namespace msclr::interop; 8 | 9 | /** 10 | * Converts a managed String object to UTF8. The caller must delete[] the 11 | * returned memory. 12 | */ 13 | char const *StringToUTF8(String ^string) 14 | { 15 | pin_ptr raw = PtrToStringChars(string); 16 | 17 | int size = WideCharToMultiByte(CP_UTF8, 0, raw, -1, NULL, 0, NULL, NULL); 18 | char *out = new char[size]; 19 | if (!out) 20 | return 0; 21 | 22 | WideCharToMultiByte(CP_UTF8, 0, raw, -1, out, size, NULL, NULL); 23 | return out; 24 | } 25 | 26 | /** 27 | * Converts a UTF-8 string to a managed String object. 28 | */ 29 | String ^UTF8ToString(char const *utf8) 30 | { 31 | int size = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); 32 | wchar_t *text = new wchar_t[size]; 33 | if (!text) 34 | throw gcnew OutOfMemoryException(); 35 | 36 | MultiByteToWideChar(CP_UTF8, 0, utf8, -1, text, size); 37 | String ^out = marshal_as(text); 38 | delete[] text; 39 | return out; 40 | } 41 | -------------------------------------------------------------------------------- /Plugin/PluginPackage.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.VisualStudio.Shell; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | 6 | namespace EditorConfig.VisualStudio 7 | { 8 | [PackageRegistration(UseManagedResourcesOnly = true)] 9 | [ProvideAutoLoad(UIContextGuids.NoSolution)] 10 | [Guid(GuidList.guidPluginPkgString)] 11 | public sealed class PluginPackage : Package 12 | { 13 | private static SaveListener _listener; 14 | 15 | internal static IViewSettingsContainer ViewSettingsContainer 16 | { 17 | get { return _listener; } 18 | } 19 | 20 | protected override void Dispose(bool disposing) 21 | { 22 | Debug.Assert(_listener != null); 23 | 24 | _listener.Dispose(); 25 | _listener = null; 26 | 27 | base.Dispose(disposing); 28 | } 29 | 30 | protected override void Initialize() 31 | { 32 | Debug.Assert(_listener == null); 33 | 34 | var rdt = (IVsRunningDocumentTable)GetService(typeof(SVsRunningDocumentTable)); 35 | _listener = new SaveListener(rdt); 36 | 37 | base.Initialize(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /EditorConfig.VisualStudio.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin", "Plugin\Plugin.csproj", "{47C063F7-1FE9-4695-9854-30DF51B87783}" 5 | EndProject 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Wrapper", "Wrapper\Wrapper.vcxproj", "{85F4C576-ECA4-44D7-98D0-FE065632C51E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {47C063F7-1FE9-4695-9854-30DF51B87783}.Debug|Win32.ActiveCfg = Debug|Any CPU 15 | {47C063F7-1FE9-4695-9854-30DF51B87783}.Debug|Win32.Build.0 = Debug|Any CPU 16 | {47C063F7-1FE9-4695-9854-30DF51B87783}.Release|Win32.ActiveCfg = Release|Any CPU 17 | {47C063F7-1FE9-4695-9854-30DF51B87783}.Release|Win32.Build.0 = Release|Any CPU 18 | {85F4C576-ECA4-44D7-98D0-FE065632C51E}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {85F4C576-ECA4-44D7-98D0-FE065632C51E}.Debug|Win32.Build.0 = Debug|Win32 20 | {85F4C576-ECA4-44D7-98D0-FE065632C51E}.Release|Win32.ActiveCfg = Release|Win32 21 | {85F4C576-ECA4-44D7-98D0-FE065632C51E}.Release|Win32.Build.0 = Release|Win32 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /Plugin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EditorConfig for Visual Studio")] 9 | [assembly: AssemblyDescription("EditorConfig plugin for Visual Studio")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("EditorConfig Team")] 12 | [assembly: AssemblyProduct("EditorConfig for Visual Studio")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /Wrapper/AssemblyInfo.cpp: -------------------------------------------------------------------------------- 1 | using namespace System; 2 | using namespace System::Reflection; 3 | using namespace System::Runtime::CompilerServices; 4 | using namespace System::Runtime::InteropServices; 5 | using namespace System::Security::Permissions; 6 | 7 | // 8 | // General Information about an assembly is controlled through the following 9 | // set of attributes. Change these attribute values to modify the information 10 | // associated with an assembly. 11 | // 12 | [assembly:AssemblyTitleAttribute("EditorConfig")]; 13 | [assembly:AssemblyDescriptionAttribute("A library for loading text editor settings")]; 14 | [assembly:AssemblyConfigurationAttribute("")]; 15 | [assembly:AssemblyCompanyAttribute("EditorConfig Team")]; 16 | [assembly:AssemblyProductAttribute("EditorConfig")]; 17 | [assembly:AssemblyCopyrightAttribute("")]; 18 | [assembly:AssemblyTrademarkAttribute("")]; 19 | [assembly:AssemblyCultureAttribute("")]; 20 | 21 | // 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the value or you can default the Revision and Build Numbers 30 | // by using the '*' as shown below: 31 | 32 | [assembly:AssemblyVersionAttribute("1.0.*")]; 33 | 34 | [assembly:ComVisible(false)]; 35 | 36 | [assembly:CLSCompliantAttribute(true)]; 37 | 38 | [assembly:SecurityPermission(SecurityAction::RequestMinimum, UnmanagedCode = true)]; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise stated, all files are distributed under the Simplified BSD 2 | license included below. 3 | 4 | Copyright (c) 2012 EditorConfig Team 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Plugin/ListenerBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | 4 | namespace EditorConfig.VisualStudio 5 | { 6 | internal abstract class ListenerBase : IVsRunningDocTableEvents3 7 | { 8 | public abstract int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, 9 | IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, 10 | IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew); 11 | 12 | public abstract int OnBeforeSave(uint docCookie); 13 | 14 | public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) 15 | { 16 | return VSConstants.S_OK; 17 | } 18 | 19 | public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) 20 | { 21 | return VSConstants.S_OK; 22 | } 23 | 24 | public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, 25 | uint dwEditLocksRemaining) 26 | { 27 | return VSConstants.S_OK; 28 | } 29 | 30 | public int OnAfterSave(uint docCookie) 31 | { 32 | return VSConstants.S_OK; 33 | } 34 | 35 | public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) 36 | { 37 | return VSConstants.S_OK; 38 | } 39 | 40 | public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, 41 | uint dwEditLocksRemaining) 42 | { 43 | return VSConstants.S_OK; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Plugin/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EditorConfig 5 | EditorConfig Team 6 | 0.3.2 7 | Loads editor options such as indentation sizes from standard, cross-platform .editorconfig files. 8 | 1033 9 | http://editorconfig.org/ 10 | ..\LICENSE 11 | logo.png 12 | 13 | 14 | Ultimate 15 | Premium 16 | Pro 17 | 18 | 19 | Ultimate 20 | Premium 21 | Pro 22 | 23 | 24 | Ultimate 25 | Premium 26 | Pro 27 | 28 | 29 | 30 | 31 | 32 | 33 | |%CurrentProject%;PkgdefProjectOutputGroup| 34 | |%CurrentProject%| 35 | 36 | 37 | -------------------------------------------------------------------------------- /Plugin/PluginFactory.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | using EnvDTE; 3 | using Microsoft.VisualStudio.Shell; 4 | using Microsoft.VisualStudio.Text; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | using Microsoft.VisualStudio.Utilities; 7 | 8 | namespace EditorConfig.VisualStudio 9 | { 10 | /// 11 | /// Listens for editor-creation events and attaches a new plugin instance 12 | /// to each one. Visual Studio to magically instantiates this class and 13 | /// calls it at the correct time, thanks to the fancy metadata. 14 | /// 15 | [Export(typeof(IWpfTextViewCreationListener))] 16 | [ContentType("text")] 17 | [TextViewRole(PredefinedTextViewRoles.Document)] 18 | internal sealed class PluginFactory : IWpfTextViewCreationListener 19 | { 20 | [Import] 21 | internal ITextDocumentFactoryService docFactory = null; 22 | 23 | [Import] 24 | internal SVsServiceProvider serviceProvider = null; 25 | 26 | ErrorListProvider messageList = null; 27 | 28 | /// 29 | /// Creates a plugin instance when a new text editor is opened 30 | /// 31 | public void TextViewCreated(IWpfTextView view) 32 | { 33 | ITextDocument document; 34 | if (!docFactory.TryGetTextDocument(view.TextDataModel.DocumentBuffer, out document)) 35 | return; 36 | 37 | DTE dte = (DTE)serviceProvider.GetService(typeof(DTE)); 38 | if (dte == null) 39 | return; 40 | 41 | if (messageList == null) 42 | { 43 | messageList = new ErrorListProvider(serviceProvider); 44 | messageList.ProviderGuid = new System.Guid("{6B4A6B64-EDA9-4078-A549-905ED7D6B8AA}"); 45 | messageList.ProviderName = "EditorConfig"; 46 | } 47 | 48 | new Plugin(view, document, dte, messageList, PluginPackage.ViewSettingsContainer); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # EditorConfig Visual Studio Plugin 2 | 3 | This plugin causes Visual Studio to load it's indentation options from a standard `.editorconfig` settings file. See the [project web site](http://editorconfig.org) for more information. 4 | 5 | ## Installing 6 | 7 | This plugin works with Visual Studio 2010 or later. The easiest way to install it is through Visual Studio's built-in Extension Manager. Just search for "EditorConfig" in the Online Gallery section. Or, download a copy from the [Visual Studio gallery](http://visualstudiogallery.msdn.microsoft.com/c8bccfe2-650c-4b42-bc5c-845e21f96328) website. 8 | 9 | ## Building 10 | 11 | To build this software, first download and build the [EditorConfig core library](https://github.com/editorconfig/editorconfig-core) in the `Core` directory. To automatically download the core library, use the git command: 12 | 13 | git submodule update --init 14 | 15 | Follow the [build instructions](https://github.com/editorconfig/editorconfig-core-c/blob/master/INSTALL.md#installing-from-source) for the core library as normal, but include the `-DMSVC_MD=ON` option when invoking CMake: 16 | 17 | cd Core/ 18 | cmake . -DMSVC_MD=ON 19 | 20 | Once the core library is built, open the solution file `EditorConfigVS.sln` and compile the plugin. You may need to install the [Visual Studio SDK](http://www.microsoft.com/en-us/download/details.aspx?id=21835) for this to work. The resulting plugin is named `Plugin/bin/(Debug|Release)/EditorConfigPlugin.vsix`, and double-clicking installs it into Visual Studio. 21 | 22 | ## Supported properties 23 | 24 | The plugin supports the following EditorConfig [properties](http://editorconfig.org/#supported-properties): 25 | 26 | * indent_style 27 | * indent_size 28 | * tab_width 29 | * end_of_line 30 | * insert_final_newline 31 | * trim_trailing_whitespace 32 | * root (only used by EditorConfig core) 33 | 34 | ## Reporting problems 35 | 36 | If you encounter any problems, feel free to report them at the central EditorConfig [issue tracker](https://github.com/editorconfig/editorconfig/issues). 37 | -------------------------------------------------------------------------------- /Wrapper/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | #include 3 | 4 | namespace EditorConfig { 5 | 6 | public ref class Results : public System::Collections::Generic::Dictionary 7 | { 8 | public: 9 | Results() 10 | {} 11 | }; 12 | 13 | public ref class ParseException : public System::Exception 14 | { 15 | private: 16 | String ^file; 17 | int line; 18 | 19 | public: 20 | ParseException(String ^file, int line) 21 | { 22 | this->line = line; 23 | this->file = file; 24 | } 25 | 26 | property String ^File { 27 | String ^get() { 28 | return this->file; 29 | } 30 | } 31 | 32 | property int Line { 33 | int get() { 34 | return this->line; 35 | } 36 | } 37 | }; 38 | 39 | public ref class CoreException : public System::Exception 40 | { 41 | public: 42 | CoreException(String ^message) : 43 | System::Exception(message) 44 | { 45 | } 46 | }; 47 | 48 | public ref class Core 49 | { 50 | public: 51 | static Results ^Parse(String ^filename) 52 | { 53 | char const *name = StringToUTF8(filename); 54 | editorconfig_handle handle = editorconfig_handle_init(); 55 | 56 | int rv = editorconfig_parse(name, handle); 57 | if (0 < rv) { 58 | throw gcnew ParseException(UTF8ToString(editorconfig_handle_get_err_file(handle)), rv); 59 | } else if (rv < 0) { 60 | throw gcnew CoreException(UTF8ToString(editorconfig_get_error_msg(rv))); 61 | } 62 | 63 | // Package the results into a .net collection datatype: 64 | Results ^dict = gcnew Results(); 65 | int count = editorconfig_handle_get_name_value_count(handle); 66 | for (int i = 0; i < count; ++i) { 67 | char const *name; 68 | char const *value; 69 | editorconfig_handle_get_name_value(handle, i, &name, &value); 70 | dict->Add(UTF8ToString(name), UTF8ToString(value)); 71 | } 72 | 73 | delete[] name; 74 | editorconfig_handle_destroy(handle); 75 | return dict; 76 | } 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /Plugin/FileSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EditorConfig.VisualStudio 4 | { 5 | internal class FileSettings 6 | { 7 | public FileSettings(Results results) 8 | { 9 | if (results == null) 10 | { 11 | throw new ArgumentNullException("results"); 12 | } 13 | 14 | TabWidth = IntOrNull(results, "tab_width"); 15 | IndentSize = IntOrNull(results, "indent_size"); 16 | InsertFinalNewLine = BoolOrNull(results, "insert_final_newline"); 17 | TrimTrailingWhitespace = BoolOrNull(results, "trim_trailing_whitespace"); 18 | 19 | if (results.ContainsKey("indent_style")) 20 | { 21 | switch (results["indent_style"]) 22 | { 23 | case "tab": 24 | ConvertTabsToSpaces = false; 25 | break; 26 | case "space": 27 | ConvertTabsToSpaces = true; 28 | break; 29 | } 30 | } 31 | 32 | if (results.ContainsKey("end_of_line")) 33 | { 34 | switch (results["end_of_line"]) 35 | { 36 | case "lf": 37 | EndOfLine = "\n"; 38 | break; 39 | case "cr": 40 | EndOfLine = "\r"; 41 | break; 42 | case "crlf": 43 | EndOfLine = "\r\n"; 44 | break; 45 | } 46 | } 47 | } 48 | 49 | public int? TabWidth { get; private set; } 50 | public int? IndentSize { get; private set; } 51 | public bool? ConvertTabsToSpaces { get; private set; } 52 | public string EndOfLine { get; private set; } 53 | public bool? InsertFinalNewLine { get; private set; } 54 | public bool? TrimTrailingWhitespace { get; private set; } 55 | 56 | private int? IntOrNull(Results results, string key) 57 | { 58 | if (!results.ContainsKey(key)) 59 | { 60 | return null; 61 | } 62 | 63 | try 64 | { 65 | return Convert.ToInt32(results[key]); 66 | } 67 | catch 68 | { 69 | return null; 70 | } 71 | } 72 | 73 | private bool? BoolOrNull(Results results, string key) 74 | { 75 | if (!results.ContainsKey(key)) 76 | { 77 | return null; 78 | } 79 | 80 | switch (results[key]) 81 | { 82 | case "true": 83 | return true; 84 | case "false": 85 | return false; 86 | default: 87 | return null; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Plugin/SettingsViewApplier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Text; 3 | using Microsoft.VisualStudio.Text.Editor; 4 | using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; 5 | 6 | namespace EditorConfig.VisualStudio 7 | { 8 | internal class SettingsViewApplier 9 | { 10 | public static void Update(IWpfTextView view, FileSettings settings) 11 | { 12 | if (settings.InsertFinalNewLine.HasValue 13 | && settings.InsertFinalNewLine.Value) 14 | { 15 | EnsureTrailingNewLine(view); 16 | } 17 | 18 | if (settings.TrimTrailingWhitespace.HasValue 19 | && settings.TrimTrailingWhitespace.Value) 20 | { 21 | TrimTrailingWhitespace(view); 22 | } 23 | } 24 | 25 | private static bool IsWhiteSpace(Char c) 26 | { 27 | switch (c) 28 | { 29 | case ' ': 30 | case '\t': 31 | case '\f': 32 | case '\v': 33 | return true; 34 | default: 35 | return false; 36 | } 37 | } 38 | 39 | private static void TrimTrailingWhitespace(IWpfTextView view) 40 | { 41 | ITextSnapshot snapshot = view.TextSnapshot; 42 | var lineCount = snapshot.LineCount; 43 | 44 | if (lineCount == 0) 45 | { 46 | return; 47 | } 48 | 49 | using (var edit = snapshot.TextBuffer.CreateEdit()) 50 | { 51 | for (int i = 0; i < lineCount; i++) 52 | { 53 | ITextSnapshotLine line = snapshot.GetLineFromLineNumber(i); 54 | 55 | int length = line.Length; 56 | 57 | if (length == 0) 58 | { 59 | continue; 60 | } 61 | 62 | string content = line.GetText(); 63 | 64 | int pos = length - 1; 65 | while (IsWhiteSpace(content[pos])) 66 | { 67 | pos --; 68 | } 69 | 70 | if (pos == length - 1) 71 | { 72 | continue; 73 | } 74 | 75 | edit.Delete(line.Start.Position + pos + 1, length - 1 - pos); 76 | } 77 | 78 | edit.Apply(); 79 | } 80 | } 81 | 82 | private static void EnsureTrailingNewLine(IWpfTextView view) 83 | { 84 | ITextSnapshot snapshot = view.TextSnapshot; 85 | var lineCount = snapshot.LineCount; 86 | 87 | if (lineCount == 0) 88 | { 89 | return; 90 | } 91 | 92 | ITextSnapshotLine line = snapshot.GetLineFromLineNumber(lineCount - 1); 93 | if (line.Length == 0) 94 | { 95 | return; 96 | } 97 | 98 | using (var edit = snapshot.TextBuffer.CreateEdit()) 99 | { 100 | edit.Insert(snapshot.Length, view.Options.GetNewLineCharacter()); 101 | edit.Apply(); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Wrapper/Wrapper.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {85F4C576-ECA4-44D7-98D0-FE065632C51E} 15 | v4.0 16 | ManagedCProj 17 | EditorConfig 18 | 19 | 20 | 21 | DynamicLibrary 22 | true 23 | true 24 | Unicode 25 | v110 26 | 27 | 28 | DynamicLibrary 29 | false 30 | true 31 | Unicode 32 | v110 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | true 46 | $(ProjectDir)$(Configuration)\ 47 | EditorConfigWrapper 48 | 49 | 50 | false 51 | $(ProjectDir)$(Configuration)\ 52 | EditorConfigWrapper 53 | 54 | 55 | 56 | Level3 57 | Disabled 58 | WIN32;_DEBUG;%(PreprocessorDefinitions) 59 | ..\Core\include 60 | 61 | 62 | true 63 | Shlwapi.lib;..\Core\lib\$(Configuration)\editorconfig_static.lib 64 | 65 | 66 | 67 | 68 | Level3 69 | WIN32;NDEBUG;%(PreprocessorDefinitions) 70 | ..\Core\include 71 | 72 | 73 | true 74 | Shlwapi.lib;..\Core\lib\$(Configuration)\editorconfig_static.lib 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Plugin/SaveListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using Microsoft.VisualStudio; 7 | using Microsoft.VisualStudio.Shell.Interop; 8 | using Microsoft.VisualStudio.Text.Editor; 9 | 10 | namespace EditorConfig.VisualStudio 11 | { 12 | internal class SaveListener : ListenerBase, IViewSettingsContainer, IDisposable 13 | { 14 | private readonly Dictionary views = new Dictionary(); 15 | 16 | private readonly IVsRunningDocumentTable _rdt; 17 | private readonly uint _pdwCookie; 18 | 19 | public SaveListener(IVsRunningDocumentTable rdt) 20 | { 21 | if (rdt == null) 22 | { 23 | throw new ArgumentNullException("rdt"); 24 | } 25 | 26 | _rdt = rdt; 27 | ErrorHandler.ThrowOnFailure(rdt.AdviseRunningDocTableEvents(this, out _pdwCookie)); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | Debug.Assert(views.Count == 0); 33 | 34 | _rdt.UnadviseRunningDocTableEvents(_pdwCookie); 35 | } 36 | 37 | public override int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, 38 | IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, 39 | IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) 40 | { 41 | 42 | if ((pszMkDocumentOld == null) && (pszMkDocumentNew == null)) 43 | { 44 | return VSConstants.S_OK; 45 | } 46 | 47 | ApplySettings(pszMkDocumentNew); 48 | 49 | return VSConstants.S_OK; 50 | } 51 | 52 | public override int OnBeforeSave(uint docCookie) 53 | { 54 | var filepath = NameFrom(docCookie); 55 | 56 | ApplySettings(filepath); 57 | 58 | return VSConstants.S_OK; 59 | } 60 | 61 | private string NameFrom(uint docCookie) 62 | { 63 | string pbstrMkDocument; 64 | var ppunkDocData = IntPtr.Zero; 65 | 66 | try 67 | { 68 | uint pgrfRdtFlags, pdwReadLocks, pdwEditLocks; 69 | IVsHierarchy ppHier; 70 | uint pitemid; 71 | 72 | ErrorHandler.ThrowOnFailure(_rdt.GetDocumentInfo(docCookie, 73 | out pgrfRdtFlags, out pdwReadLocks, out pdwEditLocks, 74 | out pbstrMkDocument, out ppHier, out pitemid, out ppunkDocData)); 75 | } 76 | finally 77 | { 78 | if (ppunkDocData != IntPtr.Zero) 79 | Marshal.Release(ppunkDocData); 80 | } 81 | 82 | return pbstrMkDocument; 83 | } 84 | 85 | private void ApplySettings(string filepath) 86 | { 87 | ViewsSettings viewsSettings; 88 | if (!views.TryGetValue(filepath, out viewsSettings)) 89 | { 90 | // This view isn't monitored by the Plugin 91 | // For instance, this will occur when working with 92 | // a non text view (e.g. An icon editor view) 93 | return; 94 | } 95 | 96 | SettingsViewApplier.Update(viewsSettings.Views.First(), viewsSettings.Settings); 97 | } 98 | 99 | public void Register(string filepath, IWpfTextView view, FileSettings settings) 100 | { 101 | ViewsSettings viewsSettings; 102 | 103 | if (!views.TryGetValue(filepath, out viewsSettings)) 104 | { 105 | viewsSettings = new ViewsSettings(view, settings); 106 | views.Add(filepath, viewsSettings); 107 | } 108 | else 109 | { 110 | Debug.Assert(!viewsSettings.Views.Contains(view)); 111 | 112 | viewsSettings.Views.Add(view); 113 | } 114 | } 115 | 116 | public void Unregister(string filepath, IWpfTextView view) 117 | { 118 | Debug.Assert(views.ContainsKey(filepath)); 119 | 120 | ViewsSettings viewsSettings = views[filepath]; 121 | 122 | Debug.Assert(viewsSettings.Views.Contains(view)); 123 | 124 | viewsSettings.Views.Remove(view); 125 | 126 | if (viewsSettings.Views.Count == 0) 127 | { 128 | views.Remove(filepath); 129 | } 130 | } 131 | 132 | public void Update(string oldFilepath, string newFilePath, IWpfTextView view, FileSettings newSettings) 133 | { 134 | Unregister(oldFilepath, view); 135 | Register(newFilePath, view, newSettings); 136 | } 137 | 138 | private class ViewsSettings 139 | { 140 | public HashSet Views 141 | { 142 | get; 143 | private set; 144 | } 145 | 146 | public FileSettings Settings 147 | { 148 | get; 149 | private set; 150 | } 151 | 152 | public ViewsSettings(IWpfTextView view, FileSettings settings) 153 | { 154 | Settings = settings; 155 | Views = new HashSet(); 156 | Views.Add(view); 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Plugin/Plugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9 | {47C063F7-1FE9-4695-9854-30DF51B87783} 10 | Library 11 | Properties 12 | EditorConfig.VisualStudio 13 | EditorConfigPlugin 14 | v4.0 15 | 16 | 17 | 18 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\VSSDK\Microsoft.VsSDK.targets 19 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets 20 | 21 | 22 | 23 | 11.0 24 | 25 | 26 | 27 | 12.0 28 | 29 | 30 | true 31 | full 32 | false 33 | bin\Debug\ 34 | DEBUG;TRACE 35 | prompt 36 | 4 37 | x86 38 | True 39 | True 40 | 41 | 42 | pdbonly 43 | true 44 | bin\Release\ 45 | TRACE 46 | prompt 47 | 4 48 | x86 49 | True 50 | True 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Always 67 | true 68 | 69 | 70 | Always 71 | true 72 | 73 | 74 | Designer 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {85F4C576-ECA4-44D7-98D0-FE065632C51E} 97 | Wrapper 98 | 99 | 100 | 101 | 10.0 102 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 103 | true 104 | 105 | 106 | 107 | 114 | -------------------------------------------------------------------------------- /Plugin/Plugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EnvDTE; 3 | using Microsoft.VisualStudio.Shell; 4 | using Microsoft.VisualStudio.Text; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | 7 | namespace EditorConfig.VisualStudio 8 | { 9 | /// 10 | /// This plugin attaches to an editor instance and updates its settings at 11 | /// the appropriate times 12 | /// 13 | internal class Plugin 14 | { 15 | IWpfTextView view; 16 | ITextDocument document; 17 | DTE dte; 18 | ErrorListProvider messageList; 19 | ErrorTask message; 20 | private readonly IViewSettingsContainer viewSettingsContainer; 21 | 22 | FileSettings settings; 23 | private string documentPath; 24 | 25 | public Plugin(IWpfTextView view, ITextDocument document, DTE dte, 26 | ErrorListProvider messageList, IViewSettingsContainer viewSettingsContainer) 27 | { 28 | this.view = view; 29 | this.document = document; 30 | this.dte = dte; 31 | this.messageList = messageList; 32 | this.message = null; 33 | this.viewSettingsContainer = viewSettingsContainer; 34 | 35 | document.FileActionOccurred += FileActionOccurred; 36 | view.GotAggregateFocus += GotAggregateFocus; 37 | view.Closed += Closed; 38 | 39 | documentPath = document.FilePath; 40 | 41 | LoadSettings(documentPath); 42 | viewSettingsContainer.Register(documentPath, view, settings); 43 | } 44 | 45 | /// 46 | /// Reloads the settings when the filename changes 47 | /// 48 | void FileActionOccurred(object sender, TextDocumentFileActionEventArgs e) 49 | { 50 | if (!e.FileActionType.HasFlag(FileActionTypes.DocumentRenamed)) 51 | return; 52 | 53 | LoadSettings(e.FilePath); 54 | viewSettingsContainer.Update(documentPath, e.FilePath, view, settings); 55 | 56 | documentPath = e.FilePath; 57 | 58 | if (settings != null && view.HasAggregateFocus) 59 | ApplyGlobalSettings(); 60 | } 61 | 62 | /// 63 | /// Updates the global settings when the local editor receives focus 64 | /// 65 | void GotAggregateFocus(object sender, EventArgs e) 66 | { 67 | if (settings != null) 68 | ApplyGlobalSettings(); 69 | } 70 | 71 | /// 72 | /// Removes the any messages when the document is closed 73 | /// 74 | void Closed(object sender, EventArgs e) 75 | { 76 | ClearMessage(); 77 | viewSettingsContainer.Unregister(documentPath, view); 78 | 79 | document.FileActionOccurred -= FileActionOccurred; 80 | view.GotAggregateFocus -= GotAggregateFocus; 81 | view.Closed -= Closed; 82 | } 83 | 84 | /// 85 | /// Loads the settings for the given file path 86 | /// 87 | private void LoadSettings(string path) 88 | { 89 | ClearMessage(); 90 | settings = null; 91 | 92 | // Prevent parsing of internet-located documents, 93 | // or documents that do not have proper paths. 94 | if (path.StartsWith("http:", StringComparison.OrdinalIgnoreCase) 95 | || path.Equals("Temp.txt")) 96 | return; 97 | 98 | try 99 | { 100 | settings = new FileSettings(Core.Parse(path)); 101 | ApplyLocalSettings(); 102 | } 103 | catch (ParseException e) 104 | { 105 | ShowError(path, "EditorConfig syntax error in file \"" + e.File + "\", line " + e.Line); 106 | } 107 | catch (CoreException e) 108 | { 109 | ShowError(path, "EditorConfig core error: " + e.Message); 110 | } 111 | } 112 | 113 | /// 114 | /// Applies settings to the local text editor instance 115 | /// 116 | private void ApplyLocalSettings() 117 | { 118 | IEditorOptions options = view.Options; 119 | 120 | if (settings.TabWidth != null) 121 | { 122 | int value = settings.TabWidth.Value; 123 | options.SetOptionValue(DefaultOptions.TabSizeOptionId, value); 124 | } 125 | 126 | if (settings.IndentSize != null) 127 | { 128 | int value = settings.IndentSize.Value; 129 | options.SetOptionValue(DefaultOptions.IndentSizeOptionId, value); 130 | } 131 | 132 | if (settings.ConvertTabsToSpaces != null) 133 | { 134 | bool value = settings.ConvertTabsToSpaces.Value; 135 | options.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, value); 136 | } 137 | 138 | if (settings.EndOfLine != null) 139 | { 140 | string value = settings.EndOfLine; 141 | options.SetOptionValue(DefaultOptions.NewLineCharacterOptionId, value); 142 | options.SetOptionValue(DefaultOptions.ReplicateNewLineCharacterOptionId, false); 143 | } 144 | } 145 | 146 | /// 147 | /// Applies settings to the global Visual Studio application. Some 148 | /// source-code formatters, such as curly-brace auto-indenter, ignore 149 | /// the local text editor settings. This causes horrible bugs when 150 | /// the local text-editor settings disagree with the formatter's 151 | /// settings. To fix this, just apply the same settings at the global 152 | /// application level as well. 153 | /// 154 | private void ApplyGlobalSettings() 155 | { 156 | Properties props; 157 | try 158 | { 159 | string type = view.TextDataModel.ContentType.TypeName; 160 | props = dte.Properties["TextEditor", type]; 161 | } 162 | catch 163 | { 164 | // If the above code didn't work, this particular content type 165 | // didn't need its settings changed anyhow 166 | return; 167 | } 168 | 169 | if (settings.TabWidth != null) 170 | { 171 | int value = settings.TabWidth.Value; 172 | props.Item("TabSize").Value = value; 173 | } 174 | 175 | if (settings.IndentSize != null) 176 | { 177 | int value = settings.IndentSize.Value; 178 | props.Item("IndentSize").Value = value; 179 | } 180 | 181 | if (settings.ConvertTabsToSpaces != null) 182 | { 183 | bool value = !settings.ConvertTabsToSpaces.Value; 184 | props.Item("InsertTabs").Value = value; 185 | } 186 | } 187 | 188 | /// 189 | /// Adds an error message to the Visual Studio tasks pane 190 | /// 191 | void ShowError(string path, string text) 192 | { 193 | message = new ErrorTask(); 194 | message.ErrorCategory = TaskErrorCategory.Error; 195 | message.Category = TaskCategory.Comments; 196 | message.Document = path; 197 | message.Line = 0; 198 | message.Column = 0; 199 | message.Text = text; 200 | 201 | messageList.Tasks.Add(message); 202 | messageList.Show(); 203 | } 204 | 205 | /// 206 | /// Removes the file's messages, if any 207 | /// 208 | void ClearMessage() 209 | { 210 | if (message != null) 211 | messageList.Tasks.Remove(message); 212 | message = null; 213 | } 214 | } 215 | } 216 | --------------------------------------------------------------------------------