//===-- 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 | |
{ | |
/// <summary> | |
/// 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). | |
/// </summary> | |
public partial class ClangTidyPropertyGrid : UserControl | |
{ | |
/// <summary> | |
/// The sequence of .clang-tidy configuration files, starting from the root | |
/// of the filesystem, down to the selected file. | |
/// </summary> | |
List<KeyValuePair<string, ClangTidyProperties>> 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<KeyValuePair<string, ClangTidyProperties>>(); | |
PropertyChain_.Add(new KeyValuePair<string, ClangTidyProperties>(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"; | |
/// <summary> | |
/// 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. | |
/// </summary> | |
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<CategoryAttribute>().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; | |
} | |
} | |
} |