Click here to Skip to main content
15,892,161 members
Articles / Desktop Programming / Windows Forms

C# and VB.NET Code Searcher - Using Roslyn

Rate me:
Please Sign up or sign in to vote.
4.84/5 (51 votes)
7 Mar 2013LGPL314 min read 201.5K   5.6K   130  
A fast C# and VB.NET code searcher using Roslyn.
using System;
using System.Text;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace FastColoredTextBoxNS
{
    /// <summary>
    /// Diapason of text chars
    /// </summary>
    public class Range : IEnumerable<Place>
    {
        Place start;
        Place end;
        public readonly FastColoredTextBox tb;
        int preferedPos = -1;
        int updating = 0;

        string cachedText;
        List<Place> cachedCharIndexToPlace;
        int cachedTextVersion = -1;

        /// <summary>
        /// Constructor
        /// </summary>
        public Range(FastColoredTextBox tb)
        {
            this.tb = tb;
        }

        /// <summary>
        /// Return true if no selected text
        /// </summary>
        public virtual bool IsEmpty
        {
            get {
                if (ColumnSelectionMode)
                    return Start.iChar == End.iChar;
                else
                    return Start == End;
            }
        }

        private bool columnSelectionMode;

        /// <summary>
        /// Column selection mode
        /// </summary>
        public bool ColumnSelectionMode
        {
            get { return columnSelectionMode; }
            set { columnSelectionMode = value; }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public Range(FastColoredTextBox tb, int iStartChar, int iStartLine, int iEndChar, int iEndLine)
            : this(tb)
        {
            start = new Place(iStartChar, iStartLine);
            end = new Place(iEndChar, iEndLine);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public Range(FastColoredTextBox tb, Place start, Place end)
            : this(tb)
        {
            this.start = start;
            this.end = end;
        }

        public bool Contains(Place place)
        {
            if (place.iLine < Math.Min(start.iLine, end.iLine)) return false;
            if (place.iLine > Math.Max(start.iLine, end.iLine)) return false;

            Place s = start;
            Place e = end;

            if (s.iLine > e.iLine || (s.iLine == e.iLine && s.iChar > e.iChar))
            {
                var temp = s;
                s = e;
                e = temp;
            }

            if (place.iLine == s.iLine && place.iChar < s.iChar) return false;
            if (place.iLine == e.iLine && place.iChar > e.iChar) return false;

            return true;
        }

        /// <summary>
        /// Returns intersection with other range,
        /// empty range returned otherwise
        /// </summary>
        /// <param name="range"></param>
        /// <returns></returns>
        public virtual Range GetIntersectionWith(Range range)
        {
            if (ColumnSelectionMode)
                return GetIntersectionWith_ColumnSelectionMode(range);

            Range r1 = this.Clone();
            Range r2 = range.Clone();
            r1.Normalize();
            r2.Normalize();
            Place newStart = r1.Start > r2.Start ? r1.Start : r2.Start;
            Place newEnd = r1.End < r2.End ? r1.End : r2.End;
            if (newEnd < newStart) 
                return new Range(tb, start, start);
            return tb.GetRange(newStart, newEnd);
        }

        /// <summary>
        /// Returns union with other range.
        /// </summary>
        /// <param name="range"></param>
        /// <returns></returns>
        public Range GetUnionWith(Range range)
        {
            Range r1 = this.Clone();
            Range r2 = range.Clone();
            r1.Normalize();
            r2.Normalize();
            Place newStart = r1.Start < r2.Start ? r1.Start : r2.Start;
            Place newEnd = r1.End > r2.End ? r1.End : r2.End;

            return tb.GetRange(newStart, newEnd);
        }

        /// <summary>
        /// Select all chars of control
        /// </summary>
        public void SelectAll()
        {
            ColumnSelectionMode = false;

            Start = new Place(0, 0);
            if (tb.LinesCount == 0)
                Start = new Place(0, 0);
            else
            {
                end = new Place(0, 0);
                start = new Place(tb[tb.LinesCount - 1].Count, tb.LinesCount - 1);
            }
            if (this == tb.Selection)
                tb.Invalidate();
        }

        /// <summary>
        /// Start line and char position
        /// </summary>
        public Place Start
        {
            get { return start; }
            set
            {
                end = start = value;
                preferedPos = -1;
                OnSelectionChanged();
            }
        }

        /// <summary>
        /// Finish line and char position
        /// </summary>
        public Place End
        {
            get
            {
                return end;
            }
            set
            {
                end = value;
                OnSelectionChanged();
            }
        }

        /// <summary>
        /// Text of range
        /// </summary>
        /// <remarks>This property has not 'set' accessor because undo/redo stack works only with 
        /// FastColoredTextBox.Selection range. So, if you want to set text, you need to use FastColoredTextBox.Selection
        /// and FastColoredTextBox.InsertText() mehtod.
        /// </remarks>
        public virtual string Text
        {
            get
            {
                if (ColumnSelectionMode)
                    return Text_ColumnSelectionMode;

                int fromLine = Math.Min(end.iLine, start.iLine);
                int toLine = Math.Max(end.iLine, start.iLine);
                int fromChar = FromX;
                int toChar = ToX;
                if (fromLine < 0) return null;
                //
                StringBuilder sb = new StringBuilder();
                for (int y = fromLine; y <= toLine; y++)
                {
                    int fromX = y == fromLine ? fromChar : 0;
                    int toX = y == toLine ? Math.Min(tb[y].Count - 1, toChar - 1) : tb[y].Count - 1;
                    for (int x = fromX; x <= toX; x++)
                        sb.Append(tb[y][x].c);
                    if (y != toLine && fromLine != toLine)
                        sb.AppendLine();
                }
                return sb.ToString();
            }
        }

        internal void GetText(out string text, out List<Place> charIndexToPlace)
        {
            //try get cached text
            if (tb.TextVersion == cachedTextVersion)
            {
                text = cachedText;
                charIndexToPlace = cachedCharIndexToPlace;
                return;
            }
            //
            int fromLine = Math.Min(end.iLine, start.iLine);
            int toLine = Math.Max(end.iLine, start.iLine);
            int fromChar = FromX;
            int toChar = ToX;

            StringBuilder sb = new StringBuilder((toLine - fromLine)*50);
            charIndexToPlace = new List<Place>(sb.Capacity);
            if (fromLine >= 0)
            {
                for (int y = fromLine; y <= toLine; y++)
                {
                    int fromX = y == fromLine ? fromChar : 0;
                    int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
                    for (int x = fromX; x <= toX; x++)
                    {
                        sb.Append(tb[y][x].c);
                        charIndexToPlace.Add(new Place(x, y));
                    }
                    if (y != toLine && fromLine != toLine)
                    foreach (char c in Environment.NewLine)
                    {
                        sb.Append(c);
                        charIndexToPlace.Add(new Place(tb[y].Count/*???*/, y));
                    }
                }
            }
            text = sb.ToString();
            charIndexToPlace.Add(End > Start ? End : Start);
            //caching
            cachedText = text;
            cachedCharIndexToPlace = charIndexToPlace;
            cachedTextVersion = tb.TextVersion;
        }

        /// <summary>
        /// Returns first char after Start place
        /// </summary>
        public char CharAfterStart
        {
            get
            {
                if (Start.iChar >= tb[Start.iLine].Count)
                    return '\n';
                else
                    return tb[Start.iLine][Start.iChar].c;
            }
        }

        /// <summary>
        /// Returns first char before Start place
        /// </summary>
        public char CharBeforeStart
        {
            get
            {
                if (Start.iChar > tb[Start.iLine].Count)
                    return '\n';

                if (Start.iChar <= 0)
                    return '\n';
                else
                    return tb[Start.iLine][Start.iChar - 1].c;
            }
        }

        /// <summary>
        /// Clone range
        /// </summary>
        /// <returns></returns>
        public Range Clone()
        {
            return (Range)MemberwiseClone();
        }

        /// <summary>
        /// Return minimum of end.X and start.X
        /// </summary>
        internal int FromX
        {
            get
            {
                if (end.iLine < start.iLine) return end.iChar;
                if (end.iLine > start.iLine) return start.iChar;
                return Math.Min(end.iChar, start.iChar);
            }
        }

        /// <summary>
        /// Return maximum of end.X and start.X
        /// </summary>
        internal int ToX
        {
            get
            {
                if (end.iLine < start.iLine) return start.iChar;
                if (end.iLine > start.iLine) return end.iChar;
                return Math.Max(end.iChar, start.iChar);
            }
        }

        /// <summary>
        /// Move range right
        /// </summary>
        /// <remarks>This method jump over folded blocks</remarks>
        public bool GoRight()
        {
            Place prevStart = start;
            GoRight(false);
            return prevStart != start;
        }

        /// <summary>
        /// Move range left
        /// </summary>
        /// <remarks>This method can to go inside folded blocks</remarks>
        public virtual bool GoRightThroughFolded()
        {
            if (ColumnSelectionMode)
                return GoRightThroughFolded_ColumnSelectionMode();

            if (start.iLine >= tb.LinesCount - 1 && start.iChar >= tb[tb.LinesCount - 1].Count)
                return false;

            if (start.iChar < tb[start.iLine].Count)
                start.Offset(1, 0);
            else
                start = new Place(0, start.iLine + 1);

            preferedPos = -1;
            end = start;
            OnSelectionChanged();
            return true;
        }

        /// <summary>
        /// Move range left
        /// </summary>
        /// <remarks>This method jump over folded blocks</remarks>
        public bool GoLeft()
        {
            ColumnSelectionMode = false;

            Place prevStart = start;
            GoLeft(false);
            return prevStart != start;
        }

        /// <summary>
        /// Move range left
        /// </summary>
        /// <remarks>This method can to go inside folded blocks</remarks>
        public bool GoLeftThroughFolded()
        {
            ColumnSelectionMode = false;

            if (start.iChar == 0 && start.iLine == 0)
                return false;

            if (start.iChar > 0)
                start.Offset(-1, 0);
            else
                start = new Place(tb[start.iLine - 1].Count, start.iLine - 1);

            preferedPos = -1;
            end = start;
            OnSelectionChanged();
            return true;
        }

        public void GoLeft(bool shift)
        {
            ColumnSelectionMode = false;

            if (!shift)
            if (start > end)
            {
                Start = End;
                return;
            }

            if (start.iChar != 0 || start.iLine != 0)
            {
                if (start.iChar > 0 && tb.lineInfos[start.iLine].VisibleState == VisibleState.Visible)
                    start.Offset(-1, 0);
                else
                {
                    int i = tb.FindPrevVisibleLine(start.iLine);
                    if (i == start.iLine) return;
                    start = new Place(tb[i].Count, i);
                }
            }

            if (!shift)
                end = start;

            OnSelectionChanged();

            preferedPos = -1;
        }

        public void GoRight(bool shift)
        {
            ColumnSelectionMode = false;

            if (!shift)
            if (start < end)
            {
                Start = End;
                return;
            }

            if (start.iLine < tb.LinesCount - 1 || start.iChar < tb[tb.LinesCount - 1].Count)
            {
                if (start.iChar < tb[start.iLine].Count && tb.lineInfos[start.iLine].VisibleState == VisibleState.Visible)
                    start.Offset(1, 0);
                else
                {
                    int i = tb.FindNextVisibleLine(start.iLine);
                    if (i == start.iLine) return;
                    start = new Place(0, i);
                }
            }

            if (!shift)
                end = start;

            OnSelectionChanged();

            preferedPos = -1;
        }

        internal void GoUp(bool shift)
        {
            ColumnSelectionMode = false;

            if (!shift)
            if (start.iLine > end.iLine)
            {
                Start = End;
                return;
            }

            if (preferedPos < 0)
                preferedPos = start.iChar - tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));

            int iWW = tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
            if (iWW == 0)
            {
                if (start.iLine <= 0) return;
                int i = tb.FindPrevVisibleLine(start.iLine);
                if (i == start.iLine) return;
                start.iLine = i;
                iWW = tb.lineInfos[start.iLine].WordWrapStringsCount;
            }

            if (iWW > 0)
            {
                int finish = tb.lineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW - 1, tb[start.iLine]);
                start.iChar = tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(iWW - 1) + preferedPos;
                if (start.iChar > finish + 1)
                    start.iChar = finish + 1;
            }

            if (!shift)
                end = start;

            OnSelectionChanged();
        }

        internal void GoPageUp(bool shift)
        {
            ColumnSelectionMode = false;

            if (preferedPos < 0)
                preferedPos = start.iChar - tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));

            int pageHeight = tb.ClientRectangle.Height / tb.CharHeight - 1;

            for (int i = 0; i < pageHeight; i++)
            {
                int iWW = tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
                if (iWW == 0)
                {
                    if (start.iLine <= 0) break;
                    //pass hidden
                    int newLine = tb.FindPrevVisibleLine(start.iLine);
                    if (newLine == start.iLine) break;
                    start.iLine = newLine;
                    iWW = tb.lineInfos[start.iLine].WordWrapStringsCount;
                }

                if (iWW > 0)
                {
                    int finish = tb.lineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW - 1, tb[start.iLine]);
                    start.iChar = tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(iWW - 1) + preferedPos;
                    if (start.iChar > finish + 1)
                        start.iChar = finish + 1;
                }
            }

            if (!shift)
                end = start;

            OnSelectionChanged();
        }

        internal void GoDown(bool shift)
        {
            ColumnSelectionMode = false;

            if(!shift)
            if (start.iLine < end.iLine)
            {
                Start = End;
                return;
            }

            if (preferedPos < 0)
                preferedPos = start.iChar - tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));

            int iWW = tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
            if (iWW >= tb.lineInfos[start.iLine].WordWrapStringsCount - 1)
            {
                if (start.iLine >= tb.LinesCount - 1) return;
                //pass hidden
                int i = tb.FindNextVisibleLine(start.iLine);
                if (i == start.iLine) return;
                start.iLine = i;
                iWW = -1;
            }

            if (iWW < tb.lineInfos[start.iLine].WordWrapStringsCount - 1)
            {
                int finish = tb.lineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW + 1, tb[start.iLine]);
                start.iChar = tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(iWW + 1) + preferedPos;
                if (start.iChar > finish + 1)
                    start.iChar = finish + 1;
            }

            if (!shift)
                end = start;

            OnSelectionChanged();
        }

        internal void GoPageDown(bool shift)
        {
            ColumnSelectionMode = false;

            if (preferedPos < 0)
                preferedPos = start.iChar - tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));

            int pageHeight = tb.ClientRectangle.Height / tb.CharHeight - 1;

            for (int i = 0; i < pageHeight; i++)
            {
                int iWW = tb.lineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
                if (iWW >= tb.lineInfos[start.iLine].WordWrapStringsCount - 1)
                {
                    if (start.iLine >= tb.LinesCount - 1) break;
                    //pass hidden
                    int newLine = tb.FindNextVisibleLine(start.iLine);
                    if (newLine == start.iLine) break;
                    start.iLine = newLine;
                    iWW = -1;
                }

                if (iWW < tb.lineInfos[start.iLine].WordWrapStringsCount - 1)
                {
                    int finish = tb.lineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW + 1, tb[start.iLine]);
                    start.iChar = tb.lineInfos[start.iLine].GetWordWrapStringStartPosition(iWW + 1) + preferedPos;
                    if (start.iChar > finish + 1)
                        start.iChar = finish + 1;
                }
            }

            if (!shift)
                end = start;

            OnSelectionChanged();
        }

        internal void GoHome(bool shift)
        {
            ColumnSelectionMode = false;

            if (start.iLine < 0)
                return;

            if (tb.lineInfos[start.iLine].VisibleState != VisibleState.Visible)
                return;

            start = new Place(0, start.iLine);

            if (!shift)
                end = start;

            OnSelectionChanged();

            preferedPos = -1;
        }

        internal void GoEnd(bool shift)
        {
            ColumnSelectionMode = false;

            if (start.iLine < 0)
                return;
            if (tb.lineInfos[start.iLine].VisibleState != VisibleState.Visible)
                return;

            start = new Place(tb[start.iLine].Count, start.iLine);

            if (!shift)
                end = start;

            OnSelectionChanged();

            preferedPos = -1;
        }

        /// <summary>
        /// Set style for range
        /// </summary>
        public void SetStyle(Style style)
        {
            //search code for style
            int code = tb.GetOrSetStyleLayerIndex(style);
            //set code to chars
            SetStyle(ToStyleIndex(code));
            //
            tb.Invalidate();
        }

        /// <summary>
        /// Set style for given regex pattern
        /// </summary>
        public void SetStyle(Style style, string regexPattern)
        {
            //search code for style
            StyleIndex layer = ToStyleIndex(tb.GetOrSetStyleLayerIndex(style));
            SetStyle(layer, regexPattern, RegexOptions.None);
        }

        /// <summary>
        /// Set style for given regex
        /// </summary>
        public void SetStyle(Style style, Regex regex)
        {
            //search code for style
            StyleIndex layer = ToStyleIndex(tb.GetOrSetStyleLayerIndex(style));
            SetStyle(layer, regex);
        }


        /// <summary>
        /// Set style for given regex pattern
        /// </summary>
        public void SetStyle(Style style, string regexPattern, RegexOptions options)
        {
            //search code for style
            StyleIndex layer = ToStyleIndex(tb.GetOrSetStyleLayerIndex(style));
            SetStyle(layer, regexPattern, options);
        }

        /// <summary>
        /// Set style for given regex pattern
        /// </summary>
        public void SetStyle(StyleIndex styleLayer, string regexPattern, RegexOptions options)
        {
            if (Math.Abs(Start.iLine - End.iLine) > 1000)
                options |= SyntaxHighlighter.RegexCompiledOption;
            //
            foreach (var range in GetRanges(regexPattern, options))
                range.SetStyle(styleLayer);
            //
            tb.Invalidate();
        }

        /// <summary>
        /// Set style for given regex pattern
        /// </summary>
        public void SetStyle(StyleIndex styleLayer, Regex regex)
        {
            foreach (var range in GetRanges(regex))
                range.SetStyle(styleLayer);
            //
            tb.Invalidate();
        }

        /// <summary>
        /// Appends style to chars of range
        /// </summary>
        public void SetStyle(StyleIndex styleIndex)
        {
            //set code to chars
            int fromLine = Math.Min(End.iLine, Start.iLine);
            int toLine = Math.Max(End.iLine, Start.iLine);
            int fromChar = FromX;
            int toChar = ToX;
            if (fromLine < 0) return;
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                int fromX = y == fromLine ? fromChar : 0;
                int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
                for (int x = fromX; x <= toX; x++)
                {
                    Char c = tb[y][x];
                    c.style |= styleIndex;
                    tb[y][x] = c;
                }
            }
        }

        /// <summary>
        /// Sets folding markers
        /// </summary>
        /// <param name="startFoldingPattern">Pattern for start folding line</param>
        /// <param name="finishFoldingPattern">Pattern for finish folding line</param>
        public void SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern)
        {
            SetFoldingMarkers(startFoldingPattern, finishFoldingPattern, SyntaxHighlighter.RegexCompiledOption);
        }

        /// <summary>
        /// Sets folding markers
        /// </summary>
        /// <param name="startFoldingPattern">Pattern for start folding line</param>
        /// <param name="finishFoldingPattern">Pattern for finish folding line</param>
        public void SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern, RegexOptions options)
        {
            if (startFoldingPattern == finishFoldingPattern)
            {
                SetFoldingMarkers(startFoldingPattern, options);
                return;
            }

            foreach (var range in GetRanges(startFoldingPattern, options))
                tb[range.Start.iLine].FoldingStartMarker = startFoldingPattern;

            foreach (var range in GetRanges(finishFoldingPattern, options))
                tb[range.Start.iLine].FoldingEndMarker = startFoldingPattern;
            //
            tb.Invalidate();
        }

        /// <summary>
        /// Sets folding markers
        /// </summary>
        /// <param name="startEndFoldingPattern">Pattern for start and end folding line</param>
        public void SetFoldingMarkers(string foldingPattern, RegexOptions options)
        {
            foreach (var range in GetRanges(foldingPattern, options))
            {
                if (range.Start.iLine > 0)
                    tb[range.Start.iLine-1].FoldingEndMarker = foldingPattern;
                tb[range.Start.iLine].FoldingStartMarker = foldingPattern;
            }

            tb.Invalidate();
        }
        /// <summary>
        /// Finds ranges for given regex pattern
        /// </summary>
        /// <param name="regexPattern">Regex pattern</param>
        /// <returns>Enumeration of ranges</returns>
        public IEnumerable<Range> GetRanges(string regexPattern)
        {
            return GetRanges(regexPattern, RegexOptions.None);
        }

        /// <summary>
        /// Finds ranges for given regex pattern
        /// </summary>
        /// <param name="regexPattern">Regex pattern</param>
        /// <returns>Enumeration of ranges</returns>
        public IEnumerable<Range> GetRanges(string regexPattern, RegexOptions options)
        {
            //get text
            string text;
            List<Place> charIndexToPlace;
            GetText(out text, out charIndexToPlace);
            //create regex
            Regex regex = new Regex(regexPattern, options);
            //
            foreach (Match m in regex.Matches(text))
            {
                Range r = new Range(this.tb);
                //try get 'range' group, otherwise use group 0
                Group group = m.Groups["range"];
                if (!group.Success)
                    group = m.Groups[0];
                //
                r.Start = charIndexToPlace[group.Index];
                r.End = charIndexToPlace[group.Index + group.Length];
                yield return r;
            }
        }

        /// <summary>
        /// Finds ranges for given regex pattern.
        /// Search is separately in each line.
        /// This method requires less memory than GetRanges().
        /// </summary>
        /// <param name="regexPattern">Regex pattern</param>
        /// <returns>Enumeration of ranges</returns>
        public IEnumerable<Range> GetRangesByLines(string regexPattern, RegexOptions options)
        {
            Normalize();
            //create regex
            Regex regex = new Regex(regexPattern, options);
            //
            var fts = tb.TextSource as FileTextSource;//<----!!!! ugly
            //enumaerate lines
            for (int iLine = Start.iLine; iLine <= End.iLine; iLine++)
            {
                //
                bool isLineLoaded = fts != null ? fts.IsLineLoaded(iLine) : true;
                //
                var r = new Range(tb, new Place(0, iLine), new Place(tb[iLine].Count, iLine));
                if (iLine == Start.iLine || iLine == End.iLine)
                    r = r.GetIntersectionWith(this);

                foreach (var foundRange in r.GetRanges(regex))
                    yield return foundRange;

                if (!isLineLoaded)
                    fts.UnloadLine(iLine);
            }
        }

        /// <summary>
        /// Finds ranges for given regex
        /// </summary>
        /// <returns>Enumeration of ranges</returns>
        public IEnumerable<Range> GetRanges(Regex regex)
        {
            //get text
            string text;
            List<Place> charIndexToPlace;
            GetText(out text, out charIndexToPlace);
            //
            foreach (Match m in regex.Matches(text))
            {
                Range r = new Range(this.tb);
                //try get 'range' group, otherwise use group 0
                Group group = m.Groups["range"];
                if (!group.Success)
                    group = m.Groups[0];
                //
                r.Start = charIndexToPlace[group.Index];
                r.End = charIndexToPlace[group.Index + group.Length];
                yield return r;
            }
        }

        /// <summary>
        /// Clear styles of range
        /// </summary>
        public void ClearStyle(params Style[] styles)
        {
            try
            {
               ClearStyle(tb.GetStyleIndexMask(styles));
            }
            catch { ;}
        }

        /// <summary>
        /// Clear styles of range
        /// </summary>
        public void ClearStyle(StyleIndex styleIndex)
        {
            //set code to chars
            int fromLine = Math.Min(End.iLine, Start.iLine);
            int toLine = Math.Max(End.iLine, Start.iLine);
            int fromChar = FromX;
            int toChar = ToX;
            if (fromLine < 0) return;
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                int fromX = y == fromLine ? fromChar : 0;
                int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
                for (int x = fromX; x <= toX; x++)
                {
                    Char c = tb[y][x];
                    c.style &= ~styleIndex;
                    tb[y][x] = c;
                }
            }
            //
            tb.Invalidate();
        }

        /// <summary>
        /// Clear folding markers of all lines of range
        /// </summary>
        public void ClearFoldingMarkers()
        {
            //set code to chars
            int fromLine = Math.Min(End.iLine, Start.iLine);
            int toLine = Math.Max(End.iLine, Start.iLine);
            if (fromLine < 0) return;
            //
            for (int y = fromLine; y <= toLine; y++)
                tb[y].ClearFoldingMarkers();
            //
            tb.Invalidate();
        }

        void OnSelectionChanged()
        {
            //clear cache
            cachedTextVersion = -1;
            cachedText = null;
            cachedCharIndexToPlace = null;
            //
            if (tb.Selection == this)
                if (updating == 0)
                    tb.OnSelectionChanged();
        }

        /// <summary>
        /// Starts selection position updating
        /// </summary>
        public void BeginUpdate()
        {
            updating++;
        }

        /// <summary>
        /// Ends selection position updating
        /// </summary>
        public void EndUpdate()
        {
            updating--;
            if (updating == 0)
                OnSelectionChanged();
        }

        public override string ToString()
        {
            return "Start: " + Start + " End: " + End;
        }

        /// <summary>
        /// Exchanges Start and End if End appears before Start
        /// </summary>
        public void Normalize()
        {
            if (Start > End)
                Inverse();
        }

        /// <summary>
        /// Exchanges Start and End
        /// </summary>
        public void Inverse()
        {
            var temp = start;
            start = end;
            end = temp;
        }

        /// <summary>
        /// Expands range from first char of Start line to last char of End line
        /// </summary>
        public void Expand()
        {
            Normalize();
            start = new Place(0, start.iLine);
            end = new Place(tb.GetLineLength(end.iLine), end.iLine);
        }

        IEnumerator<Place> IEnumerable<Place>.GetEnumerator()
        {
            if (ColumnSelectionMode)
            {
                foreach(var p in GetEnumerator_ColumnSelectionMode())
                    yield return p;
                yield break;
            }

            int fromLine = Math.Min(end.iLine, start.iLine);
            int toLine = Math.Max(end.iLine, start.iLine);
            int fromChar = FromX;
            int toChar = ToX;
            if (fromLine < 0) yield break;
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                int fromX = y == fromLine ? fromChar : 0;
                int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
                for (int x = fromX; x <= toX; x++)
                    yield return new Place(x, y);
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (this as IEnumerable<Place>).GetEnumerator();
        }

        /// <summary>
        /// Get fragment of text around Start place. Returns maximal mathed to pattern fragment.
        /// </summary>
        /// <param name="allowedSymbolsPattern">Allowed chars pattern for fragment</param>
        /// <returns>Range of found fragment</returns>
        public Range GetFragment(string allowedSymbolsPattern)
        {
            return GetFragment(allowedSymbolsPattern, RegexOptions.None);
        }

        /// <summary>
        /// Get fragment of text around Start place. Returns maximal mathed to pattern fragment.
        /// </summary>
        /// <param name="allowedSymbolsPattern">Allowed chars pattern for fragment</param>
        /// <returns>Range of found fragment</returns>
        public Range GetFragment(string allowedSymbolsPattern, RegexOptions options)
        {
            Range r = new Range(tb);
            r.Start = Start;
            Regex regex = new Regex(allowedSymbolsPattern, options);
            //go left, check symbols
            while (r.GoLeftThroughFolded())
            {
                if (!regex.IsMatch(r.CharAfterStart.ToString()))
                {
                    r.GoRightThroughFolded();
                    break;
                }
            }
            Place startFragment = r.Start;

            r.Start = Start;
            //go right, check symbols
            do
            {
                if (!regex.IsMatch(r.CharAfterStart.ToString()))
                    break;
            } while (r.GoRightThroughFolded()) ;
            Place endFragment = r.Start;

            return new Range(tb, startFragment, endFragment);
        }

        bool IsIdentifierChar(char c)
        {
            return char.IsLetterOrDigit(c) || c == '_';
        }

        public void GoWordLeft(bool shift)
        {
            ColumnSelectionMode = false;

            if (start > end)
            {
                Start = End;
                return;
            }

            Range range = this.Clone();//for OnSelectionChanged disable

            Place prev;
            bool findIdentifier = IsIdentifierChar(range.CharBeforeStart);

            do{
                prev = range.Start;
                if (IsIdentifierChar(range.CharBeforeStart) ^ findIdentifier)
                    break;

                //move left
                range.GoLeft(shift);
            } while (prev != range.Start);

            this.Start = range.Start;
            this.End = range.End;

            if (tb.lineInfos[Start.iLine].VisibleState != VisibleState.Visible)
                GoRight(shift);
        }

        public void GoWordRight(bool shift)
        {
            ColumnSelectionMode = false;

            if (start < end)
            {
                Start = End;
                return;
            }

            Range range = this.Clone();//for OnSelectionChanged disable

            Place prev;
            bool findIdentifier = IsIdentifierChar(range.CharAfterStart);

            do
            {
                prev = range.Start;
                if (IsIdentifierChar(range.CharAfterStart) ^ findIdentifier)
                    break;

                //move right
                range.GoRight(shift);
            } while (prev != range.Start);

            this.Start = range.Start;
            this.End = range.End;

            if (tb.lineInfos[Start.iLine].VisibleState != VisibleState.Visible)
                GoLeft(shift);
        }

        internal void GoFirst(bool shift)
        {
            ColumnSelectionMode = false;

            start = new Place(0, 0);
            if (tb.lineInfos[Start.iLine].VisibleState != VisibleState.Visible)
                GoRight(shift);
            if(!shift)
                end = start;

            OnSelectionChanged();
        }

        internal void GoLast(bool shift)
        {
            ColumnSelectionMode = false;

            start = new Place(tb[tb.LinesCount - 1].Count, tb.LinesCount-1);
            if (tb.lineInfos[Start.iLine].VisibleState != VisibleState.Visible)
                GoLeft(shift);
            if (!shift)
                end = start;

            OnSelectionChanged();
        }

        public static StyleIndex ToStyleIndex(int i)
        {
            return (StyleIndex)(1 << i);
        }

        public RangeRect Bounds
        {
            get
            {
                int minX = Math.Min(Start.iChar, End.iChar);
                int minY = Math.Min(Start.iLine, End.iLine);
                int maxX = Math.Max(Start.iChar, End.iChar);
                int maxY = Math.Max(Start.iLine, End.iLine);
                return new RangeRect(minY, minX, maxY, maxX);
            }
        }

        public IEnumerable<Range> GetSubRanges(bool includeEmpty)
        {
            if (!ColumnSelectionMode)
            {
                yield return this;
                yield break;
            }

            var rect = Bounds;
            for (int y = rect.iStartLine; y <= rect.iEndLine; y++)
            {
                if (rect.iStartChar > tb[y].Count && !includeEmpty)
                    continue;

                var r = new Range(tb, rect.iStartChar, y, Math.Min(rect.iEndChar, tb[y].Count), y);
                yield return r;
            }
        }

        #region ColumnSelectionMode

        private Range GetIntersectionWith_ColumnSelectionMode(Range range)
        {
            if (range.Start.iLine != range.End.iLine)
                return new Range(tb, Start, Start);
            var rect = Bounds;
            if (range.Start.iLine < rect.iStartLine || range.Start.iLine > rect.iEndLine)
                return new Range(tb, Start, Start);

            return new Range(tb, rect.iStartChar, range.Start.iLine, rect.iEndChar, range.Start.iLine).GetIntersectionWith(range);
        }

        private bool GoRightThroughFolded_ColumnSelectionMode()
        {
            var boundes = Bounds;
            var endOfLines = true;
            for (int iLine = boundes.iStartLine; iLine <= boundes.iEndLine; iLine++)
                if(boundes.iEndChar < tb[iLine].Count)
                {
                    endOfLines = false;
                    break;
                }

            if (endOfLines)
                return false;

            var start = Start;
            var end = End;
            start.Offset(1, 0);
            end.Offset(1, 0);
            BeginUpdate();
            Start = start;
            End = end;
            EndUpdate();

            return true;
        }

        private IEnumerable<Place> GetEnumerator_ColumnSelectionMode()
        {
            var bounds = Bounds;
            if (bounds.iStartLine < 0) yield break;
            //
            for (int y = bounds.iStartLine; y <= bounds.iEndLine; y++)
            {
                for (int x = bounds.iStartChar; x < bounds.iEndChar; x++)
                {
                    if (x < tb[y].Count)
                        yield return new Place(x, y);
                }
            }
        }

        private string Text_ColumnSelectionMode
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                var bounds = Bounds;
                if (bounds.iStartLine < 0) return "";
                //
                for (int y = bounds.iStartLine; y <= bounds.iEndLine; y++)
                {
                    for (int x = bounds.iStartChar; x < bounds.iEndChar; x++)
                    {
                        if (x < tb[y].Count)
                            sb.Append(tb[y][x].c);
                    }
                    if (bounds.iEndLine != bounds.iStartLine && y != bounds.iEndLine)
                        sb.AppendLine();
                }

                return sb.ToString();
            }
        }

        internal void GoDown_ColumnSelectionMode()
        {
            var iLine = tb.FindNextVisibleLine(End.iLine);
            End = new Place(End.iChar, iLine);
        }

        internal void GoUp_ColumnSelectionMode()
        {
            var iLine = tb.FindPrevVisibleLine(End.iLine);
            End = new Place(End.iChar, iLine);
        }

        internal void GoRight_ColumnSelectionMode()
        {
            End = new Place(End.iChar + 1, End.iLine);
        }

        internal void GoLeft_ColumnSelectionMode()
        {
            if (End.iChar > 0)
                End = new Place(End.iChar - 1, End.iLine);
        }

        #endregion
    }

    public struct RangeRect
    {
        public RangeRect(int iStartLine, int iStartChar, int iEndLine, int iEndChar)
        {
            this.iStartLine = iStartLine;
            this.iStartChar = iStartChar;
            this.iEndLine = iEndLine;
            this.iEndChar = iEndChar;
        }

        public int iStartLine;
        public int iStartChar;
        public int iEndLine;
        public int iEndChar;
    }
}

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 GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior)
Netherlands Netherlands
Languages: C#, ASP.NET, HTML, VB, Javascript.
Tools: Visual Studio 2010, 2012.
Databases: MS SQL Server, Oracle, SQLite.
Skills: MCPD Web Developer 4.0, MCSD 2.0
Likes: Solving problems at projecteuler.net. at #66 now.
Technologies:C#, Azure, Xamarin.iOS, Web API, T/SQL, PL/SQL, MSBuild, WIX, XSLT, WPF, WCF, JavaScript

I have been a programmer since 1995. At first at school where we had a computer club with nice Aster CT-80 (TRS-80 clone) computers and 1 MSX computer where I learned to program in BASIC. Later I had my own 8086 XT computer.
My first experience with programming was way before that when I was about 8. A friend of mine had a Commodore 64. He typed over a BASIC game from a computing magazine. The program worked but it contained an error. The thing he did was go to the source line that caused the error, deleted the line, and restarted the program. It worked every time he got an error...

Comments and Discussions