summaryrefslogtreecommitdiffstats
path: root/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs
blob: c9945b2741cd6845f1be135b4a9138898c03e99a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//===-- ClangTidyPropertyGrid.cs - UI for configuring clang-tidy -*- C# -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// 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;
        }
    }
}