Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

Visual Studio Add-in for testing regular expressions

, 1 Dec 2010
An add-in for debugging and creating regular expression directly in Visual Studio
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using RegexTester.UserControls;
using System.Threading;
using System.Diagnostics;

namespace RegexTester
{
    /// <summary>
    /// Add-in form.
    /// </summary>
    public partial class RegexTesterForm : Form
    {
        private RichTextBox regularExpressionRichTextBox;
        private MessageDecorator messageControl;
        private RegexTesterModel model;
        private Thread updateThread;
        private Stopwatch monitorWatch = new Stopwatch();
        private Thread monitorThread;

        /// <summary>
        /// Make the form close on ESC.
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (msg.WParam.ToInt32() == (int)Keys.Escape)
            {
                DialogResult = DialogResult.Cancel;
                this.Close();
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }

        /// <summary>
        /// C.tor.
        /// </summary>
        public RegexTesterForm()
        {
            InitializeComponent();
            regularExpressionRichTextBox = new SyntaxHighlightRichTextBox();
            regularExpressionRichTextBox.Multiline = true;
            regularExpressionRichTextBox.BorderStyle = BorderStyle.None;
            regularExpressionRichTextBox.Font = richTextBoxMatches.Font;
            regularExpressionRichTextBox.DetectUrls = false;
            regularExpressionRichTextBox.TextChanged += regexParameters_Changed;
            regularExpressionRichTextBox.TabIndex = 1;
            messageControl = new MessageDecorator { WrappedControl = regularExpressionRichTextBox };
            showValidRegExTextBox.WrappedControl = messageControl;
            showValidRegExTextBox.BorderWidth = 4;

            checkBoxCompiled.CheckedChanged += regexParameters_Changed;
            checkBoxCultureInvariant.CheckedChanged += regexParameters_Changed;
            checkBoxECMAScript.CheckedChanged += regexParameters_Changed;
            checkBoxExplicitCapture.CheckedChanged += regexParameters_Changed;
            checkBoxIgnoreCase.CheckedChanged += regexParameters_Changed;
            checkBoxIgnorePatternWhitespace.CheckedChanged += regexParameters_Changed;
            checkBoxMultiLine.CheckedChanged += regexParameters_Changed;
            checkBoxRightToLeft.CheckedChanged += regexParameters_Changed;
            checkBoxSingleline.CheckedChanged += regexParameters_Changed;

            model = new RegexTesterModel();

            this.dataGridViewRegexs.Columns[0].DataPropertyName = "Name";
            this.dataGridViewRegexs.Columns[1].DataPropertyName = "Regex";
            this.dataGridViewRegexs.AutoGenerateColumns = false;
            RefreshLoadedRegexsView();

            updateThread = new Thread(UpdateFormThreadStart);
            updateThread.Start();
            monitorThread = new Thread(MonitorUpdateThreadStart);
            monitorThread.Start();
            Disposed += new EventHandler(RegexTesterForm_Disposed);

            Shown += new EventHandler(RegexTesterForm_Shown);
        }

        private void RegexTesterForm_Shown(object sender, EventArgs e)
        {
            lock (testFormUpdateIndexLock)
            {
                TestFormUpdateIndex++;
            }
        }

        private bool IsDisposed { get; set; }

        private void RegexTesterForm_Disposed(object sender, EventArgs e)
        {
            IsDisposed = true;
        }

        private List<RegexInfo> CurrentRegexs { get; set; }

        private bool IsRefreshingRegexView { get; set; }

        private void RefreshLoadedRegexsView()
        {
            IsRefreshingRegexView = true;
            CurrentRegexs = model.GetRegularExpressions().ToList();
            this.dataGridViewRegexs.DataSource = CurrentRegexs;
            IsRefreshingRegexView = false;
        }

        private object testFormUpdateIndexLock = new object();
        private int TestFormUpdateIndex { get; set; }

        private void UpdateFormThreadStart()
        {
            int maxIndex = -1;
            while (!IsDisposed)
            {
                bool updated = false;
                lock (testFormUpdateIndexLock)
                {
                    if (TestFormUpdateIndex > maxIndex)
                    {
                        maxIndex = TestFormUpdateIndex;
                        updated = true;
                    }
                }
                if (updated)
                {
                    monitorWatch.Start();
                    UpdateTestForm();
                    monitorWatch.Reset();
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
        }

        private void MonitorUpdateThreadStart()
        {
            while (!IsDisposed)
            {
                if (labelWarningEvaluationTime.Visible != monitorWatch.Elapsed.Seconds > 2)
                {
                    CallGUIFromOtherThread(() => { labelWarningEvaluationTime.Visible = !labelWarningEvaluationTime.Visible; });
                }
                Thread.Sleep(100);
            }
        }

        private void buttonResetEvaluation_Click(object sender, EventArgs e)
        {
            updateThread.Abort();
            updateThread.Join();
            ToMatch = "";
            updateThread = new Thread(UpdateFormThreadStart);
            updateThread.Start();
        }

        /// <summary>
        /// Regular expression evaluated by form.
        /// </summary>
        public string RegularExpression
        {
            get { return regularExpressionRichTextBox.Text; }
            set { regularExpressionRichTextBox.Text = value; }
        }

        public string ToMatch
        {
            get { return textBoxItemToMatch.Text; }
            set { textBoxItemToMatch.Text = value; }
        }

        public string ReplacePattern
        {
            get { return textBoxReplacePattern.Text; }
            set { textBoxReplacePattern.Text = value; }
        }

        public string ReplaceResult
        {
            get { return textBoxReplaceResult.Text; }
            set { textBoxReplaceResult.Text = value; }
        }

        public RegexOptions Options
        {
            set
            {
                checkBoxCompiled.Checked = (value & RegexOptions.Compiled) == RegexOptions.Compiled;
                checkBoxCultureInvariant.Checked = (value & RegexOptions.CultureInvariant) == RegexOptions.CultureInvariant;
                checkBoxECMAScript.Checked = (value & RegexOptions.ECMAScript) == RegexOptions.ECMAScript;
                checkBoxExplicitCapture.Checked = (value & RegexOptions.ExplicitCapture) == RegexOptions.ExplicitCapture;
                checkBoxIgnoreCase.Checked = (value & RegexOptions.IgnoreCase) == RegexOptions.IgnoreCase;
                checkBoxIgnorePatternWhitespace.Checked = (value & RegexOptions.IgnorePatternWhitespace) == RegexOptions.IgnorePatternWhitespace;
                checkBoxMultiLine.Checked = (value & RegexOptions.Multiline) == RegexOptions.Multiline;
                checkBoxRightToLeft.Checked = (value & RegexOptions.RightToLeft) == RegexOptions.RightToLeft;
                checkBoxSingleline.Checked = (value & RegexOptions.Singleline) == RegexOptions.Singleline;
            }
            get
            {
                RegexOptions regexOptions = RegexOptions.None;
                regexOptions |= checkBoxCompiled.Checked ? RegexOptions.Compiled : RegexOptions.None;
                regexOptions |= checkBoxCultureInvariant.Checked ? RegexOptions.CultureInvariant : RegexOptions.None;
                regexOptions |= checkBoxECMAScript.Checked ? RegexOptions.ECMAScript : RegexOptions.None;
                regexOptions |= checkBoxExplicitCapture.Checked ? RegexOptions.ExplicitCapture : RegexOptions.None;
                regexOptions |= checkBoxIgnoreCase.Checked ? RegexOptions.IgnoreCase : RegexOptions.None;
                regexOptions |= checkBoxIgnorePatternWhitespace.Checked ? RegexOptions.IgnorePatternWhitespace : RegexOptions.None;
                regexOptions |= checkBoxMultiLine.Checked ? RegexOptions.Multiline : RegexOptions.None;
                regexOptions |= checkBoxRightToLeft.Checked ? RegexOptions.RightToLeft : RegexOptions.None;
                regexOptions |= checkBoxSingleline.Checked ? RegexOptions.Singleline : RegexOptions.None;
                return regexOptions;
            }
        }

        private RegexInfo CurrentRegexInfo { get; set; }

        private delegate void InvokeDelegate();
        private void CallGUIFromOtherThread(Action a)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new InvokeDelegate(a));
            }
            else
            {
                a();
            }
        }

        private void UpdateTestForm()
        {
            string regularExpressionText = null;
            RegexOptions options = RegexOptions.None;
            string replacePatternText = null;
            string itemToMatchText = null;
            CallGUIFromOtherThread(() =>
            {
                regularExpressionText = regularExpressionRichTextBox.Text;
                options = Options;
                replacePatternText = textBoxReplacePattern.Text;
                itemToMatchText = textBoxItemToMatch.Text;
            });

            // Get regular expression
            ParsedRegex parsedRegex = model.GetRegularExpression(regularExpressionText, options);
            bool isValid = parsedRegex.Regex != null;
            // Update validity of text box showing regular expression.
            CallGUIFromOtherThread(() => { showValidRegExTextBox.IsValid = isValid; });
            if (isValid)
            {
                string replaceValue = parsedRegex.Regex.Replace(itemToMatchText, replacePatternText);
                CallGUIFromOtherThread(() =>
                {
                    // Set replace value.
                    textBoxReplaceResult.Text = replaceValue;
                    // Clear error message.
                    messageControl.ResetMessage();
                });
            }
            else
            {
                // Set error message.
                CallGUIFromOtherThread(() => messageControl.SetMessage(parsedRegex.ParseError));
            }
            // Build result tree.
            RegexResultTreeNode resultTree = model.BuildMatchTree(parsedRegex.Regex, itemToMatchText);
            CallGUIFromOtherThread(() =>
            {
                // Reset result rich text field.
                richTextBoxMatches.Clear();
                // Visit regular expression tree.
                resultTree.Accept(new RegexResultTreeToRichTextVisitor(richTextBoxMatches));
            });
        }

        private void SetCurrentRegexInfo(RegexInfo current, bool clearMatch)
        {
            this.CurrentRegexInfo = current;
            this.RegularExpression = current.Regex ?? "";
            this.ReplacePattern = current.ReplacePattern ?? "";
            this.Options = current.Options;
            if (clearMatch)
            {
                this.ToMatch = "";
            }
            this.Text = "Test Regular Expressions";
            if (current.Name != null)
            {
                this.Text += " - " + current.Name;
            }
            this.tabControlRegEx.SelectedTab = this.tabControlRegEx.TabPages[0];

        }

        private void regexParameters_Changed(object sender, EventArgs e)
        {
            lock (testFormUpdateIndexLock)
            {
                TestFormUpdateIndex++;
            }
        }

        private void buttonOk_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
            Close();
        }

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
            Close();
        }

        private void buttonNew_Click(object sender, EventArgs e)
        {
            SetCurrentRegexInfo(new RegexInfo(), true);
        }

        private void buttonSave_Click(object sender, EventArgs e)
        {
            RegexInfo toFill = null;
            if (CurrentRegexInfo != null && CurrentRegexInfo.Name != null)
            {
                toFill = CurrentRegexInfo;
            }
            else
            {
                SaveRegexForm saveForm = new SaveRegexForm();
                if (saveForm.ShowDialog() == DialogResult.OK)
                {
                    toFill = new RegexInfo
                    {
                        Name = saveForm.Name
                    };
                    CurrentRegexs.Add(toFill);
                }
            }
            if (toFill != null)
            {
                toFill.Options = this.Options;
                toFill.Regex = this.RegularExpression;
                toFill.ReplacePattern = this.ReplacePattern;
                model.SaveRegexs(CurrentRegexs);
                SetCurrentRegexInfo(toFill, false);

            }
            RefreshLoadedRegexsView();
        }

        private void buttonDelete_Click(object sender, EventArgs e)
        {
            List<RegexInfo> newList = new List<RegexInfo>();
            IDictionary<int, int> rowsToDelete = new Dictionary<int, int>();
            foreach (DataGridViewRow r in dataGridViewRegexs.SelectedRows)
            {
                rowsToDelete.Add(r.Index, r.Index);
            }
            for (int i = 0; i < CurrentRegexs.Count; i++)
            {
                if (!rowsToDelete.ContainsKey(i))
                {
                    newList.Add(CurrentRegexs[i]);
                }
            }
            model.SaveRegexs(newList);
            RefreshLoadedRegexsView();
        }

        private void dataGridViewRegexs_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            RegexInfo toLoad = CurrentRegexs[e.RowIndex];
            SetCurrentRegexInfo(toLoad, true);
        }
    }

    internal class RegexResultTreeToRichTextVisitor : IRegexResultTreeNodeVisitor
    {
        private RichTextBox richTextBox;
        private Stack<Color> colorStack;

        internal RegexResultTreeToRichTextVisitor(RichTextBox richTextBox)
        {
            this.richTextBox = richTextBox;
            this.colorStack = new Stack<Color>();
        }

        public void Visit(RootNode node, bool beforeChildren)
        {

        }

        public void Visit(MatchNode node, bool beforeChildren)
        {
            Font currentFont = richTextBox.SelectionFont;
            Font boldFont = new Font(currentFont, FontStyle.Bold);
            richTextBox.SelectionFont = boldFont;
            if (beforeChildren)
            {
                colorStack.Push(richTextBox.SelectionColor);
                richTextBox.SelectionColor = Color.Green;
            }
            richTextBox.AppendText(beforeChildren ? "[" : "]");
            if (!beforeChildren)
            {
                richTextBox.SelectionColor = colorStack.Pop();
            }
            richTextBox.SelectionFont = currentFont;
        }

        public void Visit(GroupNode node, bool beforeChildren)
        {
            Font currentFont = richTextBox.SelectionFont;
            Font boldFont = new Font(currentFont, FontStyle.Bold);
            richTextBox.SelectionFont = boldFont;
            if (beforeChildren)
            {
                colorStack.Push(richTextBox.SelectionColor);
                richTextBox.SelectionColor = Color.Red;
            }
            richTextBox.AppendText(beforeChildren ? "(<" + node.GroupName + ">" : ")");
            if (!beforeChildren)
            {
                richTextBox.SelectionColor = colorStack.Pop();
            }
            richTextBox.SelectionFont = currentFont;
        }

        public void Visit(LiteralNode node)
        {
            richTextBox.AppendText(node.Literal);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andreas Andersen
Software Developer (Senior)
Denmark Denmark
.NET developer. I wanted to be first an astronaut, then a jet pilot, but when I got a Commodore 64 for Christmas I never looked back. Also I would never have qualified for the first two things and everybody knows computer programmers get all the girls.

| Advertise | Privacy | Mobile
Web02 | 2.8.140922.1 | Last Updated 1 Dec 2010
Article Copyright 2010 by Andreas Andersen
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid