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