1 //===-- ClangTidyPropertyGrid.cs - UI for configuring clang-tidy -*- C# -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This class contains a UserControl consisting of a .NET PropertyGrid control 10 // allowing configuration of checks and check options for ClangTidy. 11 // 12 //===----------------------------------------------------------------------===// 13 using System; 14 using System.Collections.Generic; 15 using System.ComponentModel; 16 using System.Drawing; 17 using System.Data; 18 using System.Linq; 19 using System.Text; 20 using System.Threading.Tasks; 21 using System.Windows.Forms; 22 using System.IO; 23 using Microsoft.VisualStudio.Shell; 24 25 namespace LLVM.ClangTidy 26 { 27 /// <summary> 28 /// A UserControl displaying a PropertyGrid allowing configuration of clang-tidy 29 /// checks and check options, as well as serialization and deserialization of 30 /// clang-tidy configuration files. When a configuration file is loaded, the 31 /// entire chain of configuration files is analyzed based on the file path, 32 /// and quick access is provided to edit or view any of the files in the 33 /// configuration chain, allowing easy visualization of where values come from 34 /// (similar in spirit to the -explain-config option of clang-tidy). 35 /// </summary> 36 public partial class ClangTidyPropertyGrid : UserControl 37 { 38 /// <summary> 39 /// The sequence of .clang-tidy configuration files, starting from the root 40 /// of the filesystem, down to the selected file. 41 /// </summary> 42 List<KeyValuePair<string, ClangTidyProperties>> PropertyChain_ = null; 43 ClangTidyPropertyGrid()44 public ClangTidyPropertyGrid() 45 { 46 InitializeComponent(); 47 InitializeSettings(); 48 } 49 50 private enum ShouldCancel 51 { 52 Yes, 53 No, 54 } 55 SaveSettingsToStorage()56 public void SaveSettingsToStorage() 57 { 58 PersistUnsavedChanges(false); 59 } 60 PersistUnsavedChanges(bool PromptFirst)61 private ShouldCancel PersistUnsavedChanges(bool PromptFirst) 62 { 63 var UnsavedResults = PropertyChain_.Where(x => x.Key != null && x.Value.GetHasUnsavedChanges()); 64 if (UnsavedResults.Count() == 0) 65 return ShouldCancel.No; 66 67 bool ShouldSave = false; 68 if (PromptFirst) 69 { 70 var Response = MessageBox.Show( 71 "You have unsaved changes! Do you want to save before loading a new file?", 72 "clang-tidy", 73 MessageBoxButtons.YesNoCancel); 74 75 ShouldSave = (Response == DialogResult.Yes); 76 if (Response == DialogResult.Cancel) 77 return ShouldCancel.Yes; 78 } 79 else 80 ShouldSave = true; 81 82 if (ShouldSave) 83 { 84 foreach (var Result in UnsavedResults) 85 { 86 ClangTidyConfigParser.SerializeClangTidyFile(Result.Value, Result.Key); 87 Result.Value.SetHasUnsavedChanges(false); 88 } 89 } 90 return ShouldCancel.No; 91 } 92 InitializeSettings()93 public void InitializeSettings() 94 { 95 PropertyChain_ = new List<KeyValuePair<string, ClangTidyProperties>>(); 96 PropertyChain_.Add(new KeyValuePair<string, ClangTidyProperties>(null, ClangTidyProperties.RootProperties)); 97 reloadPropertyChain(); 98 } 99 button1_Click(object sender, EventArgs e)100 private void button1_Click(object sender, EventArgs e) 101 { 102 ShouldCancel Cancel = PersistUnsavedChanges(true); 103 if (Cancel == ShouldCancel.Yes) 104 return; 105 106 using (OpenFileDialog D = new OpenFileDialog()) 107 { 108 D.Filter = "Clang Tidy files|.clang-tidy"; 109 D.CheckPathExists = true; 110 D.CheckFileExists = true; 111 112 if (D.ShowDialog() == DialogResult.OK) 113 { 114 PropertyChain_.Clear(); 115 PropertyChain_ = ClangTidyConfigParser.ParseConfigurationChain(D.FileName); 116 textBox1.Text = D.FileName; 117 reloadPropertyChain(); 118 } 119 } 120 } 121 122 private static readonly string DefaultText = "(Default)"; 123 private static readonly string BrowseText = "Browse for a file to edit its properties"; 124 125 /// <summary> 126 /// After a new configuration file is chosen, analyzes the directory hierarchy 127 /// and finds all .clang-tidy files in the path, parses them and updates the 128 /// PropertyGrid and quick-access LinkLabel control to reflect the new property 129 /// chain. 130 /// </summary> reloadPropertyChain()131 private void reloadPropertyChain() 132 { 133 StringBuilder LinkBuilder = new StringBuilder(); 134 LinkBuilder.Append(DefaultText); 135 LinkBuilder.Append(" > "); 136 int PrefixLength = LinkBuilder.Length; 137 138 if (PropertyChain_.Count == 1) 139 LinkBuilder.Append(BrowseText); 140 else 141 LinkBuilder.Append(PropertyChain_[PropertyChain_.Count - 1].Key); 142 143 linkLabelPath.Text = LinkBuilder.ToString(); 144 145 // Given a path like D:\Foo\Bar\Baz, construct a LinkLabel where individual 146 // components of the path are clickable iff they contain a .clang-tidy file. 147 // Clicking one of the links then updates the PropertyGrid to display the 148 // selected .clang-tidy file. 149 ClangTidyProperties LastProps = ClangTidyProperties.RootProperties; 150 linkLabelPath.Links.Clear(); 151 linkLabelPath.Links.Add(0, DefaultText.Length, LastProps); 152 foreach (var Prop in PropertyChain_.Skip(1)) 153 { 154 LastProps = Prop.Value; 155 string ClangTidyFolder = Path.GetFileName(Prop.Key); 156 int ClangTidyFolderOffset = Prop.Key.Length - ClangTidyFolder.Length; 157 linkLabelPath.Links.Add(PrefixLength + ClangTidyFolderOffset, ClangTidyFolder.Length, LastProps); 158 } 159 propertyGrid1.SelectedObject = LastProps; 160 } 161 propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)162 private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) 163 { 164 ClangTidyProperties Props = (ClangTidyProperties)propertyGrid1.SelectedObject; 165 Props.SetHasUnsavedChanges(true); 166 167 // When a CategoryVerb is selected, perform the corresponding action. 168 PropertyDescriptor Property = e.ChangedItem.PropertyDescriptor; 169 if (!(e.ChangedItem.Value is CategoryVerb)) 170 return; 171 172 CategoryVerb Action = (CategoryVerb)e.ChangedItem.Value; 173 if (Action == CategoryVerb.None) 174 return; 175 176 var Category = Property.Attributes.OfType<CategoryAttribute>().FirstOrDefault(); 177 if (Category == null) 178 return; 179 var SameCategoryProps = Props.GetProperties(new Attribute[] { Category }); 180 foreach (PropertyDescriptor P in SameCategoryProps) 181 { 182 if (P == Property) 183 continue; 184 switch (Action) 185 { 186 case CategoryVerb.Disable: 187 P.SetValue(propertyGrid1.SelectedObject, false); 188 break; 189 case CategoryVerb.Enable: 190 P.SetValue(propertyGrid1.SelectedObject, true); 191 break; 192 case CategoryVerb.Inherit: 193 P.ResetValue(propertyGrid1.SelectedObject); 194 break; 195 } 196 } 197 Property.ResetValue(propertyGrid1.SelectedObject); 198 propertyGrid1.Invalidate(); 199 } 200 linkLabelPath_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)201 private void linkLabelPath_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 202 { 203 ClangTidyProperties Props = (ClangTidyProperties)e.Link.LinkData; 204 propertyGrid1.SelectedObject = Props; 205 } 206 } 207 } 208