// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.IO; using System.Drawing; namespace UnrealControls { /// /// This delegate is for creating filtered documents. /// /// The line of text that is being checked for filtering. /// User supplied data that may control filtering. /// True if the line of text is to be filtered out of the document. public delegate bool OutputWindowDocumentFilterDelegate(OutputWindowDocument.ReadOnlyDocumentLine Line, object Data); /// /// A document that can be viewed by multiple 's. /// public class OutputWindowDocument { /// /// Represents a line of colored text within a document. /// internal class DocumentLine : ICloneable { //StringBuilder mText = new StringBuilder(); StringBuilder mBldrText = new StringBuilder(); string mFinalText; List mColorRanges = new List(); ICloneable mUserData; /// /// Gets the length of the line in characters. /// public int Length { get { return mFinalText == null ? mBldrText.Length : mFinalText.Length; } } /// /// Gets the character at the specified index. /// /// The index of the character to get. /// The character at the specified index. public char this[int Index] { get { return mFinalText == null ? mBldrText[Index] : mFinalText[Index]; } } /// /// Gets/Sets the user data associated with the line. /// public ICloneable UserData { get { return mUserData; } set { mUserData = value; } } /// /// Appends a character to the line of text. /// /// The color of the character. /// The character to append. public void Append(Color? TxtColor, char CharToAppend) { System.Diagnostics.Debug.Assert(mBldrText != null); if(mColorRanges.Count == 0) { mColorRanges.Add(new ColoredTextRange(TxtColor, null, 0, 1)); } else { int CurColorIndex = mColorRanges.Count - 1; if(mColorRanges[CurColorIndex].ForeColor != TxtColor) { mColorRanges.Add(new ColoredTextRange(TxtColor, null, mBldrText.Length, 1)); } else { ++mColorRanges[CurColorIndex].Length; } } mBldrText.Append(CharToAppend); System.Diagnostics.Debug.Assert(CountEOL() <= 1); } /// /// This is a debug function that counts the number of EOL's in a line. There should only ever be 1. /// /// The number of EOL's in the line. int CountEOL() { System.Diagnostics.Debug.Assert(mBldrText != null); int NumEOL = 0; for(int i = 0; i < mBldrText.Length; ++i) { if(mBldrText[i] == '\n' && i > 0 && mBldrText[i - 1] == '\r') { ++NumEOL; } } return NumEOL; } /// /// Appends text to the end of the line. /// /// The color of the text. /// The text to append. public void Append(Color? TxtColor, string TxtToAppend) { System.Diagnostics.Debug.Assert(mBldrText != null); if(mColorRanges.Count == 0) { mColorRanges.Add(new ColoredTextRange(TxtColor, null, 0, TxtToAppend.Length)); } else { int CurColorIndex = mColorRanges.Count - 1; if(mColorRanges[CurColorIndex].ForeColor != TxtColor) { mColorRanges.Add(new ColoredTextRange(TxtColor, null, mBldrText.Length, TxtToAppend.Length)); } else { mColorRanges[CurColorIndex].Length += TxtToAppend.Length; } } mBldrText.Append(TxtToAppend); System.Diagnostics.Debug.Assert(CountEOL() <= 1); } /// /// Gets an array of colored line segments that can be used for drawing. /// /// An array of colored line segments. public ColoredDocumentLineSegment[] GetLineSegments() { ColoredDocumentLineSegment[] Segments = new ColoredDocumentLineSegment[mColorRanges.Count]; int CurSegment = 0; foreach(ColoredTextRange CurRange in mColorRanges) { Segments[CurSegment] = new ColoredDocumentLineSegment(new ColorPair(CurRange.BackColor, CurRange.ForeColor), this.ToString(CurRange.StartIndex, CurRange.Length)); } return Segments; } /// /// Gets an array of colored line segments that can be used for drawing. /// /// The starting character at which to begin generating segments. /// The number of characters to include in segment generation. /// An array of colored line segments. public ColoredDocumentLineSegment[] GetLineSegments(int StartIndex, int Length) { List Segments = new List(mColorRanges.Count); int CharsToCopy = 0; bool bFoundStartSegment = false; foreach(ColoredTextRange CurRange in mColorRanges) { if(bFoundStartSegment) { CharsToCopy = Math.Min(Length, CurRange.Length); Segments.Add(new ColoredDocumentLineSegment(new ColorPair(CurRange.BackColor, CurRange.ForeColor), this.ToString(CurRange.StartIndex, CharsToCopy))); Length -= CharsToCopy; if(Length <= 0) { break; } } else { if(CurRange.StartIndex <= StartIndex) { bFoundStartSegment = true; CharsToCopy = Math.Min(Length, CurRange.Length); Segments.Add(new ColoredDocumentLineSegment(new ColorPair(CurRange.BackColor, CurRange.ForeColor), this.ToString(Math.Max(StartIndex, CurRange.StartIndex), CharsToCopy))); Length -= CharsToCopy; if(Length <= 0) { break; } } } } return Segments.ToArray(); } /// /// Gets an array of colored line segments that can be used for drawing. /// /// /// If the line of text contains any instances of they will be replaced with new line segments that contain for drawing. /// /// The starting character at which to begin generating segments. /// The number of characters to include in segment generation. /// Text to search for that requires special coloring. /// The colors to use when drawing . /// The type of string comparison that will be conducted when searching for . /// Set to True if exists within the line segments. /// An array of colored line segments. public ColoredDocumentLineSegment[] GetLineSegmentsWithFindString(int StartIndex, int Length, string FindText, ColorPair InsertionColors, StringComparison ComparisonType, out bool bHasFindText) { string LineText = this.ToString(); int FindTextIndex = LineText.IndexOf(FindText, ComparisonType); int EndLineIndex = StartIndex + Length; List FindTextSegments = new List(mColorRanges.Count); List Segments = new List(mColorRanges.Count * 2); while(FindTextIndex != -1) { if(FindTextIndex >= EndLineIndex) { break; } // If any part of the text is past StartIndex it will be visible so add it to the array if(FindTextIndex + FindText.Length > StartIndex) { if(FindTextIndex >= StartIndex) { FindTextSegments.Add(new ColoredTextRange(InsertionColors.ForeColor, InsertionColors.BackColor, FindTextIndex, FindText.Length)); } else { FindTextSegments.Add(new ColoredTextRange(InsertionColors.ForeColor, InsertionColors.BackColor, StartIndex, (FindTextIndex + FindText.Length) - StartIndex)); } } FindTextIndex = LineText.IndexOf(FindText, FindTextIndex + FindText.Length, ComparisonType); } bHasFindText = FindTextSegments.Count > 0; int CurrentFindTextSegmentIndex = 0; bool bHasOverlap = false; foreach(ColoredTextRange TextRange in this.mColorRanges) { if(TextRange.StartIndex < StartIndex && TextRange.StartIndex + TextRange.Length <= StartIndex) { continue; } else if(TextRange.StartIndex >= EndLineIndex) { break; } int CurTextRangeStart = Math.Max(StartIndex, TextRange.StartIndex); if(CurrentFindTextSegmentIndex < FindTextSegments.Count) { int CurTextRangeEnd = Math.Min(TextRange.StartIndex + TextRange.Length, EndLineIndex); // check to see if it's an overlapping find segment if(bHasOverlap) { // if it is then it has already been added so reassign the start point CurTextRangeStart = FindTextSegments[CurrentFindTextSegmentIndex].StartIndex + FindTextSegments[CurrentFindTextSegmentIndex].Length; ++CurrentFindTextSegmentIndex; bHasOverlap = false; } while(CurrentFindTextSegmentIndex < FindTextSegments.Count && CurTextRangeStart < EndLineIndex && FindTextSegments[CurrentFindTextSegmentIndex].StartIndex >= CurTextRangeStart && FindTextSegments[CurrentFindTextSegmentIndex].StartIndex < CurTextRangeEnd) { if(FindTextSegments[CurrentFindTextSegmentIndex].StartIndex > CurTextRangeStart) { Segments.Add(new ColoredDocumentLineSegment(new ColorPair(TextRange.BackColor, TextRange.ForeColor), this.ToString(CurTextRangeStart, Math.Min(FindTextSegments[CurrentFindTextSegmentIndex].StartIndex - CurTextRangeStart, EndLineIndex - CurTextRangeStart)))); } if(FindTextSegments[CurrentFindTextSegmentIndex].StartIndex < EndLineIndex) { Segments.Add(new ColoredDocumentLineSegment(InsertionColors, this.ToString(FindTextSegments[CurrentFindTextSegmentIndex].StartIndex, Math.Min(FindTextSegments[CurrentFindTextSegmentIndex].Length, EndLineIndex - FindTextSegments[CurrentFindTextSegmentIndex].StartIndex)))); } CurTextRangeStart = FindTextSegments[CurrentFindTextSegmentIndex].StartIndex + FindTextSegments[CurrentFindTextSegmentIndex].Length; // if the beginning of the next segment is within the current text range move onto the next "find" segment // if not that means the current "find" text segment overlaps into the next regular text segment if(CurTextRangeStart < CurTextRangeEnd) { ++CurrentFindTextSegmentIndex; } else { bHasOverlap = true; } } // if there was text remaining in the current text range after the last segment then add it now if(CurTextRangeStart < CurTextRangeEnd && CurTextRangeStart < EndLineIndex) { Segments.Add(new ColoredDocumentLineSegment(new ColorPair(TextRange.BackColor, TextRange.ForeColor), this.ToString(CurTextRangeStart, Math.Min(CurTextRangeEnd - CurTextRangeStart, EndLineIndex - CurTextRangeStart)))); } } else { Segments.Add(new ColoredDocumentLineSegment(new ColorPair(TextRange.BackColor, TextRange.ForeColor), this.ToString(CurTextRangeStart, Math.Min(TextRange.Length, EndLineIndex - CurTextRangeStart)))); } } return Segments.ToArray(); } /// /// Finishes construction of the line so that reads and searches may be optimized. /// public void FinishBuilding() { if(mFinalText == null) { mFinalText = mBldrText.ToString(); mBldrText = null; } } /// /// Returns the text associated with the line. /// /// The text associated with the line. public override string ToString() { return mFinalText == null ? mBldrText.ToString() : mFinalText; } /// /// Returns the text associated with the line. /// /// The character index that the returned text will start at. /// The number of characters to return. /// A string containing the text within the specified range of the line. public string ToString(int StartIndex, int Length) { if(mFinalText == null) { return mBldrText.ToString(StartIndex, Length); } return mFinalText.Substring(StartIndex, Length); } #region ICloneable Members public object Clone() { DocumentLine NewLine = new DocumentLine(); if(mBldrText != null) { NewLine.mBldrText.Append(mBldrText.ToString()); } else { NewLine.mBldrText = null; } NewLine.mFinalText = mFinalText; NewLine.mColorRanges = new List(mColorRanges); if(mUserData != null) { NewLine.mUserData = mUserData.Clone() as ICloneable; } return NewLine; } #endregion } /// /// This structure represents a read only document line. /// /// /// This is a structure for performance reasons. This will be allocated for every line that is completed for the OnLineAdded event and if it was a class it would increase GC pressure. /// public struct ReadOnlyDocumentLine { DocumentLine mDocLine; internal ReadOnlyDocumentLine(DocumentLine DocLine) { mDocLine = DocLine; } /// /// Gets the length of the line in characters. /// public int Length { get { return mDocLine.Length; } } /// /// Gets/Sets the user data associated with the line. /// public ICloneable UserData { get { return mDocLine.UserData; } set { mDocLine.UserData = value; } } /// /// Gets the character at the specified index. /// /// The index of the character to get. /// The character at the specified index. public char this[int Index] { get { return mDocLine[Index]; } } /// /// Gets an array of colored line segments that can be used for drawing. /// /// An array of colored line segments. public ColoredDocumentLineSegment[] GetLineSegments() { return mDocLine.GetLineSegments(); } /// /// Gets an array of colored line segments that can be used for drawing. /// /// The starting character at which to begin generating segments. /// The number of characters to include in segment generation. /// An array of colored line segments. public ColoredDocumentLineSegment[] GetLineSegments(int StartIndex, int Length) { return mDocLine.GetLineSegments(StartIndex, Length); } /// /// Returns the text associated with the line. /// /// The text associated with the line. public override string ToString() { return mDocLine.ToString(); } /// /// Returns the text associated with the line. /// /// The character index that the returned text will start at. /// The number of characters to return. /// A string containing the text within the specified range of the line. public string ToString(int StartIndex, int Length) { return mDocLine.ToString(StartIndex, Length); } } List mLines = new List(); int mLongestLineLength; EventHandler mOnLineAdded; EventHandler mOnModified; /// /// Triggered when a full line of text has been added to the document. /// public event EventHandler LineAdded { add { mOnLineAdded += value; } remove { mOnLineAdded -= value; } } /// /// Triggered when the document has been modified. /// public event EventHandler Modified { add { mOnModified += value; } remove { mOnModified -= value; } } /// /// Gets/Sets the text in the document. /// public string Text { set { if(value == null) { throw new ArgumentNullException("value"); } Clear(); AppendText(null, value); } get { StringBuilder Bldr = new StringBuilder(); foreach(DocumentLine CurLine in mLines) { Bldr.Append(CurLine.ToString()); } return Bldr.ToString(); } } /// /// Gets the lines associated with the document. /// public string[] Lines { get { string[] Ret = new string[mLines.Count]; for(int i = 0; i < mLines.Count; ++i) { Ret[i] = mLines[i].ToString(); } return Ret; } } /// /// Gets the number of lines in the document. /// public int LineCount { get { return mLines.Count; } } /// /// Gets the length in characters of the longest line in the document. /// public int LongestLineLength { get { return mLongestLineLength; } } /// /// Gets the of the beginning of the document. /// public TextLocation BeginningOfDocument { get { return new TextLocation(0, 0); } } /// /// Gets the of the end of the document. /// public TextLocation EndOfDocument { get { int LineIndex = Math.Max(0, mLines.Count - 1); return new TextLocation(LineIndex, GetLineLength(LineIndex)); } } /// /// Constructor. /// public OutputWindowDocument() { // The document always has at least 1 line. mLines.Add(new DocumentLine()); } /// /// Appends text of the specified color to the document. /// /// The color of the text to be appended /// The text to be appended public void AppendText(Color? TxtColor, string Txt) { if(Txt.Length == 0) { return; } DocumentLine CurLine; int CurLineLength = 0; if(mLines.Count == 0) { CurLine = new DocumentLine(); mLines.Add(CurLine); } else { CurLine = mLines[mLines.Count - 1]; } char LastChar = (char)0; // Break the Text in to multiple lines String[] Lines = System.Text.RegularExpressions.Regex.Split(Txt, "\r\n"); foreach (String Line in Lines) { // Skip empty lines if (Line.Length == 0) { continue; } // Put back the \r\n String ThisLine = Line + "\r\n"; Color? CurrColor = TxtColor; // Check for keywords if (ThisLine.Contains("Error")) { CurrColor = Color.Red; } else if (ThisLine.Contains("Warning")) { CurrColor = Color.Orange; } // Append the line foreach (char CurChar in ThisLine) { if (CurChar == '\n' && LastChar != '\r') { CurLine.Append(CurrColor, '\r'); } // replace tabs with 4 spaces if (CurChar == '\t') { CurLine.Append(CurrColor, " "); } else { CurLine.Append(CurrColor, CurChar); } if (CurChar == '\n') { // only count displayable characters (cut \r\n) CurLineLength = CurLine.Length - 2; if (CurLineLength > mLongestLineLength) { mLongestLineLength = CurLineLength; } // This isn't required but it enables readonly optimizations CurLine.FinishBuilding(); // A full line has been created so trigger the line added event OnLineAdded(new OutputWindowDocumentLineAddedEventArgs(mLines.Count - 1, new ReadOnlyDocumentLine(CurLine))); // Then begin our new line CurLine = new DocumentLine(); mLines.Add(CurLine); } LastChar = CurChar; } } // NOTE: This line hasn't been terminated yet so we don't have to account for \r\n at the end CurLineLength = CurLine.Length; if(CurLineLength > mLongestLineLength) { mLongestLineLength = CurLineLength; } OnModified(new EventArgs()); } /// /// Appends a line of colored text to the document. /// /// The color of the text to be appended. /// The text to append. public void AppendLine(Color? TxtColor, string Txt) { AppendText(TxtColor, Txt + Environment.NewLine); } /// /// Retrieves a line of text from the document. /// /// The index of the line to retrieve. /// True if the line includes the EOL characters. /// A string containing the text of the specified line. public string GetLine(int Index, bool bIncludeEOL) { DocumentLine Line = mLines[Index]; if(!bIncludeEOL && Line.Length >= 2 && Line[Line.Length - 1] == '\n' && Line[Line.Length - 2] == '\r') { return Line.ToString(0, Line.Length - 2); } return Line.ToString(); } /// /// Retrieves a line of text from the document. /// /// The index of the line to retrieve. /// The character to start copying. /// The number of characters to copy. /// A string containing the specified range of text text in the specified line. public string GetLine(int LineIndex, int CharOffset, int Length) { return mLines[LineIndex].ToString(CharOffset, Length); } /// /// Gets the length of the line in characters at the specified index. /// /// The index of the line to retrieve length information for. /// The length of the line in characters at the specified index. public int GetLineLength(int Index) { if(Index >= 0 && Index < mLines.Count) { return GetLineLength(mLines[Index]); } return 0; } /// /// Gets the length of the line in characters. /// /// The line whose length is to be retrieved. /// The length of the line with the EOL characters removed (if they exist). private static int GetLineLength(DocumentLine Line) { if(Line.Length >= 2 && Line[Line.Length - 1] == '\n' && Line[Line.Length - 2] == '\r') { return Line.Length - 2; } else { return Line.Length; } } /// /// Clears the document. /// public void Clear() { mLines.Clear(); mLongestLineLength = 0; OnModified(new EventArgs()); } /// /// Finds the first occurence of the specified string within the document if it exists. /// /// The string to find. /// The position within the document to begin searching. /// The position within the document to end searching. /// Flags telling the document how to conduct its search. /// The location within the document of the supplied text. /// True if the text was found. public bool Find(string Txt, TextLocation StartLoc, TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result) { if((Flags & RichTextBoxFinds.Reverse) == RichTextBoxFinds.Reverse) { return FindReverse(Txt, ref StartLoc, ref EndLoc, Flags, out Result); } else { return FindForward(Txt, ref StartLoc, ref EndLoc, Flags, out Result); } } /// /// Looks forward in the document from the specified location for the supplied text. /// /// The text to find. /// The location to start searching from. /// The location to stop searching at. /// Flags that control how the search is performed. /// Receives the result. /// True if a match was found. private bool FindForward(string Txt, ref TextLocation StartLoc, ref TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result) { bool bMatchWord; bool bFound = false; bool bIsWord; StringComparison ComparisonFlags; SetupFindState(Txt, ref StartLoc, ref EndLoc, Flags, out Result, out bIsWord, out ComparisonFlags, out bMatchWord); for(int CurLineIndex = StartLoc.Line; CurLineIndex <= EndLoc.Line && !bFound; ++CurLineIndex) { if(GetLineLength(CurLineIndex) == 0) { continue; } DocumentLine CurLineBldr = mLines[CurLineIndex]; string LineTxt; int ColumnIndex = 0; if(CurLineIndex == StartLoc.Line && StartLoc.Column > 0) { LineTxt = CurLineBldr.ToString(StartLoc.Column, CurLineBldr.Length - StartLoc.Column); ColumnIndex = StartLoc.Column; } else if(CurLineIndex == EndLoc.Line && EndLoc.Column < GetLineLength(CurLineIndex)) { LineTxt = CurLineBldr.ToString(0, EndLoc.Column + 1); } else { LineTxt = CurLineBldr.ToString(); } int Index = LineTxt.IndexOf(Txt, ComparisonFlags); if(Index != -1) { ColumnIndex += Index; CheckForWholeWord(Txt, ref Result, bMatchWord, ref bFound, bIsWord, CurLineIndex, CurLineBldr, ColumnIndex, Index); } } return bFound; } /// /// Checks to see if a text location is a matching word. /// /// The text being searched for. /// Receives the result if the text location is a matching word. /// True if an entire word is to be matched. /// Set to true if a matching word is found. /// True if is a valid word. /// The index of the current line. /// The text of the current line. /// The character index within the line of text where the matching will begin. /// The index of a match within the range of searchable characters for the current line. The true line index is + . private static void CheckForWholeWord(string Txt, ref FindResult Result, bool bMatchWord, ref bool bFound, bool bIsWord, int CurLineIndex, DocumentLine CurLine, int ColumnIndex, int Index) { int FinalCharIndex = ColumnIndex + Txt.Length; if(bMatchWord && bIsWord) { if((FinalCharIndex >= CurLine.Length || !IsWordCharacter(CurLine[FinalCharIndex])) && (ColumnIndex == 0 || !IsWordCharacter(CurLine[ColumnIndex - 1]))) { bFound = true; } } else { bFound = true; } if(bFound) { Result.Line = CurLineIndex; Result.Column = ColumnIndex; Result.Length = Txt.Length; } } /// /// Checks to see if a character is valid within a word. /// /// The character to check. /// True if the character is valid within a word. private static bool IsWordCharacter(char CharToCheck) { return char.IsLetterOrDigit(CharToCheck) || CharToCheck == '_'; } /// /// Performs general housekeeping for setting up a search. /// /// The text to search for. /// The location to begin searching from. /// The location to stop searching at. /// Flags controlling how the search is performed. /// Receives the resulting location if a match is found. /// Set to true if is a valid word. /// Receives flags controlling how strings are compared. /// Is set to true if only full words are to be matched. private void SetupFindState(string Txt, ref TextLocation StartLoc, ref TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result, out bool bIsWord, out StringComparison ComparisonFlags, out bool bMatchWord) { Result = FindResult.Empty; if(!IsValidTextLocation(StartLoc)) { throw new ArgumentException("StartLoc is an invalid text location!"); } if(!IsValidTextLocation(EndLoc)) { throw new ArgumentException("EndLoc is an invalid text location!"); } if((Flags & RichTextBoxFinds.Reverse) == RichTextBoxFinds.Reverse) { if(StartLoc < EndLoc) { throw new ArgumentException("StartLoc must be greater than EndLoc when doing a reverse search!"); } } else { if(StartLoc > EndLoc) { throw new ArgumentException("StartLoc must be less than EndLoc when doing a forward search!"); } } bMatchWord = (Flags & RichTextBoxFinds.WholeWord) == RichTextBoxFinds.WholeWord; bIsWord = IsWord(0, Txt); ComparisonFlags = StringComparison.OrdinalIgnoreCase; if((Flags & RichTextBoxFinds.MatchCase) == RichTextBoxFinds.MatchCase) { ComparisonFlags = StringComparison.Ordinal; } } /// /// Checks a string to see if it contains a valid word. /// /// The index to start validating at. /// The text to be validated. /// True if all text including and after are part of a valid word. private static bool IsWord(int Index, string Txt) { for(; Index < Txt.Length; ++Index) { char CurChar = Txt[Index]; if(!char.IsLetterOrDigit(CurChar) && CurChar != '_') { return false; } } return true; } /// /// Searches for a string in the reverse direction of . /// /// The text to search for. /// The starting location of the search. /// The ending location of the search. /// Flags controlling how the searching is conducted. /// Receives the results of the search. /// True if a match was found. private bool FindReverse(string Txt, ref TextLocation StartLoc, ref TextLocation EndLoc, RichTextBoxFinds Flags, out FindResult Result) { bool bFound = false; bool bMatchWord; bool bIsWord; StringComparison ComparisonFlags; SetupFindState(Txt, ref StartLoc, ref EndLoc, Flags, out Result, out bIsWord, out ComparisonFlags, out bMatchWord); for(int CurLineIndex = StartLoc.Line; CurLineIndex >= EndLoc.Line && !bFound; --CurLineIndex) { if(GetLineLength(CurLineIndex) == 0) { continue; } DocumentLine CurLineBldr = mLines[CurLineIndex]; string LineTxt; int ColumnIndex = 0; if(CurLineIndex == StartLoc.Line && StartLoc.Column < GetLineLength(CurLineIndex)) { LineTxt = CurLineBldr.ToString(0, StartLoc.Column); } else if(CurLineIndex == EndLoc.Line && EndLoc.Column > 0) { LineTxt = CurLineBldr.ToString(EndLoc.Column, CurLineBldr.Length - EndLoc.Column); ColumnIndex = EndLoc.Column; } else { LineTxt = CurLineBldr.ToString(); } int Index = LineTxt.LastIndexOf(Txt, ComparisonFlags); if(Index != -1) { ColumnIndex += Index; CheckForWholeWord(Txt, ref Result, bMatchWord, ref bFound, bIsWord, CurLineIndex, CurLineBldr, ColumnIndex, Index); } } return bFound; } /// /// Saves the document to disk. /// /// The path to a file that the document will be written to. public void SaveToFile(string Name) { using(StreamWriter Writer = new StreamWriter(File.Open(Name, FileMode.Create, FileAccess.Write, FileShare.Read))) { foreach(DocumentLine CurLine in mLines) { Writer.Write(CurLine.ToString()); } } } /// /// Determines whether a location within the document is valid. /// /// The location to validate. /// True if the supplied location is within the bounds of the document. public bool IsValidTextLocation(TextLocation Loc) { bool bResult = false; if(Loc.Line >= 0 && Loc.Line < mLines.Count && Loc.Column >= 0 && Loc.Column <= GetLineLength(Loc.Line)) { bResult = true; } return bResult; } /// /// Gets an array of line segments for the line at the specified index. /// /// The index of the line to retrieve the segments for. /// An array of line segments. public ColoredDocumentLineSegment[] GetLineSegments(int LineIndex) { return mLines[LineIndex].GetLineSegments(); } /// /// Gets an array of line segments for the line at the specified index. /// /// The index of the line to retrieve the segments for. /// The starting character at which to begin generating segments. /// The number of characters to include in segment generation. /// An array of line segments. public ColoredDocumentLineSegment[] GetLineSegments(int LineIndex, int StartIndex, int Length) { return mLines[LineIndex].GetLineSegments(StartIndex, Length); } /// /// Gets an array of colored line segments that can be used for drawing. /// /// /// If the line of text contains any instances of they will be replaced with new line segments that contain for drawing. /// /// The index of the line to retrieve the segments for. /// The starting character at which to begin generating segments. /// The number of characters to include in segment generation. /// Text to search for that requires special coloring. /// The colors to use when drawing . /// The type of string comparison that will be conducted when searching for . /// Set to True if exists within the line segments. /// An array of colored line segments. public ColoredDocumentLineSegment[] GetLineSegmentsWithFindString(int LineIndex, int StartIndex, int Length, string FindText, ColorPair InsertionColors, StringComparison ComparisonType, out bool bHasFindText) { return mLines[LineIndex].GetLineSegmentsWithFindString(StartIndex, Length, FindText, InsertionColors, ComparisonType, out bHasFindText); } /// /// Event handler for when a full line of text has been added to the document. /// /// Information about the event. protected virtual void OnLineAdded(OutputWindowDocumentLineAddedEventArgs e) { if(mOnLineAdded != null) { mOnLineAdded(this, e); } } /// /// Event handler for when the document has been modified. /// /// Information about the event. protected virtual void OnModified(EventArgs e) { if(mOnModified != null) { mOnModified(this, e); } } /// /// Creates a new document by filtering out the lines of the current document using the supplied filter function. /// /// The function that will filter the lines out of the current document. /// User supplied data that may control filtering. /// A new document containing filtered lines from the current document. public OutputWindowDocument CreateFilteredDocument(OutputWindowDocumentFilterDelegate Filter, object Data) { if(Filter == null) { throw new ArgumentNullException("Filter"); } OutputWindowDocument NewDoc = new OutputWindowDocument(); NewDoc.mLines.Capacity = mLines.Capacity; DocumentLine LastLine = null; foreach(DocumentLine CurLine in mLines) { if(!Filter(new ReadOnlyDocumentLine(CurLine), Data)) { DocumentLine NewLine = (DocumentLine)CurLine.Clone(); NewDoc.mLines.Add(NewLine); int LineLength = GetLineLength(NewLine); if(LineLength > NewDoc.mLongestLineLength) { NewDoc.mLongestLineLength = LineLength; } LastLine = NewLine; } } // If the last line is a full line we need to append an empty line because the empty line has been filtered out if(LastLine != null && LastLine.ToString().EndsWith(Environment.NewLine)) { NewDoc.mLines.Add(new DocumentLine()); } return NewDoc; } } }