//===-- ClangTidyPropertyGrid.cs - UI for configuring clang-tidy -*- 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 UserControl consisting of a .NET PropertyGrid control // allowing configuration of checks and check options for ClangTidy. // //===----------------------------------------------------------------------===// using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; using Microsoft.VisualStudio.Shell; namespace LLVM.ClangTidy { /// /// A UserControl displaying a PropertyGrid allowing configuration of clang-tidy /// checks and check options, as well as serialization and deserialization of /// clang-tidy configuration files. When a configuration file is loaded, the /// entire chain of configuration files is analyzed based on the file path, /// and quick access is provided to edit or view any of the files in the /// configuration chain, allowing easy visualization of where values come from /// (similar in spirit to the -explain-config option of clang-tidy). /// public partial class ClangTidyPropertyGrid : UserControl { /// /// The sequence of .clang-tidy configuration files, starting from the root /// of the filesystem, down to the selected file. /// List> PropertyChain_ = null; public ClangTidyPropertyGrid() { InitializeComponent(); InitializeSettings(); } private enum ShouldCancel { Yes, No, } public void SaveSettingsToStorage() { PersistUnsavedChanges(false); } private ShouldCancel PersistUnsavedChanges(bool PromptFirst) { var UnsavedResults = PropertyChain_.Where(x => x.Key != null && x.Value.GetHasUnsavedChanges()); if (UnsavedResults.Count() == 0) return ShouldCancel.No; bool ShouldSave = false; if (PromptFirst) { var Response = MessageBox.Show( "You have unsaved changes! Do you want to save before loading a new file?", "clang-tidy", MessageBoxButtons.YesNoCancel); ShouldSave = (Response == DialogResult.Yes); if (Response == DialogResult.Cancel) return ShouldCancel.Yes; } else ShouldSave = true; if (ShouldSave) { foreach (var Result in UnsavedResults) { ClangTidyConfigParser.SerializeClangTidyFile(Result.Value, Result.Key); Result.Value.SetHasUnsavedChanges(false); } } return ShouldCancel.No; } public void InitializeSettings() { PropertyChain_ = new List>(); PropertyChain_.Add(new KeyValuePair(null, ClangTidyProperties.RootProperties)); reloadPropertyChain(); } private void button1_Click(object sender, EventArgs e) { ShouldCancel Cancel = PersistUnsavedChanges(true); if (Cancel == ShouldCancel.Yes) return; using (OpenFileDialog D = new OpenFileDialog()) { D.Filter = "Clang Tidy files|.clang-tidy"; D.CheckPathExists = true; D.CheckFileExists = true; if (D.ShowDialog() == DialogResult.OK) { PropertyChain_.Clear(); PropertyChain_ = ClangTidyConfigParser.ParseConfigurationChain(D.FileName); textBox1.Text = D.FileName; reloadPropertyChain(); } } } private static readonly string DefaultText = "(Default)"; private static readonly string BrowseText = "Browse for a file to edit its properties"; /// /// After a new configuration file is chosen, analyzes the directory hierarchy /// and finds all .clang-tidy files in the path, parses them and updates the /// PropertyGrid and quick-access LinkLabel control to reflect the new property /// chain. /// private void reloadPropertyChain() { StringBuilder LinkBuilder = new StringBuilder(); LinkBuilder.Append(DefaultText); LinkBuilder.Append(" > "); int PrefixLength = LinkBuilder.Length; if (PropertyChain_.Count == 1) LinkBuilder.Append(BrowseText); else LinkBuilder.Append(PropertyChain_[PropertyChain_.Count - 1].Key); linkLabelPath.Text = LinkBuilder.ToString(); // Given a path like D:\Foo\Bar\Baz, construct a LinkLabel where individual // components of the path are clickable iff they contain a .clang-tidy file. // Clicking one of the links then updates the PropertyGrid to display the // selected .clang-tidy file. ClangTidyProperties LastProps = ClangTidyProperties.RootProperties; linkLabelPath.Links.Clear(); linkLabelPath.Links.Add(0, DefaultText.Length, LastProps); foreach (var Prop in PropertyChain_.Skip(1)) { LastProps = Prop.Value; string ClangTidyFolder = Path.GetFileName(Prop.Key); int ClangTidyFolderOffset = Prop.Key.Length - ClangTidyFolder.Length; linkLabelPath.Links.Add(PrefixLength + ClangTidyFolderOffset, ClangTidyFolder.Length, LastProps); } propertyGrid1.SelectedObject = LastProps; } private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) { ClangTidyProperties Props = (ClangTidyProperties)propertyGrid1.SelectedObject; Props.SetHasUnsavedChanges(true); // When a CategoryVerb is selected, perform the corresponding action. PropertyDescriptor Property = e.ChangedItem.PropertyDescriptor; if (!(e.ChangedItem.Value is CategoryVerb)) return; CategoryVerb Action = (CategoryVerb)e.ChangedItem.Value; if (Action == CategoryVerb.None) return; var Category = Property.Attributes.OfType().FirstOrDefault(); if (Category == null) return; var SameCategoryProps = Props.GetProperties(new Attribute[] { Category }); foreach (PropertyDescriptor P in SameCategoryProps) { if (P == Property) continue; switch (Action) { case CategoryVerb.Disable: P.SetValue(propertyGrid1.SelectedObject, false); break; case CategoryVerb.Enable: P.SetValue(propertyGrid1.SelectedObject, true); break; case CategoryVerb.Inherit: P.ResetValue(propertyGrid1.SelectedObject); break; } } Property.ResetValue(propertyGrid1.SelectedObject); propertyGrid1.Invalidate(); } private void linkLabelPath_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ClangTidyProperties Props = (ClangTidyProperties)e.Link.LinkData; propertyGrid1.SelectedObject = Props; } } }