first commit

This commit is contained in:
2026-01-07 11:33:05 +08:00
commit fc54ffd43b
215 changed files with 31856 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface is used to describe a span inside a text sequence
/// </summary>
public class AbstractSegment : ISegment
{
[CLSCompliant(false)]
protected int offset = -1;
[CLSCompliant(false)]
protected int length = -1;
#region ICSharpCode.TextEditor.Document.ISegment interface implementation
public virtual int Offset {
get {
return offset;
}
set {
offset = value;
}
}
public virtual int Length {
get {
return length;
}
set {
length = value;
}
}
#endregion
public override string ToString()
{
return string.Format("[AbstractSegment: Offset = {0}, Length = {1}]",
Offset,
Length);
}
}
}

View File

@@ -0,0 +1,164 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using SWF = System.Windows.Forms;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Description of Bookmark.
/// </summary>
public class Bookmark
{
IDocument document;
TextAnchor anchor;
TextLocation location;
bool isEnabled = true;
public IDocument Document {
get {
return document;
}
set {
if (document != value) {
if (anchor != null) {
location = anchor.Location;
anchor = null;
}
document = value;
CreateAnchor();
OnDocumentChanged(EventArgs.Empty);
}
}
}
void CreateAnchor()
{
if (document != null) {
LineSegment line = document.GetLineSegment(Math.Max(0, Math.Min(location.Line, document.TotalNumberOfLines-1)));
anchor = line.CreateAnchor(Math.Max(0, Math.Min(location.Column, line.Length)));
// after insertion: keep bookmarks after the initial whitespace (see DefaultFormattingStrategy.SmartReplaceLine)
anchor.MovementType = AnchorMovementType.AfterInsertion;
anchor.Deleted += AnchorDeleted;
}
}
void AnchorDeleted(object sender, EventArgs e)
{
document.BookmarkManager.RemoveMark(this);
}
/// <summary>
/// Gets the TextAnchor used for this bookmark.
/// Is null if the bookmark is not connected to a document.
/// </summary>
public TextAnchor Anchor {
get { return anchor; }
}
public TextLocation Location {
get {
if (anchor != null)
return anchor.Location;
else
return location;
}
set {
location = value;
CreateAnchor();
}
}
public event EventHandler DocumentChanged;
protected virtual void OnDocumentChanged(EventArgs e)
{
if (DocumentChanged != null) {
DocumentChanged(this, e);
}
}
public bool IsEnabled {
get {
return isEnabled;
}
set {
if (isEnabled != value) {
isEnabled = value;
if (document != null) {
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, LineNumber));
document.CommitUpdate();
}
OnIsEnabledChanged(EventArgs.Empty);
}
}
}
public event EventHandler IsEnabledChanged;
protected virtual void OnIsEnabledChanged(EventArgs e)
{
if (IsEnabledChanged != null) {
IsEnabledChanged(this, e);
}
}
public int LineNumber {
get {
if (anchor != null)
return anchor.LineNumber;
else
return location.Line;
}
}
public int ColumnNumber {
get {
if (anchor != null)
return anchor.ColumnNumber;
else
return location.Column;
}
}
/// <summary>
/// Gets if the bookmark can be toggled off using the 'set/unset bookmark' command.
/// </summary>
public virtual bool CanToggle {
get {
return true;
}
}
public Bookmark(IDocument document, TextLocation location) : this(document, location, true)
{
}
public Bookmark(IDocument document, TextLocation location, bool isEnabled)
{
this.document = document;
this.isEnabled = isEnabled;
this.Location = location;
}
public virtual bool Click(SWF.Control parent, SWF.MouseEventArgs e)
{
if (e.Button == SWF.MouseButtons.Left && CanToggle) {
document.BookmarkManager.RemoveMark(this);
return true;
}
return false;
}
public virtual void Draw(IconBarMargin margin, Graphics g, Point p)
{
margin.DrawBookmark(g, p.Y, isEnabled);
}
}
}

View File

@@ -0,0 +1,32 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public delegate void BookmarkEventHandler(object sender, BookmarkEventArgs e);
/// <summary>
/// Description of BookmarkEventHandler.
/// </summary>
public class BookmarkEventArgs : EventArgs
{
Bookmark bookmark;
public Bookmark Bookmark {
get {
return bookmark;
}
}
public BookmarkEventArgs(Bookmark bookmark)
{
this.bookmark = bookmark;
}
}
}

View File

@@ -0,0 +1,246 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Document
{
public interface IBookmarkFactory
{
Bookmark CreateBookmark(IDocument document, TextLocation location);
}
/// <summary>
/// This class handles the bookmarks for a buffer
/// </summary>
public class BookmarkManager
{
IDocument document;
#if DEBUG
IList<Bookmark> bookmark = new CheckedList<Bookmark>();
#else
List<Bookmark> bookmark = new List<Bookmark>();
#endif
/// <value>
/// Contains all bookmarks
/// </value>
public ReadOnlyCollection<Bookmark> Marks {
get {
return new ReadOnlyCollection<Bookmark>(bookmark);
}
}
public IDocument Document {
get {
return document;
}
}
/// <summary>
/// Creates a new instance of <see cref="BookmarkManager"/>
/// </summary>
internal BookmarkManager(IDocument document, LineManager lineTracker)
{
this.document = document;
}
/// <summary>
/// Gets/Sets the bookmark factory used to create bookmarks for "ToggleMarkAt".
/// </summary>
public IBookmarkFactory Factory { get; set;}
/// <summary>
/// Sets the mark at the line <code>location.Line</code> if it is not set, if the
/// line is already marked the mark is cleared.
/// </summary>
public void ToggleMarkAt(TextLocation location)
{
Bookmark newMark;
if (Factory != null) {
newMark = Factory.CreateBookmark(document, location);
} else {
newMark = new Bookmark(document, location);
}
Type newMarkType = newMark.GetType();
for (int i = 0; i < bookmark.Count; ++i) {
Bookmark mark = bookmark[i];
if (mark.LineNumber == location.Line && mark.CanToggle && mark.GetType() == newMarkType) {
bookmark.RemoveAt(i);
OnRemoved(new BookmarkEventArgs(mark));
return;
}
}
bookmark.Add(newMark);
OnAdded(new BookmarkEventArgs(newMark));
}
public void AddMark(Bookmark mark)
{
bookmark.Add(mark);
OnAdded(new BookmarkEventArgs(mark));
}
public void RemoveMark(Bookmark mark)
{
bookmark.Remove(mark);
OnRemoved(new BookmarkEventArgs(mark));
}
public void RemoveMarks(Predicate<Bookmark> predicate)
{
for (int i = 0; i < bookmark.Count; ++i) {
Bookmark bm = bookmark[i];
if (predicate(bm)) {
bookmark.RemoveAt(i--);
OnRemoved(new BookmarkEventArgs(bm));
}
}
}
/// <returns>
/// true, if a mark at mark exists, otherwise false
/// </returns>
public bool IsMarked(int lineNr)
{
for (int i = 0; i < bookmark.Count; ++i) {
if (bookmark[i].LineNumber == lineNr) {
return true;
}
}
return false;
}
/// <remarks>
/// Clears all bookmark
/// </remarks>
public void Clear()
{
foreach (Bookmark mark in bookmark) {
OnRemoved(new BookmarkEventArgs(mark));
}
bookmark.Clear();
}
/// <value>
/// The lowest mark, if no marks exists it returns -1
/// </value>
public Bookmark GetFirstMark(Predicate<Bookmark> predicate)
{
if (bookmark.Count < 1) {
return null;
}
Bookmark first = null;
for (int i = 0; i < bookmark.Count; ++i) {
if (predicate(bookmark[i]) && bookmark[i].IsEnabled && (first == null || bookmark[i].LineNumber < first.LineNumber)) {
first = bookmark[i];
}
}
return first;
}
/// <value>
/// The highest mark, if no marks exists it returns -1
/// </value>
public Bookmark GetLastMark(Predicate<Bookmark> predicate)
{
if (bookmark.Count < 1) {
return null;
}
Bookmark last = null;
for (int i = 0; i < bookmark.Count; ++i) {
if (predicate(bookmark[i]) && bookmark[i].IsEnabled && (last == null || bookmark[i].LineNumber > last.LineNumber)) {
last = bookmark[i];
}
}
return last;
}
bool AcceptAnyMarkPredicate(Bookmark mark)
{
return true;
}
public Bookmark GetNextMark(int curLineNr)
{
return GetNextMark(curLineNr, AcceptAnyMarkPredicate);
}
/// <remarks>
/// returns first mark higher than <code>lineNr</code>
/// </remarks>
/// <returns>
/// returns the next mark > cur, if it not exists it returns FirstMark()
/// </returns>
public Bookmark GetNextMark(int curLineNr, Predicate<Bookmark> predicate)
{
if (bookmark.Count == 0) {
return null;
}
Bookmark next = GetFirstMark(predicate);
foreach (Bookmark mark in bookmark) {
if (predicate(mark) && mark.IsEnabled && mark.LineNumber > curLineNr) {
if (mark.LineNumber < next.LineNumber || next.LineNumber <= curLineNr) {
next = mark;
}
}
}
return next;
}
public Bookmark GetPrevMark(int curLineNr)
{
return GetPrevMark(curLineNr, AcceptAnyMarkPredicate);
}
/// <remarks>
/// returns first mark lower than <code>lineNr</code>
/// </remarks>
/// <returns>
/// returns the next mark lower than cur, if it not exists it returns LastMark()
/// </returns>
public Bookmark GetPrevMark(int curLineNr, Predicate<Bookmark> predicate)
{
if (bookmark.Count == 0) {
return null;
}
Bookmark prev = GetLastMark(predicate);
foreach (Bookmark mark in bookmark) {
if (predicate(mark) && mark.IsEnabled && mark.LineNumber < curLineNr) {
if (mark.LineNumber > prev.LineNumber || prev.LineNumber >= curLineNr) {
prev = mark;
}
}
}
return prev;
}
protected virtual void OnRemoved(BookmarkEventArgs e)
{
if (Removed != null) {
Removed(this, e);
}
}
protected virtual void OnAdded(BookmarkEventArgs e)
{
if (Added != null) {
Added(this, e);
}
}
public event BookmarkEventHandler Removed;
public event BookmarkEventHandler Added;
}
}

View File

@@ -0,0 +1,101 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This class is used for storing the state of a bookmark manager
/// </summary>
public class BookmarkManagerMemento
{
List<int> bookmarks = new List<int>();
/// <value>
/// Contains all bookmarks as int values
/// </value>
public List<int> Bookmarks {
get {
return bookmarks;
}
set {
bookmarks = value;
}
}
/// <summary>
/// Validates all bookmarks if they're in range of the document.
/// (removing all bookmarks &lt; 0 and bookmarks &gt; max. line number
/// </summary>
public void CheckMemento(IDocument document)
{
for (int i = 0; i < bookmarks.Count; ++i) {
int mark = (int)bookmarks[i];
if (mark < 0 || mark >= document.TotalNumberOfLines) {
bookmarks.RemoveAt(i);
--i;
}
}
}
/// <summary>
/// Creates a new instance of <see cref="BookmarkManagerMemento"/>
/// </summary>
public BookmarkManagerMemento()
{
}
/// <summary>
/// Creates a new instance of <see cref="BookmarkManagerMemento"/>
/// </summary>
public BookmarkManagerMemento(XmlElement element)
{
foreach (XmlElement el in element.ChildNodes) {
bookmarks.Add(int.Parse(el.Attributes["line"].InnerText));
}
}
/// <summary>
/// Creates a new instance of <see cref="BookmarkManagerMemento"/>
/// </summary>
public BookmarkManagerMemento(List<int> bookmarks)
{
this.bookmarks = bookmarks;
}
/// <summary>
/// Converts a xml element to a <see cref="BookmarkManagerMemento"/> object
/// </summary>
public object FromXmlElement(XmlElement element)
{
return new BookmarkManagerMemento(element);
}
/// <summary>
/// Converts this <see cref="BookmarkManagerMemento"/> to a xml element
/// </summary>
public XmlElement ToXmlElement(XmlDocument doc)
{
XmlElement bookmarknode = doc.CreateElement("Bookmarks");
foreach (int line in bookmarks) {
XmlElement markNode = doc.CreateElement("Mark");
XmlAttribute lineAttr = doc.CreateAttribute("line");
lineAttr.InnerText = line.ToString();
markNode.Attributes.Append(lineAttr);
bookmarknode.AppendChild(markNode);
}
return bookmarknode;
}
}
}

View File

@@ -0,0 +1,457 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using ICSharpCode.TextEditor.Undo;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Describes the caret marker
/// </summary>
public enum LineViewerStyle {
/// <summary>
/// No line viewer will be displayed
/// </summary>
None,
/// <summary>
/// The row in which the caret is will be marked
/// </summary>
FullRow
}
/// <summary>
/// Describes the indent style
/// </summary>
public enum IndentStyle {
/// <summary>
/// No indentation occurs
/// </summary>
None,
/// <summary>
/// The indentation from the line above will be
/// taken to indent the curent line
/// </summary>
Auto,
/// <summary>
/// Inteligent, context sensitive indentation will occur
/// </summary>
Smart
}
/// <summary>
/// Describes the bracket highlighting style
/// </summary>
public enum BracketHighlightingStyle {
/// <summary>
/// Brackets won't be highlighted
/// </summary>
None,
/// <summary>
/// Brackets will be highlighted if the caret is on the bracket
/// </summary>
OnBracket,
/// <summary>
/// Brackets will be highlighted if the caret is after the bracket
/// </summary>
AfterBracket
}
/// <summary>
/// Describes the selection mode of the text area
/// </summary>
public enum DocumentSelectionMode {
/// <summary>
/// The 'normal' selection mode.
/// </summary>
Normal,
/// <summary>
/// Selections will be added to the current selection or new
/// ones will be created (multi-select mode)
/// </summary>
Additive
}
/// <summary>
/// The default <see cref="IDocument"/> implementation.
/// </summary>
internal sealed class DefaultDocument : IDocument
{
bool readOnly = false;
LineManager lineTrackingStrategy;
BookmarkManager bookmarkManager;
ITextBufferStrategy textBufferStrategy;
IFormattingStrategy formattingStrategy;
FoldingManager foldingManager;
UndoStack undoStack = new UndoStack();
ITextEditorProperties textEditorProperties = new DefaultTextEditorProperties();
MarkerStrategy markerStrategy;
public LineManager LineManager {
get { return lineTrackingStrategy; }
set { lineTrackingStrategy = value; }
}
public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged {
add { lineTrackingStrategy.LineLengthChanged += value; }
remove { lineTrackingStrategy.LineLengthChanged -= value; }
}
public event EventHandler<LineCountChangeEventArgs> LineCountChanged {
add { lineTrackingStrategy.LineCountChanged += value; }
remove { lineTrackingStrategy.LineCountChanged -= value; }
}
public event EventHandler<LineEventArgs> LineDeleted {
add { lineTrackingStrategy.LineDeleted += value; }
remove { lineTrackingStrategy.LineDeleted -= value; }
}
public MarkerStrategy MarkerStrategy {
get { return markerStrategy; }
set { markerStrategy = value; }
}
public ITextEditorProperties TextEditorProperties {
get {
return textEditorProperties;
}
set {
textEditorProperties = value;
}
}
public UndoStack UndoStack {
get {
return undoStack;
}
}
public IList<LineSegment> LineSegmentCollection {
get {
return lineTrackingStrategy.LineSegmentCollection;
}
}
public bool ReadOnly {
get {
return readOnly;
}
set {
readOnly = value;
}
}
public ITextBufferStrategy TextBufferStrategy {
get {
return textBufferStrategy;
}
set {
textBufferStrategy = value;
}
}
public IFormattingStrategy FormattingStrategy {
get {
return formattingStrategy;
}
set {
formattingStrategy = value;
}
}
public FoldingManager FoldingManager {
get {
return foldingManager;
}
set {
foldingManager = value;
}
}
public IHighlightingStrategy HighlightingStrategy {
get {
return lineTrackingStrategy.HighlightingStrategy;
}
set {
lineTrackingStrategy.HighlightingStrategy = value;
}
}
public int TextLength {
get {
return textBufferStrategy.Length;
}
}
public BookmarkManager BookmarkManager {
get {
return bookmarkManager;
}
set {
bookmarkManager = value;
}
}
public string TextContent {
get {
return GetText(0, textBufferStrategy.Length);
}
set {
Debug.Assert(textBufferStrategy != null);
Debug.Assert(lineTrackingStrategy != null);
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, 0, 0, value));
textBufferStrategy.SetContent(value);
lineTrackingStrategy.SetContent(value);
undoStack.ClearAll();
OnDocumentChanged(new DocumentEventArgs(this, 0, 0, value));
OnTextContentChanged(EventArgs.Empty);
}
}
public void Insert(int offset, string text)
{
if (readOnly) {
return;
}
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, -1, text));
textBufferStrategy.Insert(offset, text);
lineTrackingStrategy.Insert(offset, text);
undoStack.Push(new UndoableInsert(this, offset, text));
OnDocumentChanged(new DocumentEventArgs(this, offset, -1, text));
}
public void Remove(int offset, int length)
{
if (readOnly) {
return;
}
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length));
undoStack.Push(new UndoableDelete(this, offset, GetText(offset, length)));
textBufferStrategy.Remove(offset, length);
lineTrackingStrategy.Remove(offset, length);
OnDocumentChanged(new DocumentEventArgs(this, offset, length));
}
public void Replace(int offset, int length, string text)
{
if (readOnly) {
return;
}
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length, text));
undoStack.Push(new UndoableReplace(this, offset, GetText(offset, length), text));
textBufferStrategy.Replace(offset, length, text);
lineTrackingStrategy.Replace(offset, length, text);
OnDocumentChanged(new DocumentEventArgs(this, offset, length, text));
}
public char GetCharAt(int offset)
{
return textBufferStrategy.GetCharAt(offset);
}
public string GetText(int offset, int length)
{
#if DEBUG
if (length < 0) throw new ArgumentOutOfRangeException("length", length, "length < 0");
#endif
return textBufferStrategy.GetText(offset, length);
}
public string GetText(ISegment segment)
{
return GetText(segment.Offset, segment.Length);
}
public int TotalNumberOfLines {
get {
return lineTrackingStrategy.TotalNumberOfLines;
}
}
public int GetLineNumberForOffset(int offset)
{
return lineTrackingStrategy.GetLineNumberForOffset(offset);
}
public LineSegment GetLineSegmentForOffset(int offset)
{
return lineTrackingStrategy.GetLineSegmentForOffset(offset);
}
public LineSegment GetLineSegment(int line)
{
return lineTrackingStrategy.GetLineSegment(line);
}
public int GetFirstLogicalLine(int lineNumber)
{
return lineTrackingStrategy.GetFirstLogicalLine(lineNumber);
}
public int GetLastLogicalLine(int lineNumber)
{
return lineTrackingStrategy.GetLastLogicalLine(lineNumber);
}
public int GetVisibleLine(int lineNumber)
{
return lineTrackingStrategy.GetVisibleLine(lineNumber);
}
// public int GetVisibleColumn(int logicalLine, int logicalColumn)
// {
// return lineTrackingStrategy.GetVisibleColumn(logicalLine, logicalColumn);
// }
//
public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
{
return lineTrackingStrategy.GetNextVisibleLineAbove(lineNumber, lineCount);
}
public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
{
return lineTrackingStrategy.GetNextVisibleLineBelow(lineNumber, lineCount);
}
public TextLocation OffsetToPosition(int offset)
{
int lineNr = GetLineNumberForOffset(offset);
LineSegment line = GetLineSegment(lineNr);
return new TextLocation(offset - line.Offset, lineNr);
}
public int PositionToOffset(TextLocation p)
{
if (p.Y >= this.TotalNumberOfLines) {
return 0;
}
LineSegment line = GetLineSegment(p.Y);
return Math.Min(this.TextLength, line.Offset + Math.Min(line.Length, p.X));
}
public void UpdateSegmentListOnDocumentChange<T>(List<T> list, DocumentEventArgs e) where T : ISegment
{
int removedCharacters = e.Length > 0 ? e.Length : 0;
int insertedCharacters = e.Text != null ? e.Text.Length : 0;
for (int i = 0; i < list.Count; ++i) {
ISegment s = list[i];
int segmentStart = s.Offset;
int segmentEnd = s.Offset + s.Length;
if (e.Offset <= segmentStart) {
segmentStart -= removedCharacters;
if (segmentStart < e.Offset)
segmentStart = e.Offset;
}
if (e.Offset < segmentEnd) {
segmentEnd -= removedCharacters;
if (segmentEnd < e.Offset)
segmentEnd = e.Offset;
}
Debug.Assert(segmentStart <= segmentEnd);
if (segmentStart == segmentEnd) {
list.RemoveAt(i);
--i;
continue;
}
if (e.Offset <= segmentStart)
segmentStart += insertedCharacters;
if (e.Offset < segmentEnd)
segmentEnd += insertedCharacters;
Debug.Assert(segmentStart < segmentEnd);
s.Offset = segmentStart;
s.Length = segmentEnd - segmentStart;
}
}
void OnDocumentAboutToBeChanged(DocumentEventArgs e)
{
if (DocumentAboutToBeChanged != null) {
DocumentAboutToBeChanged(this, e);
}
}
void OnDocumentChanged(DocumentEventArgs e)
{
if (DocumentChanged != null) {
DocumentChanged(this, e);
}
}
public event DocumentEventHandler DocumentAboutToBeChanged;
public event DocumentEventHandler DocumentChanged;
// UPDATE STUFF
List<TextAreaUpdate> updateQueue = new List<TextAreaUpdate>();
public List<TextAreaUpdate> UpdateQueue {
get {
return updateQueue;
}
}
public void RequestUpdate(TextAreaUpdate update)
{
if (updateQueue.Count == 1 && updateQueue[0].TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) {
// if we're going to update the whole text area, we don't need to store detail updates
return;
}
if (update.TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) {
// if we're going to update the whole text area, we don't need to store detail updates
updateQueue.Clear();
}
updateQueue.Add(update);
}
public void CommitUpdate()
{
if (UpdateCommited != null) {
UpdateCommited(this, EventArgs.Empty);
}
}
void OnTextContentChanged(EventArgs e)
{
if (TextContentChanged != null) {
TextContentChanged(this, e);
}
}
public event EventHandler UpdateCommited;
public event EventHandler TextContentChanged;
[Conditional("DEBUG")]
internal static void ValidatePosition(IDocument document, TextLocation position)
{
document.GetLineSegment(position.Line);
}
}
}

View File

@@ -0,0 +1,321 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public enum BracketMatchingStyle {
Before,
After
}
public class DefaultTextEditorProperties : ITextEditorProperties
{
int tabIndent = 4;
int indentationSize = 4;
IndentStyle indentStyle = IndentStyle.Smart;
DocumentSelectionMode documentSelectionMode = DocumentSelectionMode.Normal;
Encoding encoding = System.Text.Encoding.UTF8;
BracketMatchingStyle bracketMatchingStyle = BracketMatchingStyle.After;
FontContainer fontContainer;
static Font DefaultFont;
public DefaultTextEditorProperties()
{
if (DefaultFont == null) {
DefaultFont = new Font("Courier New", 10);
}
this.fontContainer = new FontContainer(DefaultFont);
}
bool allowCaretBeyondEOL = false;
bool caretLine = false;
bool showMatchingBracket = true;
bool showLineNumbers = true;
bool showSpaces = false;
bool showTabs = false;
bool showEOLMarker = false;
bool showInvalidLines = false;
bool isIconBarVisible = false;
bool enableFolding = true;
bool showHorizontalRuler = false;
bool showVerticalRuler = true;
bool convertTabsToSpaces = false;
System.Drawing.Text.TextRenderingHint textRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault;
bool mouseWheelScrollDown = true;
bool mouseWheelTextZoom = true;
bool hideMouseCursor = false;
bool cutCopyWholeLine = true;
int verticalRulerRow = 80;
LineViewerStyle lineViewerStyle = LineViewerStyle.None;
string lineTerminator = "\r\n";
bool autoInsertCurlyBracket = true;
bool supportReadOnlySegments = false;
public int TabIndent {
get {
return tabIndent;
}
set {
tabIndent = value;
}
}
public int IndentationSize {
get { return indentationSize; }
set { indentationSize = value; }
}
public IndentStyle IndentStyle {
get {
return indentStyle;
}
set {
indentStyle = value;
}
}
public bool CaretLine {
get
{
return caretLine;
}
set
{
caretLine = value;
}
}
public DocumentSelectionMode DocumentSelectionMode {
get {
return documentSelectionMode;
}
set {
documentSelectionMode = value;
}
}
public bool AllowCaretBeyondEOL {
get {
return allowCaretBeyondEOL;
}
set {
allowCaretBeyondEOL = value;
}
}
public bool ShowMatchingBracket {
get {
return showMatchingBracket;
}
set {
showMatchingBracket = value;
}
}
public bool ShowLineNumbers {
get {
return showLineNumbers;
}
set {
showLineNumbers = value;
}
}
public bool ShowSpaces {
get {
return showSpaces;
}
set {
showSpaces = value;
}
}
public bool ShowTabs {
get {
return showTabs;
}
set {
showTabs = value;
}
}
public bool ShowEOLMarker {
get {
return showEOLMarker;
}
set {
showEOLMarker = value;
}
}
public bool ShowInvalidLines {
get {
return showInvalidLines;
}
set {
showInvalidLines = value;
}
}
public bool IsIconBarVisible {
get {
return isIconBarVisible;
}
set {
isIconBarVisible = value;
}
}
public bool EnableFolding {
get {
return enableFolding;
}
set {
enableFolding = value;
}
}
public bool ShowHorizontalRuler {
get {
return showHorizontalRuler;
}
set {
showHorizontalRuler = value;
}
}
public bool ShowVerticalRuler {
get {
return showVerticalRuler;
}
set {
showVerticalRuler = value;
}
}
public bool ConvertTabsToSpaces {
get {
return convertTabsToSpaces;
}
set {
convertTabsToSpaces = value;
}
}
public System.Drawing.Text.TextRenderingHint TextRenderingHint {
get { return textRenderingHint; }
set { textRenderingHint = value; }
}
public bool MouseWheelScrollDown {
get {
return mouseWheelScrollDown;
}
set {
mouseWheelScrollDown = value;
}
}
public bool MouseWheelTextZoom {
get {
return mouseWheelTextZoom;
}
set {
mouseWheelTextZoom = value;
}
}
public bool HideMouseCursor {
get {
return hideMouseCursor;
}
set {
hideMouseCursor = value;
}
}
public bool CutCopyWholeLine {
get {
return cutCopyWholeLine;
}
set {
cutCopyWholeLine = value;
}
}
public Encoding Encoding {
get {
return encoding;
}
set {
encoding = value;
}
}
public int VerticalRulerRow {
get {
return verticalRulerRow;
}
set {
verticalRulerRow = value;
}
}
public LineViewerStyle LineViewerStyle {
get {
return lineViewerStyle;
}
set {
lineViewerStyle = value;
}
}
public string LineTerminator {
get {
return lineTerminator;
}
set {
lineTerminator = value;
}
}
public bool AutoInsertCurlyBracket {
get {
return autoInsertCurlyBracket;
}
set {
autoInsertCurlyBracket = value;
}
}
public Font Font {
get {
return fontContainer.DefaultFont;
}
set {
fontContainer.DefaultFont = value;
}
}
public FontContainer FontContainer {
get {
return fontContainer;
}
}
public BracketMatchingStyle BracketMatchingStyle {
get {
return bracketMatchingStyle;
}
set {
bracketMatchingStyle = value;
}
}
public bool SupportReadOnlySegments {
get {
return supportReadOnlySegments;
}
set {
supportReadOnlySegments = value;
}
}
}
}

View File

@@ -0,0 +1,103 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This delegate is used for document events.
/// </summary>
public delegate void DocumentEventHandler(object sender, DocumentEventArgs e);
/// <summary>
/// This class contains more information on a document event
/// </summary>
public class DocumentEventArgs : EventArgs
{
IDocument document;
int offset;
int length;
string text;
/// <returns>
/// always a valid Document which is related to the Event.
/// </returns>
public IDocument Document {
get {
return document;
}
}
/// <returns>
/// -1 if no offset was specified for this event
/// </returns>
public int Offset {
get {
return offset;
}
}
/// <returns>
/// null if no text was specified for this event
/// </returns>
public string Text {
get {
return text;
}
}
/// <returns>
/// -1 if no length was specified for this event
/// </returns>
public int Length {
get {
return length;
}
}
/// <summary>
/// Creates a new instance off <see cref="DocumentEventArgs"/>
/// </summary>
public DocumentEventArgs(IDocument document) : this(document, -1, -1, null)
{
}
/// <summary>
/// Creates a new instance off <see cref="DocumentEventArgs"/>
/// </summary>
public DocumentEventArgs(IDocument document, int offset) : this(document, offset, -1, null)
{
}
/// <summary>
/// Creates a new instance off <see cref="DocumentEventArgs"/>
/// </summary>
public DocumentEventArgs(IDocument document, int offset, int length) : this(document, offset, length, null)
{
}
/// <summary>
/// Creates a new instance off <see cref="DocumentEventArgs"/>
/// </summary>
public DocumentEventArgs(IDocument document, int offset, int length, string text)
{
this.document = document;
this.offset = offset;
this.length = length;
this.text = text;
}
public override string ToString()
{
return string.Format("[DocumentEventArgs: Document = {0}, Offset = {1}, Text = {2}, Length = {3}]",
Document,
Offset,
Text,
Length);
}
}
}

View File

@@ -0,0 +1,57 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface represents a container which holds a text sequence and
/// all necessary information about it. It is used as the base for a text editor.
/// </summary>
public class DocumentFactory
{
/// <remarks>
/// Creates a new <see cref="IDocument"/> object. Only create
/// <see cref="IDocument"/> with this method.
/// </remarks>
public IDocument CreateDocument()
{
DefaultDocument doc = new DefaultDocument();
doc.TextBufferStrategy = new GapTextBufferStrategy();
doc.FormattingStrategy = new DefaultFormattingStrategy();
doc.LineManager = new LineManager(doc, null);
doc.FoldingManager = new FoldingManager(doc, doc.LineManager);
doc.FoldingManager.FoldingStrategy = null; //new ParserFoldingStrategy();
doc.MarkerStrategy = new MarkerStrategy(doc);
doc.BookmarkManager = new BookmarkManager(doc, doc.LineManager);
return doc;
}
/// <summary>
/// Creates a new document and loads the given file
/// </summary>
public IDocument CreateFromTextBuffer(ITextBufferStrategy textBuffer)
{
DefaultDocument doc = (DefaultDocument)CreateDocument();
doc.TextContent = textBuffer.GetText(0, textBuffer.Length);
doc.TextBufferStrategy = textBuffer;
return doc;
}
/// <summary>
/// Creates a new document and loads the given file
/// </summary>
public IDocument CreateFromFile(string fileName)
{
IDocument document = CreateDocument();
document.TextContent = Util.FileReader.ReadFileContent(fileName, Encoding.Default);
return document;
}
}
}

View File

@@ -0,0 +1,107 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is part of CodingEditor.
// Note: This project is derived from Peter Project
// (hosted on sourceforge and codeplex)
//
// Copyright (c) 2008-2009, CE Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System.Collections.Generic;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
public class CSharpFoldingStrategy : IFoldingStrategy
{
#region Methods
/// <summary>
/// Generates the foldings for our document.
/// </summary>
/// <param name="document">The current document.</param>
/// <param name="fileName">The filename of the document.</param>
/// <param name="parseInformation">Extra parse information, not used in this sample.</param>
/// <returns>A list of FoldMarkers.</returns>
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
var list = new List<FoldMarker>();
var startLines = new Stack<int>();
// Create foldmarkers for the whole document, enumerate through every line.
for (int i = 0; i < document.TotalNumberOfLines; i++)
{
var seg = document.GetLineSegment(i);
int offs, end = document.TextLength;
char c;
for (offs = seg.Offset; offs < end && ((c = document.GetCharAt(offs)) == ' ' || c == '\t'); offs++)
{
}
if (offs == end)
break;
int spaceCount = offs - seg.Offset;
// now offs points to the first non-whitespace char on the line
if (document.GetCharAt(offs) == '#')
{
string text = document.GetText(offs, seg.Length - spaceCount);
if (text.StartsWith("#region"))
startLines.Push(i);
if (text.StartsWith("#endregion") && startLines.Count > 0)
{
// Add a new FoldMarker to the list.
int start = startLines.Pop();
list.Add(new FoldMarker(document, start,
document.GetLineSegment(start).Length,
i, spaceCount + "#endregion".Length, FoldType.Region, "{...}"));
}
}
// { }
if (document.GetCharAt(offs) == '{')
{
int offsetOfClosingBracket = document.FormattingStrategy.SearchBracketForward(document, offs + 1, '{', '}');
if (offsetOfClosingBracket > 0)
{
int length = offsetOfClosingBracket - offs + 1;
list.Add(new FoldMarker(document, offs, length, "{...}", false));
}
}
if (document.GetCharAt(offs) == '/')
{
string text = document.GetText(offs, seg.Length - spaceCount);
if (text.StartsWith("/// <summary>"))
startLines.Push(i);
if ((text.StartsWith("/// <param") || text.StartsWith("/// <returns>") || text.StartsWith("/// </summary>"))
&& startLines.Count > 0)
{
// Add a new FoldMarker to the list.
int start = startLines.Pop();
list.Add(new FoldMarker(document, start,
document.GetLineSegment(start).Length,
i, spaceCount + "/// </summary>".Length, FoldType.TypeBody, "/// <summary>..."));
}
}
}
return list;
}
#endregion Methods
}
}

View File

@@ -0,0 +1,174 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public enum FoldType {
Unspecified,
MemberBody,
Region,
TypeBody
}
public class FoldMarker : AbstractSegment, IComparable
{
bool isFolded = false;
string foldText = "...";
FoldType foldType = FoldType.Unspecified;
IDocument document = null;
int startLine = -1, startColumn, endLine = -1, endColumn;
static void GetPointForOffset(IDocument document, int offset, out int line, out int column)
{
if (offset > document.TextLength) {
line = document.TotalNumberOfLines + 1;
column = 1;
} else if (offset < 0) {
line = -1;
column = -1;
} else {
line = document.GetLineNumberForOffset(offset);
column = offset - document.GetLineSegment(line).Offset;
}
}
public FoldType FoldType {
get { return foldType; }
set { foldType = value; }
}
public int StartLine {
get {
if (startLine < 0) {
GetPointForOffset(document, offset, out startLine, out startColumn);
}
return startLine;
}
}
public int StartColumn {
get {
if (startLine < 0) {
GetPointForOffset(document, offset, out startLine, out startColumn);
}
return startColumn;
}
}
public int EndLine {
get {
if (endLine < 0) {
GetPointForOffset(document, offset + length, out endLine, out endColumn);
}
return endLine;
}
}
public int EndColumn {
get {
if (endLine < 0) {
GetPointForOffset(document, offset + length, out endLine, out endColumn);
}
return endColumn;
}
}
public override int Offset {
get { return base.Offset; }
set {
base.Offset = value;
startLine = -1; endLine = -1;
}
}
public override int Length {
get { return base.Length; }
set {
base.Length = value;
endLine = -1;
}
}
public bool IsFolded {
get {
return isFolded;
}
set {
isFolded = value;
}
}
public string FoldText {
get {
return foldText;
}
}
public string InnerText {
get {
return document.GetText(offset, length);
}
}
public FoldMarker(IDocument document, int offset, int length, string foldText, bool isFolded)
{
this.document = document;
this.offset = offset;
this.length = length;
this.foldText = foldText;
this.isFolded = isFolded;
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn) : this(document, startLine, startColumn, endLine, endColumn, FoldType.Unspecified)
{
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType) : this(document, startLine, startColumn, endLine, endColumn, foldType, "...")
{
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText) : this(document, startLine, startColumn, endLine, endColumn, foldType, foldText, false)
{
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText, bool isFolded)
{
this.document = document;
startLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(startLine, 0));
ISegment startLineSegment = document.GetLineSegment(startLine);
endLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(endLine, 0));
ISegment endLineSegment = document.GetLineSegment(endLine);
// Prevent the region from completely disappearing
if (string.IsNullOrEmpty(foldText)) {
foldText = "...";
}
this.FoldType = foldType;
this.foldText = foldText;
this.offset = startLineSegment.Offset + Math.Min(startColumn, startLineSegment.Length);
this.length = (endLineSegment.Offset + Math.Min(endColumn, endLineSegment.Length)) - this.offset;
this.isFolded = isFolded;
}
public int CompareTo(object o)
{
if (!(o is FoldMarker)) {
throw new ArgumentException();
}
FoldMarker f = (FoldMarker)o;
if (offset != f.offset) {
return offset.CompareTo(f.offset);
}
return length.CompareTo(f.length);
}
}
}

View File

@@ -0,0 +1,362 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public class FoldingManager
{
List<FoldMarker> foldMarker = new List<FoldMarker>();
List<FoldMarker> foldMarkerByEnd = new List<FoldMarker>();
IDocument document;
public IList<FoldMarker> FoldMarker => foldMarker.AsReadOnly();
public IFoldingStrategy? FoldingStrategy { get; set; }
internal FoldingManager(IDocument document, LineManager lineTracker)
{
this.document = document;
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
// lineTracker.LineCountChanged += new LineManagerEventHandler(LineManagerLineCountChanged);
// lineTracker.LineLengthChanged += new LineLengthEventHandler(LineManagerLineLengthChanged);
// foldMarker.Add(new FoldMarker(0, 5, 3, 5));
//
// foldMarker.Add(new FoldMarker(5, 5, 10, 3));
// foldMarker.Add(new FoldMarker(6, 0, 8, 2));
//
// FoldMarker fm1 = new FoldMarker(10, 4, 10, 7);
// FoldMarker fm2 = new FoldMarker(10, 10, 10, 14);
//
// fm1.IsFolded = true;
// fm2.IsFolded = true;
//
// foldMarker.Add(fm1);
// foldMarker.Add(fm2);
// foldMarker.Sort();
}
void DocumentChanged(object sender, DocumentEventArgs e)
{
int oldCount = foldMarker.Count;
document.UpdateSegmentListOnDocumentChange(foldMarker, e);
if (oldCount != foldMarker.Count)
{
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
}
}
public List<FoldMarker> GetFoldingsFromPosition(int line, int column)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
for (int i = 0; i < foldMarker.Count; ++i)
{
FoldMarker fm = foldMarker[i];
if ((fm.StartLine == line && column > fm.StartColumn && !(fm.EndLine == line && column >= fm.EndColumn)) ||
(fm.EndLine == line && column < fm.EndColumn && !(fm.StartLine == line && column <= fm.StartColumn)) ||
(line > fm.StartLine && line < fm.EndLine))
{
foldings.Add(fm);
}
}
}
return foldings;
}
class StartComparer : IComparer<FoldMarker>
{
public readonly static StartComparer Instance = new StartComparer();
public int Compare(FoldMarker x, FoldMarker y)
{
if (x.StartLine < y.StartLine)
return -1;
else if (x.StartLine == y.StartLine)
return x.StartColumn.CompareTo(y.StartColumn);
else
return 1;
}
}
class EndComparer : IComparer<FoldMarker>
{
public readonly static EndComparer Instance = new EndComparer();
public int Compare(FoldMarker x, FoldMarker y)
{
if (x.EndLine < y.EndLine)
return -1;
else if (x.EndLine == y.EndLine)
return x.EndColumn.CompareTo(y.EndColumn);
else
return 1;
}
}
List<FoldMarker> GetFoldingsByStartAfterColumn(int lineNumber, int column, bool forceFolded)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
int index = foldMarker.BinarySearch(
new FoldMarker(document, lineNumber, column, lineNumber, column),
StartComparer.Instance);
if (index < 0) index = ~index;
for (; index < foldMarker.Count; index++)
{
FoldMarker fm = foldMarker[index];
if (fm.StartLine > lineNumber)
break;
if (fm.StartColumn <= column)
continue;
if (!forceFolded || fm.IsFolded)
foldings.Add(fm);
}
}
return foldings;
}
public List<FoldMarker> GetFoldingsWithStart(int lineNumber)
{
return GetFoldingsByStartAfterColumn(lineNumber, -1, false);
}
public List<FoldMarker> GetFoldedFoldingsWithStart(int lineNumber)
{
return GetFoldingsByStartAfterColumn(lineNumber, -1, true);
}
public List<FoldMarker> GetFoldedFoldingsWithStartAfterColumn(int lineNumber, int column)
{
return GetFoldingsByStartAfterColumn(lineNumber, column, true);
}
List<FoldMarker> GetFoldingsByEndAfterColumn(int lineNumber, int column, bool forceFolded)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
int index = foldMarkerByEnd.BinarySearch(
new FoldMarker(document, lineNumber, column, lineNumber, column),
EndComparer.Instance);
if (index < 0) index = ~index;
for (; index < foldMarkerByEnd.Count; index++)
{
FoldMarker fm = foldMarkerByEnd[index];
if (fm.EndLine > lineNumber)
break;
if (fm.EndColumn <= column)
continue;
if (!forceFolded || fm.IsFolded)
foldings.Add(fm);
}
}
return foldings;
}
public List<FoldMarker> GetFoldingsWithEnd(int lineNumber)
{
return GetFoldingsByEndAfterColumn(lineNumber, -1, false);
}
public List<FoldMarker> GetFoldedFoldingsWithEnd(int lineNumber)
{
return GetFoldingsByEndAfterColumn(lineNumber, -1, true);
}
public bool IsFoldStart(int lineNumber)
{
return GetFoldingsWithStart(lineNumber).Count > 0;
}
public bool IsFoldEnd(int lineNumber)
{
return GetFoldingsWithEnd(lineNumber).Count > 0;
}
public List<FoldMarker> GetFoldingsContainsLineNumber(int lineNumber)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
foreach (FoldMarker fm in foldMarker)
{
if (fm.StartLine < lineNumber && lineNumber < fm.EndLine)
{
foldings.Add(fm);
}
}
}
return foldings;
}
public bool IsBetweenFolding(int lineNumber)
{
return GetFoldingsContainsLineNumber(lineNumber).Count > 0;
}
public bool IsLineVisible(int lineNumber)
{
foreach (FoldMarker fm in GetFoldingsContainsLineNumber(lineNumber))
{
if (fm.IsFolded)
return false;
}
return true;
}
public List<FoldMarker> GetTopLevelFoldedFoldings()
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
Point end = new Point(0, 0);
foreach (FoldMarker fm in foldMarker)
{
if (fm.IsFolded && (fm.StartLine > end.Y || fm.StartLine == end.Y && fm.StartColumn >= end.X))
{
foldings.Add(fm);
end = new Point(fm.EndColumn, fm.EndLine);
}
}
}
return foldings;
}
public void UpdateFoldings(string fileName, object parseInfo)
{
UpdateFoldings(FoldingStrategy?.GenerateFoldMarkers(document, fileName, parseInfo));
}
public void UpdateFoldings(List<FoldMarker> newFoldings)
{
int oldFoldingsCount = foldMarker.Count;
lock (this)
{
if (newFoldings != null && newFoldings.Count != 0)
{
newFoldings.Sort();
if (foldMarker.Count == newFoldings.Count)
{
for (int i = 0; i < foldMarker.Count; ++i)
{
newFoldings[i].IsFolded = foldMarker[i].IsFolded;
}
foldMarker = newFoldings;
}
else
{
for (int i = 0, j = 0; i < foldMarker.Count && j < newFoldings.Count;)
{
int n = newFoldings[j].CompareTo(foldMarker[i]);
if (n > 0)
{
++i;
}
else
{
if (n == 0)
{
newFoldings[j].IsFolded = foldMarker[i].IsFolded;
}
++j;
}
}
}
}
if (newFoldings != null)
{
foldMarker = newFoldings;
foldMarkerByEnd = new List<FoldMarker>(newFoldings);
foldMarkerByEnd.Sort(EndComparer.Instance);
}
else
{
foldMarker.Clear();
foldMarkerByEnd.Clear();
}
}
if (oldFoldingsCount != foldMarker.Count)
{
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
document.CommitUpdate();
}
}
public string SerializeToString()
{
StringBuilder sb = new StringBuilder();
foreach (FoldMarker marker in this.foldMarker)
{
sb.Append(marker.Offset); sb.Append("\n");
sb.Append(marker.Length); sb.Append("\n");
sb.Append(marker.FoldText); sb.Append("\n");
sb.Append(marker.IsFolded); sb.Append("\n");
}
return sb.ToString();
}
public void DeserializeFromString(string str)
{
try
{
string[] lines = str.Split('\n');
for (int i = 0; i < lines.Length && lines[i].Length > 0; i += 4)
{
int offset = int.Parse(lines[i]);
int length = int.Parse(lines[i + 1]);
string text = lines[i + 2];
bool isFolded = bool.Parse(lines[i + 3]);
bool found = false;
foreach (FoldMarker marker in foldMarker)
{
if (marker.Offset == offset && marker.Length == length)
{
marker.IsFolded = isFolded;
found = true;
break;
}
}
if (!found)
{
foldMarker.Add(new FoldMarker(document, offset, length, text, isFolded));
}
}
if (lines.Length > 0)
{
NotifyFoldingsChanged(EventArgs.Empty);
}
}
catch (Exception)
{
// Empty catch
}
}
public void NotifyFoldingsChanged(EventArgs e)
{
if (FoldingsChanged != null)
{
FoldingsChanged(this, e);
}
}
public event EventHandler FoldingsChanged;
}
}

View File

@@ -0,0 +1,24 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface is used for the folding capabilities
/// of the textarea.
/// </summary>
public interface IFoldingStrategy
{
/// <remarks>
/// Calculates the fold level of a specific line.
/// </remarks>
List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation);
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
public interface IFoldingStrategyEx : IFoldingStrategy
{
List<string> GetFoldingErrors();
}
}

View File

@@ -0,0 +1,47 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A simple folding strategy which calculates the folding level
/// using the indent level of the line.
/// </summary>
public class IndentFoldingStrategy : IFoldingStrategy
{
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
List<FoldMarker> l = new List<FoldMarker>();
Stack<int> offsetStack = new Stack<int>();
Stack<string> textStack = new Stack<string>();
//int level = 0;
//foreach (LineSegment segment in document.LineSegmentCollection) {
//
//}
return l;
}
int GetLevel(IDocument document, int offset)
{
int level = 0;
int spaces = 0;
for (int i = offset; i < document.TextLength; ++i) {
char c = document.GetCharAt(i);
if (c == '\t' || (c == ' ' && ++spaces == 4)) {
spaces = 0;
++level;
} else {
break;
}
}
return level;
}
}
}

View File

@@ -0,0 +1,129 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is part of CodingEditor.
// Note: This project is derived from Peter Project
// (hosted on sourceforge and codeplex)
//
// Copyright (c) 2008-2009, CE Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
public class JSONFoldingStrategy : IFoldingStrategy
{
#region Methods
/// <summary>
/// Generates the foldings for our document.
/// </summary>
/// <param name="document">The current document.</param>
/// <param name="fileName">The filename of the document.</param>
/// <param name="parseInformation">Extra parse information, not used in this sample.</param>
/// <returns>A list of FoldMarkers.</returns>
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
var list = new List<FoldMarker>();
var startLines = new Stack<int>();
// Create foldmarkers for the whole document, enumerate through every line.
for (int i = 0; i < document.TotalNumberOfLines; i++)
{
var seg = document.GetLineSegment(i);
int offs, end = seg.Length + seg.Offset;
char c;
for (
offs = seg.Offset;
offs < end;
offs++)
{
c = document.GetCharAt(offs);
if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString()))
{
if (c == '{')
{
int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '{', '}');
if (offsetOfClosingBracket > 0)
{
int length = offsetOfClosingBracket - offs + 1;
list.Add(new FoldMarker(document, offs, length, "{...}", false));
}
}
if (c == '[')
{
int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '[', ']');
if (offsetOfClosingBracket > 0)
{
int length = offsetOfClosingBracket - offs + 1;
list.Add(new FoldMarker(document, offs, length, "[...]", false));
}
}
}
}
}
return list;
}
private int SearchBracketForward(IDocument document, int currLine, int currOffset, char openBracket, char closingBracket)
{
// Create foldmarkers for the whole document, enumerate through every line.
int brackets = 1, spaceCount = 0;
for (int i = currLine; i < document.TotalNumberOfLines; i++)
{
var seg = document.GetLineSegment(i);
int offs, end = seg.Length + seg.Offset;
char c;
for (
offs = i == currLine ? currOffset + 1 : seg.Offset;
offs < end;
offs++)
{
c = document.GetCharAt(offs);
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') spaceCount++;
if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString()))
{
if (c == openBracket)
{
++brackets;
}
else if (c == closingBracket)
{
--brackets;
if (brackets == 0)
{
if (offs - spaceCount - 1 == currOffset)
{
return -1;
}
return offs;
}
}
}
}
}
return -1;
}
#endregion Methods
}
}

View File

@@ -0,0 +1,310 @@
// Copied from http://codingeditor.googlecode.com/svn/trunk/libs/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/
#region Header
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Matthew Ward" email="mrward@users.sourceforge.net"/>
// <version>$Revision: 1971 $</version>
// </file>
#endregion Header
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
/// <summary>
/// Holds information about the start of a fold in an xml string.
/// </summary>
class XmlFoldStart
{
#region Fields
readonly int _col;
string _foldText = string.Empty;
readonly int _line;
readonly string _name = string.Empty;
readonly string _prefix = string.Empty;
#endregion Fields
#region Constructors
public XmlFoldStart(string prefix, string name, int line, int col)
{
_line = line;
_col = col;
_prefix = prefix;
_name = name;
}
#endregion Constructors
#region Properties
/// <summary>
/// The column where the fold should start. Columns start from 0.
/// </summary>
public int Column
{
get
{
return _col;
}
}
/// <summary>
/// The text to be displayed when the item is folded.
/// </summary>
public string FoldText
{
get
{
return _foldText;
}
set
{
_foldText = value;
}
}
/// <summary>
/// The line where the fold should start. Lines start from 0.
/// </summary>
public int Line
{
get
{
return _line;
}
}
/// <summary>
/// The name of the xml item with its prefix if it has one.
/// </summary>
public string Name
{
get
{
return _prefix.Length > 0 ? string.Concat(_prefix, ":", _name) : _name;
}
}
#endregion Properties
}
/// <summary>
/// Determines folds for an xml string in the editor.
/// </summary>
public class XmlFoldingStrategy : IFoldingStrategyEx
{
#region Fields
/// <summary>
/// Flag indicating whether attributes should be displayed on folded elements.
/// </summary>
public bool ShowAttributesWhenFolded = false;
private List<string> _foldingErrors = new List<string>();
#endregion Fields
#region Methods
public List<string> GetFoldingErrors()
{
return _foldingErrors;
}
/// <summary>
/// Adds folds to the text editor around each start-end element pair.
/// </summary>
/// <remarks>
/// <para>If the xml is not well formed then no folds are created.</para>
/// <para>Note that the xml text reader lines and positions start
/// from 1 and the SharpDevelop text editor line information starts from 0.</para>
/// </remarks>
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
_foldingErrors = new List<string>();
//showAttributesWhenFolded = XmlEditorAddInOptions.ShowAttributesWhenFolded;
var foldMarkers = new List<FoldMarker>();
var stack = new Stack();
try
{
string xml = document.TextContent;
var reader = new XmlTextReader(new StringReader(xml));
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (!reader.IsEmptyElement)
{
XmlFoldStart newFoldStart = CreateElementFoldStart(reader);
stack.Push(newFoldStart);
}
break;
case XmlNodeType.EndElement:
var foldStart = (XmlFoldStart)stack.Pop();
CreateElementFold(document, foldMarkers, reader, foldStart);
break;
case XmlNodeType.Comment:
CreateCommentFold(document, foldMarkers, reader);
break;
}
}
}
catch (Exception ex)
{
_foldingErrors.Add(ex.Message);
// If the xml is not well formed keep the foldings that already exist in the document.
return new List<FoldMarker>(document.FoldingManager.FoldMarker);
}
return foldMarkers;
}
/// <summary>
/// Xml encode the attribute string since the string returned from
/// the XmlTextReader is the plain unencoded string and .NET
/// does not provide us with an xml encode method.
/// </summary>
static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
{
var encodedValue = new StringBuilder(attributeValue);
encodedValue.Replace("&", "&amp;");
encodedValue.Replace("<", "&lt;");
encodedValue.Replace(">", "&gt;");
if (quoteChar == '"')
{
encodedValue.Replace("\"", "&quot;");
}
else
{
encodedValue.Replace("'", "&apos;");
}
return encodedValue.ToString();
}
/// <summary>
/// Creates a comment fold if the comment spans more than one line.
/// </summary>
/// <remarks>The text displayed when the comment is folded is the first
/// line of the comment.</remarks>
void CreateCommentFold(IDocument document, List<FoldMarker> foldMarkers, XmlTextReader reader)
{
if (reader.Value != null)
{
string comment = reader.Value.Replace("\r\n", "\n");
string[] lines = comment.Split('\n');
if (lines.Length > 1)
{
// Take off 5 chars to get the actual comment start (takes
// into account the <!-- chars.
int startCol = reader.LinePosition - 5;
int startLine = reader.LineNumber - 1;
// Add 3 to the end col value to take into account the '-->'
int endCol = lines[lines.Length - 1].Length + startCol + 3;
int endLine = startLine + lines.Length - 1;
string foldText = string.Concat("<!--", lines[0], "-->");
var foldMarker = new FoldMarker(document, startLine, startCol, endLine, endCol, FoldType.TypeBody, foldText);
foldMarkers.Add(foldMarker);
}
}
}
/// <summary>
/// Create an element fold if the start and end tag are on
/// different lines.
/// </summary>
void CreateElementFold(IDocument document, List<FoldMarker> foldMarkers, XmlTextReader reader, XmlFoldStart foldStart)
{
int endLine = reader.LineNumber - 1;
if (endLine > foldStart.Line)
{
int endCol = reader.LinePosition + foldStart.Name.Length;
var foldMarker = new FoldMarker(document, foldStart.Line, foldStart.Column, endLine, endCol, FoldType.TypeBody, foldStart.FoldText);
foldMarkers.Add(foldMarker);
}
}
/// <summary>
/// Creates an XmlFoldStart for the start tag of an element.
/// </summary>
XmlFoldStart CreateElementFoldStart(XmlTextReader reader)
{
// Take off 2 from the line position returned
// from the xml since it points to the start
// of the element name and not the beginning
// tag.
var newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2);
if (ShowAttributesWhenFolded && reader.HasAttributes)
{
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, " ", GetAttributeFoldText(reader), ">");
}
else
{
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, ">");
}
return newFoldStart;
}
/// <summary>
/// Gets the element's attributes as a string on one line that will
/// be displayed when the element is folded.
/// </summary>
/// <remarks>
/// Currently this puts all attributes from an element on the same
/// line of the start tag. It does not cater for elements where attributes
/// are not on the same line as the start tag.
/// </remarks>
string GetAttributeFoldText(XmlTextReader reader)
{
var text = new StringBuilder();
for (int i = 0; i < reader.AttributeCount; ++i)
{
reader.MoveToAttribute(i);
text.Append(reader.Name);
text.Append("=");
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
// Append a space if this is not the
// last attribute.
if (i < reader.AttributeCount - 1)
{
text.Append(" ");
}
}
return text.ToString();
}
#endregion Methods
}
}

View File

@@ -0,0 +1,218 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This class handles the auto and smart indenting in the textbuffer while
/// you type.
/// </summary>
public class DefaultFormattingStrategy : IFormattingStrategy
{
/// <summary>
/// Creates a new instance off <see cref="DefaultFormattingStrategy"/>
/// </summary>
public DefaultFormattingStrategy()
{
}
/// <summary>
/// returns the whitespaces which are before a non white space character in the line line
/// as a string.
/// </summary>
protected string GetIndentation(TextArea textArea, int lineNumber)
{
if (lineNumber < 0 || lineNumber > textArea.Document.TotalNumberOfLines) {
throw new ArgumentOutOfRangeException("lineNumber");
}
string lineText = TextUtilities.GetLineAsString(textArea.Document, lineNumber);
StringBuilder whitespaces = new StringBuilder();
foreach (char ch in lineText) {
if (char.IsWhiteSpace(ch)) {
whitespaces.Append(ch);
} else {
break;
}
}
return whitespaces.ToString();
}
/// <summary>
/// Could be overwritten to define more complex indenting.
/// </summary>
protected virtual int AutoIndentLine(TextArea textArea, int lineNumber)
{
string indentation = lineNumber != 0 ? GetIndentation(textArea, lineNumber - 1) : "";
if(indentation.Length > 0) {
string newLineText = indentation + TextUtilities.GetLineAsString(textArea.Document, lineNumber).Trim();
LineSegment oldLine = textArea.Document.GetLineSegment(lineNumber);
SmartReplaceLine(textArea.Document, oldLine, newLineText);
}
return indentation.Length;
}
static readonly char[] whitespaceChars = {' ', '\t'};
/// <summary>
/// Replaces the text in a line.
/// If only whitespace at the beginning and end of the line was changed, this method
/// only adjusts the whitespace and doesn't replace the other text.
/// </summary>
public static void SmartReplaceLine(IDocument document, LineSegment line, string newLineText)
{
if (document == null)
throw new ArgumentNullException("document");
if (line == null)
throw new ArgumentNullException("line");
if (newLineText == null)
throw new ArgumentNullException("newLineText");
string newLineTextTrim = newLineText.Trim(whitespaceChars);
string oldLineText = document.GetText(line);
if (oldLineText == newLineText)
return;
int pos = oldLineText.IndexOf(newLineTextTrim);
if (newLineTextTrim.Length > 0 && pos >= 0) {
document.UndoStack.StartUndoGroup();
try {
// find whitespace at beginning
int startWhitespaceLength = 0;
while (startWhitespaceLength < newLineText.Length) {
char c = newLineText[startWhitespaceLength];
if (c != ' ' && c != '\t')
break;
startWhitespaceLength++;
}
// find whitespace at end
int endWhitespaceLength = newLineText.Length - newLineTextTrim.Length - startWhitespaceLength;
// replace whitespace sections
int lineOffset = line.Offset;
document.Replace(lineOffset + pos + newLineTextTrim.Length, line.Length - pos - newLineTextTrim.Length, newLineText.Substring(newLineText.Length - endWhitespaceLength));
document.Replace(lineOffset, pos, newLineText.Substring(0, startWhitespaceLength));
} finally {
document.UndoStack.EndUndoGroup();
}
} else {
document.Replace(line.Offset, line.Length, newLineText);
}
}
/// <summary>
/// Could be overwritten to define more complex indenting.
/// </summary>
protected virtual int SmartIndentLine(TextArea textArea, int line)
{
return AutoIndentLine(textArea, line); // smart = autoindent in normal texts
}
/// <summary>
/// This function formats a specific line after <code>ch</code> is pressed.
/// </summary>
/// <returns>
/// the caret delta position the caret will be moved this number
/// of bytes (e.g. the number of bytes inserted before the caret, or
/// removed, if this number is negative)
/// </returns>
public virtual void FormatLine(TextArea textArea, int line, int cursorOffset, char ch)
{
if (ch == '\n') {
textArea.Caret.Column = IndentLine(textArea, line);
}
}
/// <summary>
/// This function sets the indentation level in a specific line
/// </summary>
/// <returns>
/// the number of inserted characters.
/// </returns>
public int IndentLine(TextArea textArea, int line)
{
textArea.Document.UndoStack.StartUndoGroup();
int result;
switch (textArea.Document.TextEditorProperties.IndentStyle) {
case IndentStyle.None:
result = 0;
break;
case IndentStyle.Auto:
result = AutoIndentLine(textArea, line);
break;
case IndentStyle.Smart:
result = SmartIndentLine(textArea, line);
break;
default:
throw new NotSupportedException("Unsupported value for IndentStyle: " + textArea.Document.TextEditorProperties.IndentStyle);
}
textArea.Document.UndoStack.EndUndoGroup();
return result;
}
/// <summary>
/// This function sets the indentlevel in a range of lines.
/// </summary>
public virtual void IndentLines(TextArea textArea, int begin, int end)
{
textArea.Document.UndoStack.StartUndoGroup();
for (int i = begin; i <= end; ++i) {
IndentLine(textArea, i);
}
textArea.Document.UndoStack.EndUndoGroup();
}
public virtual int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket)
{
int brackets = -1;
// first try "quick find" - find the matching bracket if there is no string/comment in the way
for (int i = offset; i >= 0; --i) {
char ch = document.GetCharAt(i);
if (ch == openBracket) {
++brackets;
if (brackets == 0) return i;
} else if (ch == closingBracket) {
--brackets;
} else if (ch == '"') {
break;
} else if (ch == '\'') {
break;
} else if (ch == '/' && i > 0) {
if (document.GetCharAt(i - 1) == '/') break;
if (document.GetCharAt(i - 1) == '*') break;
}
}
return -1;
}
public virtual int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket)
{
int brackets = 1;
// try "quick find" - find the matching bracket if there is no string/comment in the way
for (int i = offset; i < document.TextLength; ++i) {
char ch = document.GetCharAt(i);
if (ch == openBracket) {
++brackets;
} else if (ch == closingBracket) {
--brackets;
if (brackets == 0) return i;
} else if (ch == '"') {
break;
} else if (ch == '\'') {
break;
} else if (ch == '/' && i > 0) {
if (document.GetCharAt(i - 1) == '/') break;
} else if (ch == '*' && i > 0) {
if (document.GetCharAt(i - 1) == '/') break;
}
}
return -1;
}
}
}

View File

@@ -0,0 +1,59 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface handles the auto and smart indenting and formating
/// in the document while you type. Language bindings could overwrite this
/// interface and define their own indentation/formating.
/// </summary>
public interface IFormattingStrategy
{
/// <summary>
/// This function formats a specific line after <code>ch</code> is pressed.
/// </summary>
void FormatLine(TextArea textArea, int line, int caretOffset, char charTyped);
/// <summary>
/// This function sets the indentation level in a specific line
/// </summary>
/// <returns>
/// The target caret position (length of new indentation).
/// </returns>
int IndentLine(TextArea textArea, int line);
/// <summary>
/// This function sets the indentlevel in a range of lines.
/// </summary>
void IndentLines(TextArea textArea, int begin, int end);
/// <summary>
/// Finds the offset of the opening bracket in the block defined by offset skipping
/// brackets in strings and comments.
/// </summary>
/// <param name="document">The document to search in.</param>
/// <param name="offset">The offset of an position in the block or the offset of the closing bracket.</param>
/// <param name="openBracket">The character for the opening bracket.</param>
/// <param name="closingBracket">The character for the closing bracket.</param>
/// <returns>Returns the offset of the opening bracket or -1 if no matching bracket was found.</returns>
int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket);
/// <summary>
/// Finds the offset of the closing bracket in the block defined by offset skipping
/// brackets in strings and comments.
/// </summary>
/// <param name="document">The document to search in.</param>
/// <param name="offset">The offset of an position in the block or the offset of the opening bracket.</param>
/// <param name="openBracket">The character for the opening bracket.</param>
/// <param name="closingBracket">The character for the closing bracket.</param>
/// <returns>Returns the offset of the closing bracket or -1 if no matching bracket was found.</returns>
int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket);
}
}

View File

@@ -0,0 +1,917 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor.Document
{
public class DefaultHighlightingStrategy : IHighlightingStrategyUsingRuleSets
{
string name;
List<HighlightRuleSet> rules = new List<HighlightRuleSet>();
Dictionary<string, HighlightColor> environmentColors = new Dictionary<string, HighlightColor>();
Dictionary<string, string> properties = new Dictionary<string, string>();
string[] extensions;
HighlightColor digitColor;
HighlightRuleSet defaultRuleSet = null;
public HighlightColor DigitColor {
get {
return digitColor;
}
set {
digitColor = value;
}
}
public IEnumerable<KeyValuePair<string, HighlightColor>> EnvironmentColors {
get {
return environmentColors;
}
}
protected void ImportSettingsFrom(DefaultHighlightingStrategy source)
{
if (source == null)
throw new ArgumentNullException("source");
properties = source.properties;
extensions = source.extensions;
digitColor = source.digitColor;
defaultRuleSet = source.defaultRuleSet;
name = source.name;
rules = source.rules;
environmentColors = source.environmentColors;
defaultTextColor = source.defaultTextColor;
}
public DefaultHighlightingStrategy() : this("Default")
{
}
public DefaultHighlightingStrategy(string name)
{
this.name = name;
digitColor = new HighlightColor(SystemColors.WindowText, false, false);
defaultTextColor = new HighlightColor(SystemColors.WindowText, false, false);
// set small 'default color environment'
environmentColors["Default"] = new HighlightBackground("WindowText", "Window", false, false);
environmentColors["Selection"] = new HighlightColor("HighlightText", "Highlight", false, false);
environmentColors["VRuler"] = new HighlightColor("ControlLight", "Window", false, false);
environmentColors["InvalidLines"] = new HighlightColor(Color.Red, false, false);
environmentColors["CaretMarker"] = new HighlightColor(Color.Yellow, false, false);
environmentColors["CaretLine"] = new HighlightBackground("ControlLight", "Window", false, false);
environmentColors["LineNumbers"] = new HighlightBackground("ControlDark", "Window", false, false);
environmentColors["FoldLine"] = new HighlightColor("ControlDark", false, false);
environmentColors["FoldMarker"] = new HighlightColor("WindowText", "Window", false, false);
environmentColors["SelectedFoldLine"] = new HighlightColor("WindowText", false, false);
environmentColors["EOLMarkers"] = new HighlightColor("ControlLight", "Window", false, false);
environmentColors["SpaceMarkers"] = new HighlightColor("ControlLight", "Window", false, false);
environmentColors["TabMarkers"] = new HighlightColor("ControlLight", "Window", false, false);
}
public Dictionary<string, string> Properties {
get {
return properties;
}
}
public string Name
{
get {
return name;
}
}
public string[] Extensions
{
set {
extensions = value;
}
get {
return extensions;
}
}
public List<HighlightRuleSet> Rules {
get {
return rules;
}
}
public HighlightRuleSet FindHighlightRuleSet(string name)
{
foreach(HighlightRuleSet ruleSet in rules) {
if (ruleSet.Name == name) {
return ruleSet;
}
}
return null;
}
public void AddRuleSet(HighlightRuleSet aRuleSet)
{
HighlightRuleSet existing = FindHighlightRuleSet(aRuleSet.Name);
if (existing != null) {
existing.MergeFrom(aRuleSet);
} else {
rules.Add(aRuleSet);
}
}
public void ResolveReferences()
{
// Resolve references from Span definitions to RuleSets
ResolveRuleSetReferences();
// Resolve references from RuleSet defintitions to Highlighters defined in an external mode file
ResolveExternalReferences();
}
void ResolveRuleSetReferences()
{
foreach (HighlightRuleSet ruleSet in Rules) {
if (ruleSet.Name == null) {
defaultRuleSet = ruleSet;
}
foreach (Span aSpan in ruleSet.Spans) {
if (aSpan.Rule != null) {
bool found = false;
foreach (HighlightRuleSet refSet in Rules) {
if (refSet.Name == aSpan.Rule) {
found = true;
aSpan.RuleSet = refSet;
break;
}
}
if (!found) {
aSpan.RuleSet = null;
throw new HighlightingDefinitionInvalidException("The RuleSet " + aSpan.Rule + " could not be found in mode definition " + this.Name);
}
} else {
aSpan.RuleSet = null;
}
}
}
if (defaultRuleSet == null) {
throw new HighlightingDefinitionInvalidException("No default RuleSet is defined for mode definition " + this.Name);
}
}
void ResolveExternalReferences()
{
foreach (HighlightRuleSet ruleSet in Rules) {
ruleSet.Highlighter = this;
if (ruleSet.Reference != null) {
IHighlightingStrategy highlighter = HighlightingManager.Manager.FindHighlighter (ruleSet.Reference);
if (highlighter == null)
throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + this.Name + " mode definition could not be found");
if (highlighter is IHighlightingStrategyUsingRuleSets)
ruleSet.Highlighter = (IHighlightingStrategyUsingRuleSets)highlighter;
else
throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + this.Name + " mode definition does not implement IHighlightingStrategyUsingRuleSets");
}
}
}
// internal void SetDefaultColor(HighlightBackground color)
// {
// return (HighlightColor)environmentColors[name];
// defaultColor = color;
// }
HighlightColor defaultTextColor;
public HighlightColor DefaultTextColor {
get {
return defaultTextColor;
}
}
public void SetColorFor(string name, HighlightColor color)
{
if (name == "Default")
defaultTextColor = new HighlightColor(color.Color, color.Bold, color.Italic);
environmentColors[name] = color;
}
public HighlightColor GetColorFor(string name)
{
HighlightColor color;
if (environmentColors.TryGetValue(name, out color))
return color;
else
return defaultTextColor;
}
public HighlightColor GetColor(IDocument document, LineSegment currentSegment, int currentOffset, int currentLength)
{
return GetColor(defaultRuleSet, document, currentSegment, currentOffset, currentLength);
}
protected virtual HighlightColor GetColor(HighlightRuleSet ruleSet, IDocument document, LineSegment currentSegment, int currentOffset, int currentLength)
{
if (ruleSet != null) {
if (ruleSet.Reference != null) {
return ruleSet.Highlighter.GetColor(document, currentSegment, currentOffset, currentLength);
} else {
return (HighlightColor)ruleSet.KeyWords[document, currentSegment, currentOffset, currentLength];
}
}
return null;
}
public HighlightRuleSet GetRuleSet(Span aSpan)
{
if (aSpan == null) {
return this.defaultRuleSet;
} else {
if (aSpan.RuleSet != null)
{
if (aSpan.RuleSet.Reference != null) {
return aSpan.RuleSet.Highlighter.GetRuleSet(null);
} else {
return aSpan.RuleSet;
}
} else {
return null;
}
}
}
// Line state variable
protected LineSegment currentLine;
protected int currentLineNumber;
// Span stack state variable
protected SpanStack currentSpanStack;
public virtual void MarkTokens(IDocument document)
{
if (Rules.Count == 0) {
return;
}
int lineNumber = 0;
while (lineNumber < document.TotalNumberOfLines) {
LineSegment previousLine = (lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null);
if (lineNumber >= document.LineSegmentCollection.Count) { // may be, if the last line ends with a delimiter
break; // then the last line is not in the collection :)
}
currentSpanStack = ((previousLine != null && previousLine.HighlightSpanStack != null) ? previousLine.HighlightSpanStack.Clone() : null);
if (currentSpanStack != null) {
while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL)
{
currentSpanStack.Pop();
}
if (currentSpanStack.IsEmpty) currentSpanStack = null;
}
currentLine = (LineSegment)document.LineSegmentCollection[lineNumber];
if (currentLine.Length == -1) { // happens when buffer is empty !
return;
}
currentLineNumber = lineNumber;
List<TextWord> words = ParseLine(document);
// Alex: clear old words
if (currentLine.Words != null) {
currentLine.Words.Clear();
}
currentLine.Words = words;
currentLine.HighlightSpanStack = (currentSpanStack==null || currentSpanStack.IsEmpty) ? null : currentSpanStack;
++lineNumber;
}
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
document.CommitUpdate();
currentLine = null;
}
bool MarkTokensInLine(IDocument document, int lineNumber, ref bool spanChanged)
{
currentLineNumber = lineNumber;
bool processNextLine = false;
LineSegment previousLine = (lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null);
currentSpanStack = ((previousLine != null && previousLine.HighlightSpanStack != null) ? previousLine.HighlightSpanStack.Clone() : null);
if (currentSpanStack != null) {
while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL) {
currentSpanStack.Pop();
}
if (currentSpanStack.IsEmpty) {
currentSpanStack = null;
}
}
currentLine = (LineSegment)document.LineSegmentCollection[lineNumber];
if (currentLine.Length == -1) { // happens when buffer is empty !
return false;
}
List<TextWord> words = ParseLine(document);
if (currentSpanStack != null && currentSpanStack.IsEmpty) {
currentSpanStack = null;
}
// Check if the span state has changed, if so we must re-render the next line
// This check may seem utterly complicated but I didn't want to introduce any function calls
// or allocations here for perf reasons.
if(currentLine.HighlightSpanStack != currentSpanStack) {
if (currentLine.HighlightSpanStack == null) {
processNextLine = false;
foreach (Span sp in currentSpanStack) {
if (!sp.StopEOL) {
spanChanged = true;
processNextLine = true;
break;
}
}
} else if (currentSpanStack == null) {
processNextLine = false;
foreach (Span sp in currentLine.HighlightSpanStack) {
if (!sp.StopEOL) {
spanChanged = true;
processNextLine = true;
break;
}
}
} else {
SpanStack.Enumerator e1 = currentSpanStack.GetEnumerator();
SpanStack.Enumerator e2 = currentLine.HighlightSpanStack.GetEnumerator();
bool done = false;
while (!done) {
bool blockSpanIn1 = false;
while (e1.MoveNext()) {
if (!((Span)e1.Current).StopEOL) {
blockSpanIn1 = true;
break;
}
}
bool blockSpanIn2 = false;
while (e2.MoveNext()) {
if (!((Span)e2.Current).StopEOL) {
blockSpanIn2 = true;
break;
}
}
if (blockSpanIn1 || blockSpanIn2) {
if (blockSpanIn1 && blockSpanIn2) {
if (e1.Current != e2.Current) {
done = true;
processNextLine = true;
spanChanged = true;
}
} else {
spanChanged = true;
done = true;
processNextLine = true;
}
} else {
done = true;
processNextLine = false;
}
}
}
} else {
processNextLine = false;
}
//// Alex: remove old words
if (currentLine.Words!=null) currentLine.Words.Clear();
currentLine.Words = words;
currentLine.HighlightSpanStack = (currentSpanStack != null && !currentSpanStack.IsEmpty) ? currentSpanStack : null;
return processNextLine;
}
public virtual void MarkTokens(IDocument document, List<LineSegment> inputLines)
{
if (Rules.Count == 0) {
return;
}
Dictionary<LineSegment, bool> processedLines = new Dictionary<LineSegment, bool>();
bool spanChanged = false;
int documentLineSegmentCount = document.LineSegmentCollection.Count;
foreach (LineSegment lineToProcess in inputLines) {
if (!processedLines.ContainsKey(lineToProcess)) {
int lineNumber = lineToProcess.LineNumber;
bool processNextLine = true;
if (lineNumber != -1) {
while (processNextLine && lineNumber < documentLineSegmentCount) {
processNextLine = MarkTokensInLine(document, lineNumber, ref spanChanged);
processedLines[currentLine] = true;
++lineNumber;
}
}
}
}
if (spanChanged || inputLines.Count > 20) {
// if the span was changed (more than inputLines lines had to be reevaluated)
// or if there are many lines in inputLines, it's faster to update the whole
// text area instead of many small segments
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
} else {
// document.Caret.ValidateCaretPos();
// document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, document.GetLineNumberForOffset(document.Caret.Offset)));
//
foreach (LineSegment lineToProcess in inputLines) {
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, lineToProcess.LineNumber));
}
}
document.CommitUpdate();
currentLine = null;
}
// Span state variables
protected bool inSpan;
protected Span activeSpan;
protected HighlightRuleSet activeRuleSet;
// Line scanning state variables
protected int currentOffset;
protected int currentLength;
protected bool isDelimiter = false;
void UpdateSpanStateVariables()
{
inSpan = (currentSpanStack != null && !currentSpanStack.IsEmpty);
activeSpan = inSpan ? currentSpanStack.Peek() : null;
activeRuleSet = GetRuleSet(activeSpan);
}
List<TextWord> ParseLine(IDocument document)
{
List<TextWord> words = new List<TextWord>();
HighlightColor markNext = null;
currentOffset = 0;
currentLength = 0;
UpdateSpanStateVariables();
int currentLineLength = currentLine.Length;
int currentLineOffset = currentLine.Offset;
for (int i = 0; i < currentLineLength; ++i) {
char ch = document.GetCharAt(currentLineOffset + i);
switch (ch) {
case '\n':
case '\r':
PushCurWord(document, ref markNext, words);
++currentOffset;
break;
case ' ':
PushCurWord(document, ref markNext, words);
if (activeSpan != null && activeSpan.Color.HasBackground) {
words.Add(new TextWord.SpaceTextWord(activeSpan.Color));
} else {
words.Add(TextWord.Space);
}
++currentOffset;
break;
case '\t':
PushCurWord(document, ref markNext, words);
if (activeSpan != null && activeSpan.Color.HasBackground) {
words.Add(new TextWord.TabTextWord(activeSpan.Color));
} else {
words.Add(TextWord.Tab);
}
++currentOffset;
break;
default:
{
// handle escape characters
char escapeCharacter = '\0';
if (activeSpan != null && activeSpan.EscapeCharacter != '\0') {
escapeCharacter = activeSpan.EscapeCharacter;
} else if (activeRuleSet != null) {
escapeCharacter = activeRuleSet.EscapeCharacter;
}
if (escapeCharacter != '\0' && escapeCharacter == ch) {
// we found the escape character
if (activeSpan != null && activeSpan.End != null && activeSpan.End.Length == 1
&& escapeCharacter == activeSpan.End[0])
{
// the escape character is a end-doubling escape character
// it may count as escape only when the next character is the escape, too
if (i + 1 < currentLineLength) {
if (document.GetCharAt(currentLineOffset + i + 1) == escapeCharacter) {
currentLength += 2;
PushCurWord(document, ref markNext, words);
++i;
continue;
}
}
} else {
// this is a normal \-style escape
++currentLength;
if (i + 1 < currentLineLength) {
++currentLength;
}
PushCurWord(document, ref markNext, words);
++i;
continue;
}
}
// highlight digits
if (!inSpan && (char.IsDigit(ch) || (ch == '.' && i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1)))) && currentLength == 0) {
bool ishex = false;
bool isfloatingpoint = false;
if (ch == '0' && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'X') { // hex digits
const string hex = "0123456789ABCDEF";
++currentLength;
++i; // skip 'x'
++currentLength;
ishex = true;
while (i + 1 < currentLineLength && hex.IndexOf(char.ToUpper(document.GetCharAt(currentLineOffset + i + 1))) != -1) {
++i;
++currentLength;
}
} else {
++currentLength;
while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) {
++i;
++currentLength;
}
}
if (!ishex && i + 1 < currentLineLength && document.GetCharAt(currentLineOffset + i + 1) == '.') {
isfloatingpoint = true;
++i;
++currentLength;
while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) {
++i;
++currentLength;
}
}
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'E') {
isfloatingpoint = true;
++i;
++currentLength;
if (i + 1 < currentLineLength && (document.GetCharAt(currentLineOffset + i + 1) == '+' || document.GetCharAt(currentLine.Offset + i + 1) == '-')) {
++i;
++currentLength;
}
while (i + 1 < currentLine.Length && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) {
++i;
++currentLength;
}
}
if (i + 1 < currentLine.Length) {
char nextch = char.ToUpper(document.GetCharAt(currentLineOffset + i + 1));
if (nextch == 'F' || nextch == 'M' || nextch == 'D') {
isfloatingpoint = true;
++i;
++currentLength;
}
}
if (!isfloatingpoint) {
bool isunsigned = false;
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U') {
++i;
++currentLength;
isunsigned = true;
}
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'L') {
++i;
++currentLength;
if (!isunsigned && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U') {
++i;
++currentLength;
}
}
}
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, DigitColor, false));
currentOffset += currentLength;
currentLength = 0;
continue;
}
// Check for SPAN ENDs
if (inSpan) {
if (activeSpan.End != null && activeSpan.End.Length > 0) {
if (MatchExpr(currentLine, activeSpan.End, i, document, activeSpan.IgnoreCase)) {
PushCurWord(document, ref markNext, words);
string regex = GetRegString(currentLine, activeSpan.End, i, document);
currentLength += regex.Length;
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, activeSpan.EndColor, false));
currentOffset += currentLength;
currentLength = 0;
i += regex.Length - 1;
currentSpanStack.Pop();
UpdateSpanStateVariables();
continue;
}
}
}
// check for SPAN BEGIN
if (activeRuleSet != null) {
foreach (Span span in activeRuleSet.Spans) {
if ((!span.IsBeginSingleWord || currentLength == 0)
&& (!span.IsBeginStartOfLine.HasValue || span.IsBeginStartOfLine.Value == (currentLength == 0 && words.TrueForAll(delegate(TextWord textWord) { return textWord.Type != TextWordType.Word; })))
&& MatchExpr(currentLine, span.Begin, i, document, activeRuleSet.IgnoreCase)) {
PushCurWord(document, ref markNext, words);
string regex = GetRegString(currentLine, span.Begin, i, document);
if (!OverrideSpan(regex, document, words, span, ref i)) {
currentLength += regex.Length;
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, span.BeginColor, false));
currentOffset += currentLength;
currentLength = 0;
i += regex.Length - 1;
if (currentSpanStack == null) {
currentSpanStack = new SpanStack();
}
currentSpanStack.Push(span);
span.IgnoreCase = activeRuleSet.IgnoreCase;
UpdateSpanStateVariables();
}
goto skip;
}
}
}
// check if the char is a delimiter
if (activeRuleSet != null && (int)ch < 256 && activeRuleSet.Delimiters[(int)ch]) {
PushCurWord(document, ref markNext, words);
isDelimiter = true;
if (currentOffset + currentLength +1 < currentLine.Length) {
++currentLength;
PushCurWord(document, ref markNext, words);
goto skip;
}
}
++currentLength;
skip: continue;
}
}
}
PushCurWord(document, ref markNext, words);
OnParsedLine(document, currentLine, words);
return words;
}
protected virtual void OnParsedLine(IDocument document, LineSegment currentLine, List<TextWord> words)
{
}
protected virtual bool OverrideSpan(string spanBegin, IDocument document, List<TextWord> words, Span span, ref int lineOffset)
{
return false;
}
/// <summary>
/// pushes the curWord string on the word list, with the
/// correct color.
/// </summary>
void PushCurWord(IDocument document, ref HighlightColor markNext, List<TextWord> words)
{
// Svante Lidman : Need to look through the next prev logic.
if (currentLength > 0) {
if (words.Count > 0 && activeRuleSet != null) {
TextWord prevWord = null;
int pInd = words.Count - 1;
while (pInd >= 0) {
if (!((TextWord)words[pInd]).IsWhiteSpace) {
prevWord = (TextWord)words[pInd];
if (prevWord.HasDefaultColor) {
PrevMarker marker = (PrevMarker)activeRuleSet.PrevMarkers[document, currentLine, currentOffset, currentLength];
if (marker != null) {
prevWord.SyntaxColor = marker.Color;
// document.Caret.ValidateCaretPos();
// document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, document.GetLineNumberForOffset(document.Caret.Offset)));
}
}
break;
}
pInd--;
}
}
if (inSpan) {
HighlightColor c = null;
bool hasDefaultColor = true;
if (activeSpan.Rule == null) {
c = activeSpan.Color;
} else {
c = GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength);
hasDefaultColor = false;
}
if (c == null) {
c = activeSpan.Color;
if (c.Color == Color.Transparent) {
c = this.DefaultTextColor;
}
hasDefaultColor = true;
}
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, markNext != null ? markNext : c, hasDefaultColor));
} else {
HighlightColor c = markNext != null ? markNext : GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength);
if (c == null) {
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, this.DefaultTextColor, true) {
IsDelimiter = isDelimiter
});
} else {
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, c, false)
{
IsDelimiter = isDelimiter
});
}
isDelimiter = false;
}
if (activeRuleSet != null) {
NextMarker nextMarker = (NextMarker)activeRuleSet.NextMarkers[document, currentLine, currentOffset, currentLength];
if (nextMarker != null) {
if (nextMarker.MarkMarker && words.Count > 0) {
TextWord prevword = ((TextWord)words[words.Count - 1]);
prevword.SyntaxColor = nextMarker.Color;
}
markNext = nextMarker.Color;
} else {
markNext = null;
}
}
currentOffset += currentLength;
currentLength = 0;
}
}
#region Matching
/// <summary>
/// get the string, which matches the regular expression expr,
/// in string s2 at index
/// </summary>
static string GetRegString(LineSegment lineSegment, char[] expr, int index, IDocument document)
{
int j = 0;
StringBuilder regexpr = new StringBuilder();
for (int i = 0; i < expr.Length; ++i, ++j) {
if (index + j >= lineSegment.Length)
break;
switch (expr[i]) {
case '@': // "special" meaning
++i;
if (i == expr.Length)
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @.");
switch (expr[i]) {
case '!': // don't match the following expression
StringBuilder whatmatch = new StringBuilder();
++i;
while (i < expr.Length && expr[i] != '@') {
whatmatch.Append(expr[i++]);
}
break;
case '@': // matches @
regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j));
break;
}
break;
default:
if (expr[i] != document.GetCharAt(lineSegment.Offset + index + j)) {
return regexpr.ToString();
}
regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j));
break;
}
}
return regexpr.ToString();
}
/// <summary>
/// returns true, if the get the string s2 at index matches the expression expr
/// </summary>
static bool MatchExpr(LineSegment lineSegment, char[] expr, int index, IDocument document, bool ignoreCase)
{
for (int i = 0, j = 0; i < expr.Length; ++i, ++j) {
switch (expr[i]) {
case '@': // "special" meaning
++i;
if (i == expr.Length)
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @.");
switch (expr[i]) {
case 'C': // match whitespace or punctuation
if (index + j == lineSegment.Offset || index + j >= lineSegment.Offset + lineSegment.Length) {
// nothing (EOL or SOL)
} else {
char ch = document.GetCharAt(lineSegment.Offset + index + j);
if (!char.IsWhiteSpace(ch) && !char.IsPunctuation(ch)) {
return false;
}
}
break;
case '!': // don't match the following expression
{
StringBuilder whatmatch = new StringBuilder();
++i;
while (i < expr.Length && expr[i] != '@') {
whatmatch.Append(expr[i++]);
}
if (lineSegment.Offset + index + j + whatmatch.Length < document.TextLength) {
int k = 0;
for (; k < whatmatch.Length; ++k) {
char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j + k)) : document.GetCharAt(lineSegment.Offset + index + j + k);
char spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k];
if (docChar != spanChar) {
break;
}
}
if (k >= whatmatch.Length) {
return false;
}
}
// --j;
break;
}
case '-': // don't match the expression before
{
StringBuilder whatmatch = new StringBuilder();
++i;
while (i < expr.Length && expr[i] != '@') {
whatmatch.Append(expr[i++]);
}
if (index - whatmatch.Length >= 0) {
int k = 0;
for (; k < whatmatch.Length; ++k) {
char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k)) : document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k);
char spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k];
if (docChar != spanChar)
break;
}
if (k >= whatmatch.Length) {
return false;
}
}
// --j;
break;
}
case '@': // matches @
if (index + j >= lineSegment.Length || '@' != document.GetCharAt(lineSegment.Offset + index + j)) {
return false;
}
break;
}
break;
default:
{
if (index + j >= lineSegment.Length) {
return false;
}
char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j)) : document.GetCharAt(lineSegment.Offset + index + j);
char spanChar = ignoreCase ? char.ToUpperInvariant(expr[i]) : expr[i];
if (docChar != spanChar) {
return false;
}
break;
}
}
}
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,103 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This class is used to generate bold, italic and bold/italic fonts out
/// of a base font.
/// </summary>
public class FontContainer
{
Font defaultFont;
Font regularfont, boldfont, italicfont, bolditalicfont;
/// <value>
/// The scaled, regular version of the base font
/// </value>
public Font RegularFont {
get {
return regularfont;
}
}
/// <value>
/// The scaled, bold version of the base font
/// </value>
public Font BoldFont {
get {
return boldfont;
}
}
/// <value>
/// The scaled, italic version of the base font
/// </value>
public Font ItalicFont {
get {
return italicfont;
}
}
/// <value>
/// The scaled, bold/italic version of the base font
/// </value>
public Font BoldItalicFont {
get {
return bolditalicfont;
}
}
static float twipsPerPixelY;
public static float TwipsPerPixelY {
get {
if (twipsPerPixelY == 0) {
using (Bitmap bmp = new Bitmap(1,1)) {
using (Graphics g = Graphics.FromImage(bmp)) {
twipsPerPixelY = 1440 / g.DpiY;
}
}
}
return twipsPerPixelY;
}
}
/// <value>
/// The base font
/// </value>
public Font DefaultFont {
get {
return defaultFont;
}
set {
// 1440 twips is one inch
float pixelSize = (float)Math.Round(value.SizeInPoints * 20 / TwipsPerPixelY);
defaultFont = value;
regularfont = new Font(value.FontFamily, pixelSize * TwipsPerPixelY / 20f, FontStyle.Regular);
boldfont = new Font(regularfont, FontStyle.Bold);
italicfont = new Font(regularfont, FontStyle.Italic);
bolditalicfont = new Font(regularfont, FontStyle.Bold | FontStyle.Italic);
}
}
public static Font ParseFont(string font)
{
string[] descr = font.Split(new char[]{',', '='});
return new Font(descr[1], float.Parse(descr[3]));
}
public FontContainer(Font defaultFont)
{
this.DefaultFont = defaultFont;
}
}
}

View File

@@ -0,0 +1,51 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Extens the highlighting color with a background image.
/// </summary>
public class HighlightBackground : HighlightColor
{
Image backgroundImage;
/// <value>
/// The image used as background
/// </value>
public Image BackgroundImage {
get {
return backgroundImage;
}
}
/// <summary>
/// Creates a new instance of <see cref="HighlightBackground"/>
/// </summary>
public HighlightBackground(XmlElement el) : base(el)
{
if (el.Attributes["image"] != null) {
backgroundImage = new Bitmap(el.Attributes["image"].InnerText);
}
}
/// <summary>
/// Creates a new instance of <see cref="HighlightBackground"/>
/// </summary>
public HighlightBackground(Color color, Color backgroundcolor, bool bold, bool italic) : base(color, backgroundcolor, bold, italic)
{
}
public HighlightBackground(string systemColor, string systemBackgroundColor, bool bold, bool italic) : base(systemColor, systemBackgroundColor, bold, italic)
{
}
}
}

View File

@@ -0,0 +1,274 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A color used for highlighting
/// </summary>
public class HighlightColor
{
Color color;
Color backgroundcolor = System.Drawing.Color.WhiteSmoke;
bool bold = false;
bool italic = false;
bool hasForeground = false;
bool hasBackground = false;
public bool HasForeground {
get {
return hasForeground;
}
}
public bool HasBackground {
get {
return hasBackground;
}
}
/// <value>
/// If true the font will be displayed bold style
/// </value>
public bool Bold {
get {
return bold;
}
}
/// <value>
/// If true the font will be displayed italic style
/// </value>
public bool Italic {
get {
return italic;
}
}
/// <value>
/// The background color used
/// </value>
public Color BackgroundColor {
get {
return backgroundcolor;
}
}
/// <value>
/// The foreground color used
/// </value>
public Color Color {
get {
return color;
}
}
/// <value>
/// The font used
/// </value>
public Font GetFont(FontContainer fontContainer)
{
if (Bold) {
return Italic ? fontContainer.BoldItalicFont : fontContainer.BoldFont;
}
return Italic ? fontContainer.ItalicFont : fontContainer.RegularFont;
}
Color ParseColorString(string colorName)
{
string[] cNames = colorName.Split('*');
PropertyInfo myPropInfo = typeof(System.Drawing.SystemColors).GetProperty(cNames[0], BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static);
Color c = (Color)myPropInfo.GetValue(null, null);
if (cNames.Length == 2) {
// hack : can't figure out how to parse doubles with '.' (culture info might set the '.' to ',')
double factor = double.Parse(cNames[1]) / 100;
c = Color.FromArgb((int)((double)c.R * factor), (int)((double)c.G * factor), (int)((double)c.B * factor));
}
return c;
}
/// <summary>
/// Creates a new instance of <see cref="HighlightColor"/>
/// </summary>
public HighlightColor(XmlElement el)
{
Debug.Assert(el != null, "ICSharpCode.TextEditor.Document.SyntaxColor(XmlElement el) : el == null");
if (el.Attributes["bold"] != null) {
bold = bool.Parse(el.Attributes["bold"].InnerText);
}
if (el.Attributes["italic"] != null) {
italic = bool.Parse(el.Attributes["italic"].InnerText);
}
if (el.Attributes["color"] != null) {
string c = el.Attributes["color"].InnerText;
if (c[0] == '#') {
color = ParseColor(c);
} else if (c.StartsWith("SystemColors.")) {
color = ParseColorString(c.Substring("SystemColors.".Length));
} else {
color = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
}
hasForeground = true;
} else {
color = Color.Transparent; // to set it to the default value.
}
if (el.Attributes["bgcolor"] != null) {
string c = el.Attributes["bgcolor"].InnerText;
if (c[0] == '#') {
backgroundcolor = ParseColor(c);
} else if (c.StartsWith("SystemColors.")) {
backgroundcolor = ParseColorString(c.Substring("SystemColors.".Length));
} else {
backgroundcolor = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
}
hasBackground = true;
}
}
/// <summary>
/// Creates a new instance of <see cref="HighlightColor"/>
/// </summary>
public HighlightColor(XmlElement el, HighlightColor defaultColor)
{
Debug.Assert(el != null, "ICSharpCode.TextEditor.Document.SyntaxColor(XmlElement el) : el == null");
if (el.Attributes["bold"] != null) {
bold = bool.Parse(el.Attributes["bold"].InnerText);
} else {
bold = defaultColor.Bold;
}
if (el.Attributes["italic"] != null) {
italic = bool.Parse(el.Attributes["italic"].InnerText);
} else {
italic = defaultColor.Italic;
}
if (el.Attributes["color"] != null) {
string c = el.Attributes["color"].InnerText;
if (c[0] == '#') {
color = ParseColor(c);
} else if (c.StartsWith("SystemColors.")) {
color = ParseColorString(c.Substring("SystemColors.".Length));
} else {
color = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
}
hasForeground = true;
} else {
color = defaultColor.color;
}
if (el.Attributes["bgcolor"] != null) {
string c = el.Attributes["bgcolor"].InnerText;
if (c[0] == '#') {
backgroundcolor = ParseColor(c);
} else if (c.StartsWith("SystemColors.")) {
backgroundcolor = ParseColorString(c.Substring("SystemColors.".Length));
} else {
backgroundcolor = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
}
hasBackground = true;
} else {
backgroundcolor = defaultColor.BackgroundColor;
}
}
/// <summary>
/// Creates a new instance of <see cref="HighlightColor"/>
/// </summary>
public HighlightColor(Color color, bool bold, bool italic)
{
hasForeground = true;
this.color = color;
this.bold = bold;
this.italic = italic;
}
/// <summary>
/// Creates a new instance of <see cref="HighlightColor"/>
/// </summary>
public HighlightColor(Color color, Color backgroundcolor, bool bold, bool italic)
{
hasForeground = true;
hasBackground = true;
this.color = color;
this.backgroundcolor = backgroundcolor;
this.bold = bold;
this.italic = italic;
}
/// <summary>
/// Creates a new instance of <see cref="HighlightColor"/>
/// </summary>
public HighlightColor(string systemColor, string systemBackgroundColor, bool bold, bool italic)
{
hasForeground = true;
hasBackground = true;
this.color = ParseColorString(systemColor);
this.backgroundcolor = ParseColorString(systemBackgroundColor);
this.bold = bold;
this.italic = italic;
}
/// <summary>
/// Creates a new instance of <see cref="HighlightColor"/>
/// </summary>
public HighlightColor(string systemColor, bool bold, bool italic)
{
hasForeground = true;
this.color = ParseColorString(systemColor);
this.bold = bold;
this.italic = italic;
}
static Color ParseColor(string c)
{
int a = 255;
int offset = 0;
if (c.Length > 7) {
offset = 2;
a = int.Parse(c.Substring(1,2), NumberStyles.HexNumber);
}
int r = int.Parse(c.Substring(1 + offset,2), NumberStyles.HexNumber);
int g = int.Parse(c.Substring(3 + offset,2), NumberStyles.HexNumber);
int b = int.Parse(c.Substring(5 + offset,2), NumberStyles.HexNumber);
return Color.FromArgb(a, r, g, b);
}
/// <summary>
/// Converts a <see cref="HighlightColor"/> instance to string (for debug purposes)
/// </summary>
public override string ToString()
{
return "[HighlightColor: Bold = " + Bold +
", Italic = " + Italic +
", Color = " + Color +
", BackgroundColor = " + BackgroundColor + "]";
}
}
}

View File

@@ -0,0 +1,25 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public class HighlightInfo
{
public bool BlockSpanOn = false;
public bool Span = false;
public Span CurSpan = null;
public HighlightInfo(Span curSpan, bool span, bool blockSpanOn)
{
this.CurSpan = curSpan;
this.Span = span;
this.BlockSpanOn = blockSpanOn;
}
}
}

View File

@@ -0,0 +1,182 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections;
using System.Xml;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Document
{
public class HighlightRuleSet
{
LookupTable keyWords;
ArrayList spans = new ArrayList();
LookupTable prevMarkers;
LookupTable nextMarkers;
char escapeCharacter;
bool ignoreCase = false;
string name = null;
bool[] delimiters = new bool[256];
string reference = null;
public ArrayList Spans {
get {
return spans;
}
}
internal IHighlightingStrategyUsingRuleSets Highlighter;
public LookupTable KeyWords {
get {
return keyWords;
}
}
public LookupTable PrevMarkers {
get {
return prevMarkers;
}
}
public LookupTable NextMarkers {
get {
return nextMarkers;
}
}
public bool[] Delimiters {
get {
return delimiters;
}
}
public char EscapeCharacter {
get {
return escapeCharacter;
}
}
public bool IgnoreCase {
get {
return ignoreCase;
}
}
public string Name {
get {
return name;
}
set {
name = value;
}
}
public string Reference {
get {
return reference;
}
}
public HighlightRuleSet()
{
keyWords = new LookupTable(false);
prevMarkers = new LookupTable(false);
nextMarkers = new LookupTable(false);
}
public HighlightRuleSet(XmlElement el)
{
XmlNodeList nodes;
if (el.Attributes["name"] != null) {
Name = el.Attributes["name"].InnerText;
}
if (el.HasAttribute("escapecharacter")) {
escapeCharacter = el.GetAttribute("escapecharacter")[0];
}
if (el.Attributes["reference"] != null) {
reference = el.Attributes["reference"].InnerText;
}
if (el.Attributes["ignorecase"] != null) {
ignoreCase = bool.Parse(el.Attributes["ignorecase"].InnerText);
}
for (int i = 0; i < Delimiters.Length; ++i) {
delimiters[i] = false;
}
if (el["Delimiters"] != null) {
string delimiterString = el["Delimiters"].InnerText;
foreach (char ch in delimiterString) {
delimiters[(int)ch] = true;
}
}
// Spans = new LookupTable(!IgnoreCase);
keyWords = new LookupTable(!IgnoreCase);
prevMarkers = new LookupTable(!IgnoreCase);
nextMarkers = new LookupTable(!IgnoreCase);
nodes = el.GetElementsByTagName("KeyWords");
foreach (XmlElement el2 in nodes) {
HighlightColor color = new HighlightColor(el2);
XmlNodeList keys = el2.GetElementsByTagName("Key");
foreach (XmlElement node in keys) {
keyWords[node.Attributes["word"].InnerText] = color;
}
}
nodes = el.GetElementsByTagName("Span");
foreach (XmlElement el2 in nodes) {
Spans.Add(new Span(el2));
/*
Span span = new Span(el2);
Spans[span.Begin] = span;*/
}
nodes = el.GetElementsByTagName("MarkPrevious");
foreach (XmlElement el2 in nodes) {
PrevMarker prev = new PrevMarker(el2);
prevMarkers[prev.What] = prev;
}
nodes = el.GetElementsByTagName("MarkFollowing");
foreach (XmlElement el2 in nodes) {
NextMarker next = new NextMarker(el2);
nextMarkers[next.What] = next;
}
}
/// <summary>
/// Merges spans etc. from the other rule set into this rule set.
/// </summary>
public void MergeFrom(HighlightRuleSet ruleSet)
{
for (int i = 0; i < delimiters.Length; i++) {
delimiters[i] |= ruleSet.delimiters[i];
}
// insert merged spans in front of old spans
ArrayList oldSpans = spans;
spans = (ArrayList)ruleSet.spans.Clone();
spans.AddRange(oldSpans);
//keyWords.MergeFrom(ruleSet.keyWords);
//prevMarkers.MergeFrom(ruleSet.prevMarkers);
//nextMarkers.MergeFrom(ruleSet.nextMarkers);
}
}
}

View File

@@ -0,0 +1,32 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Runtime.Serialization;
namespace ICSharpCode.TextEditor.Document
{
[Serializable()]
public class HighlightingColorNotFoundException : Exception
{
public HighlightingColorNotFoundException() : base()
{
}
public HighlightingColorNotFoundException(string message) : base(message)
{
}
public HighlightingColorNotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
protected HighlightingColorNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -0,0 +1,37 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Runtime.Serialization;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Indicates that the highlighting definition that was tried to load was invalid.
/// You get this exception only once per highlighting definition, after that the definition
/// is replaced with the default highlighter.
/// </summary>
[Serializable()]
public class HighlightingDefinitionInvalidException : Exception
{
public HighlightingDefinitionInvalidException() : base()
{
}
public HighlightingDefinitionInvalidException(string message) : base(message)
{
}
public HighlightingDefinitionInvalidException(string message, Exception innerException) : base(message, innerException)
{
}
protected HighlightingDefinitionInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -0,0 +1,110 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Schema;
namespace ICSharpCode.TextEditor.Document
{
public static class HighlightingDefinitionParser
{
public static DefaultHighlightingStrategy Parse(SyntaxMode syntaxMode, XmlReader xmlReader)
{
return Parse(null, syntaxMode, xmlReader);
}
public static DefaultHighlightingStrategy Parse(DefaultHighlightingStrategy highlighter, SyntaxMode syntaxMode, XmlReader xmlReader)
{
if (syntaxMode == null)
throw new ArgumentNullException("syntaxMode");
if (xmlReader == null)
throw new ArgumentNullException("xmlTextReader");
try {
List<ValidationEventArgs> errors = null;
XmlReaderSettings settings = new XmlReaderSettings();
Stream shemaStream = typeof(HighlightingDefinitionParser).Assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources.Mode.xsd");
settings.Schemas.Add("", new XmlTextReader(shemaStream));
settings.Schemas.ValidationEventHandler += delegate(object sender, ValidationEventArgs args) {
if (errors == null) {
errors = new List<ValidationEventArgs>();
}
errors.Add(args);
};
settings.ValidationType = ValidationType.Schema;
XmlReader validatingReader = XmlReader.Create(xmlReader, settings);
XmlDocument doc = new XmlDocument();
doc.Load(validatingReader);
if (highlighter == null)
highlighter = new DefaultHighlightingStrategy(doc.DocumentElement.Attributes["name"].InnerText);
if (doc.DocumentElement.HasAttribute("extends")) {
KeyValuePair<SyntaxMode, ISyntaxModeFileProvider> entry = HighlightingManager.Manager.FindHighlighterEntry(doc.DocumentElement.GetAttribute("extends"));
if (entry.Key == null) {
throw new HighlightingDefinitionInvalidException("Cannot find referenced highlighting source " + doc.DocumentElement.GetAttribute("extends"));
} else {
highlighter = Parse(highlighter, entry.Key, entry.Value.GetSyntaxModeFile(entry.Key));
if (highlighter == null) return null;
}
}
if (doc.DocumentElement.HasAttribute("extensions")) {
highlighter.Extensions = doc.DocumentElement.GetAttribute("extensions").Split(new char[] { ';', '|' });
}
XmlElement environment = doc.DocumentElement["Environment"];
if (environment != null) {
foreach (XmlNode node in environment.ChildNodes) {
if (node is XmlElement) {
XmlElement el = (XmlElement)node;
if (el.Name == "Custom") {
highlighter.SetColorFor(el.GetAttribute("name"), el.HasAttribute("bgcolor") ? new HighlightBackground(el) : new HighlightColor(el));
} else {
highlighter.SetColorFor(el.Name, el.HasAttribute("bgcolor") ? new HighlightBackground(el) : new HighlightColor(el));
}
}
}
}
// parse properties
if (doc.DocumentElement["Properties"]!= null) {
foreach (XmlElement propertyElement in doc.DocumentElement["Properties"].ChildNodes) {
highlighter.Properties[propertyElement.Attributes["name"].InnerText] = propertyElement.Attributes["value"].InnerText;
}
}
if (doc.DocumentElement["Digits"]!= null) {
highlighter.DigitColor = new HighlightColor(doc.DocumentElement["Digits"]);
}
XmlNodeList nodes = doc.DocumentElement.GetElementsByTagName("RuleSet");
foreach (XmlElement element in nodes) {
highlighter.AddRuleSet(new HighlightRuleSet(element));
}
xmlReader.Close();
if (errors != null) {
StringBuilder msg = new StringBuilder();
foreach (ValidationEventArgs args in errors) {
msg.AppendLine(args.Message);
}
throw new HighlightingDefinitionInvalidException(msg.ToString());
} else {
return highlighter;
}
} catch (Exception e) {
throw new HighlightingDefinitionInvalidException("Could not load mode definition file '" + syntaxMode.FileName + "'.\n", e);
}
}
}
}

View File

@@ -0,0 +1,166 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
namespace ICSharpCode.TextEditor.Document
{
public class HighlightingManager
{
ArrayList syntaxModeFileProviders = new ArrayList();
static HighlightingManager highlightingManager;
// hash table from extension name to highlighting definition,
// OR from extension name to Pair SyntaxMode,ISyntaxModeFileProvider
Hashtable highlightingDefs = new Hashtable();
Hashtable extensionsToName = new Hashtable();
public Hashtable HighlightingDefinitions {
get {
return highlightingDefs;
}
}
public static HighlightingManager Manager {
get {
return highlightingManager;
}
}
static HighlightingManager()
{
highlightingManager = new HighlightingManager();
highlightingManager.AddSyntaxModeFileProvider(new ResourceSyntaxModeProvider());
}
public HighlightingManager()
{
CreateDefaultHighlightingStrategy();
}
public void AddSyntaxModeFileProvider(ISyntaxModeFileProvider syntaxModeFileProvider)
{
foreach (SyntaxMode syntaxMode in syntaxModeFileProvider.SyntaxModes) {
highlightingDefs[syntaxMode.Name] = new DictionaryEntry(syntaxMode, syntaxModeFileProvider);
foreach (string extension in syntaxMode.Extensions) {
extensionsToName[extension.ToUpperInvariant()] = syntaxMode.Name;
}
}
if (!syntaxModeFileProviders.Contains(syntaxModeFileProvider)) {
syntaxModeFileProviders.Add(syntaxModeFileProvider);
}
}
public void AddHighlightingStrategy(IHighlightingStrategy highlightingStrategy)
{
highlightingDefs[highlightingStrategy.Name] = highlightingStrategy;
foreach (string extension in highlightingStrategy.Extensions)
{
extensionsToName[extension.ToUpperInvariant()] = highlightingStrategy.Name;
}
}
public void ReloadSyntaxModes()
{
highlightingDefs.Clear();
extensionsToName.Clear();
CreateDefaultHighlightingStrategy();
foreach (ISyntaxModeFileProvider provider in syntaxModeFileProviders) {
provider.UpdateSyntaxModeList();
AddSyntaxModeFileProvider(provider);
}
OnReloadSyntaxHighlighting(EventArgs.Empty);
}
void CreateDefaultHighlightingStrategy()
{
DefaultHighlightingStrategy defaultHighlightingStrategy = new DefaultHighlightingStrategy();
defaultHighlightingStrategy.Extensions = new string[] {};
defaultHighlightingStrategy.Rules.Add(new HighlightRuleSet());
highlightingDefs["Default"] = defaultHighlightingStrategy;
}
IHighlightingStrategy LoadDefinition(DictionaryEntry entry)
{
SyntaxMode syntaxMode = (SyntaxMode)entry.Key;
ISyntaxModeFileProvider syntaxModeFileProvider = (ISyntaxModeFileProvider)entry.Value;
DefaultHighlightingStrategy highlightingStrategy = null;
try {
var reader = syntaxModeFileProvider.GetSyntaxModeFile(syntaxMode);
if (reader == null)
throw new HighlightingDefinitionInvalidException("Could not get syntax mode file for " + syntaxMode.Name);
highlightingStrategy = HighlightingDefinitionParser.Parse(syntaxMode, reader);
if (highlightingStrategy.Name != syntaxMode.Name) {
throw new HighlightingDefinitionInvalidException("The name specified in the .xshd '" + highlightingStrategy.Name + "' must be equal the syntax mode name '" + syntaxMode.Name + "'");
}
} finally {
if (highlightingStrategy == null) {
highlightingStrategy = DefaultHighlighting;
}
highlightingDefs[syntaxMode.Name] = highlightingStrategy;
highlightingStrategy.ResolveReferences();
}
return highlightingStrategy;
}
public DefaultHighlightingStrategy DefaultHighlighting {
get {
return (DefaultHighlightingStrategy)highlightingDefs["Default"];
}
}
internal KeyValuePair<SyntaxMode, ISyntaxModeFileProvider> FindHighlighterEntry(string name)
{
foreach (ISyntaxModeFileProvider provider in syntaxModeFileProviders) {
foreach (SyntaxMode mode in provider.SyntaxModes) {
if (mode.Name == name) {
return new KeyValuePair<SyntaxMode, ISyntaxModeFileProvider>(mode, provider);
}
}
}
return new KeyValuePair<SyntaxMode, ISyntaxModeFileProvider>(null, null);
}
public IHighlightingStrategy FindHighlighter(string name)
{
object def = highlightingDefs[name];
if (def is DictionaryEntry) {
return LoadDefinition((DictionaryEntry)def);
}
return def == null ? DefaultHighlighting : (IHighlightingStrategy)def;
}
public IHighlightingStrategy FindHighlighterForFile(string fileName)
{
string highlighterName = (string)extensionsToName[Path.GetExtension(fileName).ToUpperInvariant()];
if (highlighterName != null) {
object def = highlightingDefs[highlighterName];
if (def is DictionaryEntry) {
return LoadDefinition((DictionaryEntry)def);
}
return def == null ? DefaultHighlighting : (IHighlightingStrategy)def;
} else {
return DefaultHighlighting;
}
}
protected virtual void OnReloadSyntaxHighlighting(EventArgs e)
{
if (ReloadSyntaxHighlighting != null) {
ReloadSyntaxHighlighting(this, e);
}
}
public event EventHandler ReloadSyntaxHighlighting;
}
}

View File

@@ -0,0 +1,40 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public class HighlightingStrategyFactory
{
public static IHighlightingStrategy CreateHighlightingStrategy()
{
return (IHighlightingStrategy)HighlightingManager.Manager.HighlightingDefinitions["Default"];
}
public static IHighlightingStrategy CreateHighlightingStrategy(string name)
{
IHighlightingStrategy highlightingStrategy = HighlightingManager.Manager.FindHighlighter(name);
if (highlightingStrategy == null)
{
return CreateHighlightingStrategy();
}
return highlightingStrategy;
}
public static IHighlightingStrategy CreateHighlightingStrategyForFile(string fileName)
{
IHighlightingStrategy highlightingStrategy = HighlightingManager.Manager.FindHighlighterForFile(fileName);
if (highlightingStrategy == null)
{
return CreateHighlightingStrategy();
}
return highlightingStrategy;
}
}
}

View File

@@ -0,0 +1,67 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A highlighting strategy for a buffer.
/// </summary>
public interface IHighlightingStrategy
{
/// <value>
/// The name of the highlighting strategy, must be unique
/// </value>
string Name {
get;
}
/// <value>
/// The file extenstions on which this highlighting strategy gets
/// used
/// </value>
string[] Extensions {
get;
}
Dictionary<string, string> Properties {
get;
}
// returns special color. (BackGround Color, Cursor Color and so on)
/// <remarks>
/// Gets the color of an Environment element.
/// </remarks>
HighlightColor GetColorFor(string name);
/// <remarks>
/// Used internally, do not call
/// </remarks>
void MarkTokens(IDocument document, List<LineSegment> lines);
/// <remarks>
/// Used internally, do not call
/// </remarks>
void MarkTokens(IDocument document);
}
public interface IHighlightingStrategyUsingRuleSets : IHighlightingStrategy
{
/// <remarks>
/// Used internally, do not call
/// </remarks>
HighlightRuleSet GetRuleSet(Span span);
/// <remarks>
/// Used internally, do not call
/// </remarks>
HighlightColor GetColor(IDocument document, LineSegment keyWord, int index, int length);
}
}

View File

@@ -0,0 +1,63 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Used for mark next token
/// </summary>
public class NextMarker
{
string what;
HighlightColor color;
bool markMarker = false;
/// <value>
/// String value to indicate to mark next token
/// </value>
public string What {
get {
return what;
}
}
/// <value>
/// Color for marking next token
/// </value>
public HighlightColor Color {
get {
return color;
}
}
/// <value>
/// If true the indication text will be marked with the same color
/// too
/// </value>
public bool MarkMarker {
get {
return markMarker;
}
}
/// <summary>
/// Creates a new instance of <see cref="NextMarker"/>
/// </summary>
public NextMarker(XmlElement mark)
{
color = new HighlightColor(mark);
what = mark.InnerText;
if (mark.Attributes["markmarker"] != null) {
markMarker = bool.Parse(mark.Attributes["markmarker"].InnerText);
}
}
}
}

View File

@@ -0,0 +1,63 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Used for mark previous token
/// </summary>
public class PrevMarker
{
string what;
HighlightColor color;
bool markMarker = false;
/// <value>
/// String value to indicate to mark previous token
/// </value>
public string What {
get {
return what;
}
}
/// <value>
/// Color for marking previous token
/// </value>
public HighlightColor Color {
get {
return color;
}
}
/// <value>
/// If true the indication text will be marked with the same color
/// too
/// </value>
public bool MarkMarker {
get {
return markMarker;
}
}
/// <summary>
/// Creates a new instance of <see cref="PrevMarker"/>
/// </summary>
public PrevMarker(XmlElement mark)
{
color = new HighlightColor(mark);
what = mark.InnerText;
if (mark.Attributes["markmarker"] != null) {
markMarker = bool.Parse(mark.Attributes["markmarker"].InnerText);
}
}
}
}

View File

@@ -0,0 +1,157 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
public sealed class Span
{
bool stopEOL;
HighlightColor color;
HighlightColor beginColor;
HighlightColor endColor;
char[] begin;
char[] end;
string name;
string rule;
HighlightRuleSet ruleSet;
char escapeCharacter;
bool ignoreCase;
bool isBeginSingleWord;
bool? isBeginStartOfLine;
bool isEndSingleWord;
internal HighlightRuleSet RuleSet {
get {
return ruleSet;
}
set {
ruleSet = value;
}
}
public bool IgnoreCase {
get {
return ignoreCase;
}
set {
ignoreCase = value;
}
}
public bool StopEOL {
get {
return stopEOL;
}
}
public bool? IsBeginStartOfLine {
get {
return isBeginStartOfLine;
}
}
public bool IsBeginSingleWord {
get {
return isBeginSingleWord;
}
}
public bool IsEndSingleWord {
get {
return isEndSingleWord;
}
}
public HighlightColor Color {
get {
return color;
}
}
public HighlightColor BeginColor {
get {
if(beginColor != null) {
return beginColor;
} else {
return color;
}
}
}
public HighlightColor EndColor {
get {
return endColor!=null ? endColor : color;
}
}
public char[] Begin {
get { return begin; }
}
public char[] End {
get { return end; }
}
public string Name {
get { return name; }
}
public string Rule {
get { return rule; }
}
/// <summary>
/// Gets the escape character of the span. The escape character is a character that can be used in front
/// of the span end to make it not end the span. The escape character followed by another escape character
/// means the escape character was escaped like in @"a "" b" literals in C#.
/// The default value '\0' means no escape character is allowed.
/// </summary>
public char EscapeCharacter {
get { return escapeCharacter; }
}
public Span(XmlElement span)
{
color = new HighlightColor(span);
if (span.HasAttribute("rule")) {
rule = span.GetAttribute("rule");
}
if (span.HasAttribute("escapecharacter")) {
escapeCharacter = span.GetAttribute("escapecharacter")[0];
}
name = span.GetAttribute("name");
if (span.HasAttribute("stopateol")) {
stopEOL = bool.Parse(span.GetAttribute("stopateol"));
}
begin = span["Begin"].InnerText.ToCharArray();
beginColor = new HighlightColor(span["Begin"], color);
if (span["Begin"].HasAttribute("singleword")) {
this.isBeginSingleWord = bool.Parse(span["Begin"].GetAttribute("singleword"));
}
if (span["Begin"].HasAttribute("startofline")) {
this.isBeginStartOfLine = bool.Parse(span["Begin"].GetAttribute("startofline"));
}
if (span["End"] != null) {
end = span["End"].InnerText.ToCharArray();
endColor = new HighlightColor(span["End"], color);
if (span["End"].HasAttribute("singleword")) {
this.isEndSingleWord = bool.Parse(span["End"].GetAttribute("singleword"));
}
}
}
}
}

View File

@@ -0,0 +1,118 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A stack of Span instances. Works like Stack&lt;Span&gt;, but can be cloned quickly
/// because it is implemented as linked list.
/// </summary>
public sealed class SpanStack : ICloneable, IEnumerable<Span>
{
internal sealed class StackNode
{
public readonly StackNode Previous;
public readonly Span Data;
public StackNode(StackNode previous, Span data)
{
this.Previous = previous;
this.Data = data;
}
}
StackNode top = null;
public Span Pop()
{
Span s = top.Data;
top = top.Previous;
return s;
}
public Span Peek()
{
return top.Data;
}
public void Push(Span s)
{
top = new StackNode(top, s);
}
public bool IsEmpty {
get {
return top == null;
}
}
public SpanStack Clone()
{
SpanStack n = new SpanStack();
n.top = this.top;
return n;
}
object ICloneable.Clone()
{
return this.Clone();
}
public Enumerator GetEnumerator()
{
return new Enumerator(new StackNode(top, null));
}
IEnumerator<Span> IEnumerable<Span>.GetEnumerator()
{
return this.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public struct Enumerator : IEnumerator<Span>
{
StackNode c;
internal Enumerator(StackNode node)
{
c = node;
}
public Span Current {
get {
return c.Data;
}
}
object System.Collections.IEnumerator.Current {
get {
return c.Data;
}
}
public void Dispose()
{
c = null;
}
public bool MoveNext()
{
c = c.Previous;
return c != null;
}
public void Reset()
{
throw new NotSupportedException();
}
}
}
}

View File

@@ -0,0 +1,84 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
public class FileSyntaxModeProvider : ISyntaxModeFileProvider
{
string directory;
List<SyntaxMode> syntaxModes = null;
public ICollection<SyntaxMode> SyntaxModes {
get {
return syntaxModes;
}
}
public FileSyntaxModeProvider(string directory)
{
this.directory = directory;
UpdateSyntaxModeList();
}
public void UpdateSyntaxModeList()
{
string syntaxModeFile = Path.Combine(directory, "SyntaxModes.xml");
if (File.Exists(syntaxModeFile)) {
Stream s = File.OpenRead(syntaxModeFile);
syntaxModes = SyntaxMode.GetSyntaxModes(s);
s.Close();
} else {
syntaxModes = ScanDirectory(directory);
}
}
public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)
{
string syntaxModeFile = Path.Combine(directory, syntaxMode.FileName);
if (!File.Exists(syntaxModeFile)) {
throw new HighlightingDefinitionInvalidException("Can't load highlighting definition " + syntaxModeFile + " (file not found)!");
}
return new XmlTextReader(File.OpenRead(syntaxModeFile));
}
List<SyntaxMode> ScanDirectory(string directory)
{
string[] files = Directory.GetFiles(directory);
List<SyntaxMode> modes = new List<SyntaxMode>();
foreach (string file in files) {
if (Path.GetExtension(file).Equals(".XSHD", StringComparison.OrdinalIgnoreCase)) {
XmlTextReader reader = new XmlTextReader(file);
while (reader.Read()) {
if (reader.NodeType == XmlNodeType.Element) {
switch (reader.Name) {
case "SyntaxDefinition":
string name = reader.GetAttribute("name");
string extensions = reader.GetAttribute("extensions");
modes.Add(new SyntaxMode(Path.GetFileName(file),
name,
extensions));
goto bailout;
default:
throw new HighlightingDefinitionInvalidException("Unknown root node in syntax highlighting file :" + reader.Name);
}
}
}
bailout:
reader.Close();
}
}
return modes;
}
}
}

View File

@@ -0,0 +1,23 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
public interface ISyntaxModeFileProvider
{
ICollection<SyntaxMode> SyntaxModes {
get;
}
XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode);
void UpdateSyntaxModeList();
}
}

View File

@@ -0,0 +1,48 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
public class ResourceSyntaxModeProvider : ISyntaxModeFileProvider
{
List<SyntaxMode> syntaxModes = null;
public ICollection<SyntaxMode> SyntaxModes {
get {
return syntaxModes;
}
}
public ResourceSyntaxModeProvider()
{
Assembly assembly = typeof(SyntaxMode).Assembly;
Stream syntaxModeStream = assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources.SyntaxModes.xml");
if (syntaxModeStream != null) {
syntaxModes = SyntaxMode.GetSyntaxModes(syntaxModeStream);
} else {
syntaxModes = new List<SyntaxMode>();
}
}
public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)
{
Assembly assembly = typeof(SyntaxMode).Assembly;
return new XmlTextReader(assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources." + syntaxMode.FileName));
}
public void UpdateSyntaxModeList()
{
// resources don't change during runtime
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.HighlightingStrategy.SyntaxModes
{
public class ResourceSyntaxModeProviderEx : ISyntaxModeFileProvider
{
private const string ResourcesDir = "ICSharpCode.TextEditor.Resources.";
readonly List<SyntaxMode> _syntaxModes;
public ICollection<SyntaxMode> SyntaxModes
{
get
{
return _syntaxModes;
}
}
public ResourceSyntaxModeProviderEx()
{
var syntaxModeStream = GetSyntaxModeStream("SyntaxModesEx.xml");
_syntaxModes = syntaxModeStream != null ? SyntaxMode.GetSyntaxModes(syntaxModeStream) : new List<SyntaxMode>();
}
public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)
{
var stream = GetSyntaxModeStream(syntaxMode.FileName);
return stream != null ? new XmlTextReader(stream) : null;
}
public void UpdateSyntaxModeList()
{
// resources don't change during runtime
}
private Stream GetSyntaxModeStream(string filename)
{
Assembly assembly = Assembly.GetExecutingAssembly();
return assembly.GetManifestResourceStream(string.Format("{0}{1}", ResourcesDir, filename));
}
}
}

View File

@@ -0,0 +1,96 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml;
namespace ICSharpCode.TextEditor.Document
{
public class SyntaxMode
{
string fileName;
string name;
string[] extensions;
public string FileName {
get {
return fileName;
}
set {
fileName = value;
}
}
public string Name {
get {
return name;
}
set {
name = value;
}
}
public string[] Extensions {
get {
return extensions;
}
set {
extensions = value;
}
}
public SyntaxMode(string fileName, string name, string extensions)
{
this.fileName = fileName;
this.name = name;
this.extensions = extensions.Split(';', '|', ',');
}
public SyntaxMode(string fileName, string name, string[] extensions)
{
this.fileName = fileName;
this.name = name;
this.extensions = extensions;
}
public static List<SyntaxMode> GetSyntaxModes(Stream xmlSyntaxModeStream)
{
XmlTextReader reader = new XmlTextReader(xmlSyntaxModeStream);
List<SyntaxMode> syntaxModes = new List<SyntaxMode>();
while (reader.Read()) {
switch (reader.NodeType) {
case XmlNodeType.Element:
switch (reader.Name) {
case "SyntaxModes":
string version = reader.GetAttribute("version");
if (version != "1.0") {
throw new HighlightingDefinitionInvalidException("Unknown syntax mode file defininition with version " + version);
}
break;
case "Mode":
syntaxModes.Add(new SyntaxMode(reader.GetAttribute("file"),
reader.GetAttribute("name"),
reader.GetAttribute("extensions")));
break;
default:
throw new HighlightingDefinitionInvalidException("Unknown node in syntax mode file :" + reader.Name);
}
break;
}
}
reader.Close();
return syntaxModes;
}
public override string ToString()
{
return string.Format("[SyntaxMode: FileName={0}, Name={1}, Extensions=({2})]", fileName, name, string.Join(",", extensions));
}
}
}

View File

@@ -0,0 +1,239 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Drawing;
namespace ICSharpCode.TextEditor.Document
{
public enum TextWordType {
Word,
Space,
Tab
}
/// <summary>
/// This class represents single words with color information, two special versions of a word are
/// spaces and tabs.
/// </summary>
public class TextWord
{
HighlightColor color;
LineSegment line;
IDocument document;
int offset;
int length;
public sealed class SpaceTextWord : TextWord
{
public SpaceTextWord()
{
length = 1;
}
public SpaceTextWord(HighlightColor color)
{
length = 1;
base.SyntaxColor = color;
}
public override Font GetFont(FontContainer fontContainer)
{
return null;
}
public override TextWordType Type {
get {
return TextWordType.Space;
}
}
public override bool IsWhiteSpace {
get {
return true;
}
}
}
public sealed class TabTextWord : TextWord
{
public TabTextWord()
{
length = 1;
}
public TabTextWord(HighlightColor color)
{
length = 1;
base.SyntaxColor = color;
}
public override Font GetFont(FontContainer fontContainer)
{
return null;
}
public override TextWordType Type {
get {
return TextWordType.Tab;
}
}
public override bool IsWhiteSpace {
get {
return true;
}
}
}
static TextWord spaceWord = new SpaceTextWord();
static TextWord tabWord = new TabTextWord();
bool hasDefaultColor;
public static TextWord Space {
get {
return spaceWord;
}
}
public static TextWord Tab {
get {
return tabWord;
}
}
public int Offset {
get {
return offset;
}
}
public int Length {
get {
return length;
}
}
/// <summary>
/// Splits the <paramref name="word"/> into two parts: the part before <paramref name="pos"/> is assigned to
/// the reference parameter <paramref name="word"/>, the part after <paramref name="pos"/> is returned.
/// </summary>
public static TextWord Split(ref TextWord word, int pos)
{
#if DEBUG
if (word.Type != TextWordType.Word)
throw new ArgumentException("word.Type must be Word");
if (pos <= 0)
throw new ArgumentOutOfRangeException("pos", pos, "pos must be > 0");
if (pos >= word.Length)
throw new ArgumentOutOfRangeException("pos", pos, "pos must be < word.Length");
#endif
TextWord after = new TextWord(word.document, word.line, word.offset + pos, word.length - pos, word.color, word.hasDefaultColor);
word = new TextWord(word.document, word.line, word.offset, pos, word.color, word.hasDefaultColor);
return after;
}
public bool HasDefaultColor {
get {
return hasDefaultColor;
}
}
public virtual TextWordType Type {
get {
return TextWordType.Word;
}
}
public string Word {
get {
if (document == null) {
return string.Empty;
}
return document.GetText(line.Offset + offset, length);
}
}
public virtual Font GetFont(FontContainer fontContainer)
{
return color.GetFont(fontContainer);
}
public Color Color {
get {
if (color == null)
return Color.Black;
else
return color.Color;
}
}
public bool Bold {
get {
if (color == null)
return false;
else
return color.Bold;
}
}
public bool Italic {
get {
if (color == null)
return false;
else
return color.Italic;
}
}
public HighlightColor SyntaxColor {
get {
return color;
}
set {
Debug.Assert(value != null);
color = value;
}
}
public virtual bool IsWhiteSpace {
get {
return false;
}
}
public virtual bool IsDelimiter { get; set; } = false;
protected TextWord()
{
}
// TAB
public TextWord(IDocument document, LineSegment line, int offset, int length, HighlightColor color, bool hasDefaultColor)
{
Debug.Assert(document != null);
Debug.Assert(line != null);
Debug.Assert(color != null);
this.document = document;
this.line = line;
this.offset = offset;
this.length = length;
this.color = color;
this.hasDefaultColor = hasDefaultColor;
}
/// <summary>
/// Converts a <see cref="TextWord"/> instance to string (for debug purposes)
/// </summary>
public override string ToString()
{
return "[TextWord: Word = " + Word + ", Color = " + Color + "]";
}
}
}

View File

@@ -0,0 +1,315 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using ICSharpCode.TextEditor.Undo;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface represents a container which holds a text sequence and
/// all necessary information about it. It is used as the base for a text editor.
/// </summary>
public interface IDocument
{
ITextEditorProperties TextEditorProperties {
get;
set;
}
UndoStack UndoStack {
get;
}
/// <value>
/// If true the document can't be altered
/// </value>
bool ReadOnly {
get;
set;
}
/// <summary>
/// The <see cref="IFormattingStrategy"/> attached to the <see cref="IDocument"/> instance
/// </summary>
IFormattingStrategy FormattingStrategy {
get;
set;
}
/// <summary>
/// The <see cref="ITextBufferStrategy"/> attached to the <see cref="IDocument"/> instance
/// </summary>
ITextBufferStrategy TextBufferStrategy {
get;
}
/// <summary>
/// The <see cref="FoldingManager"/> attached to the <see cref="IDocument"/> instance
/// </summary>
FoldingManager FoldingManager {
get;
}
/// <summary>
/// The <see cref="IHighlightingStrategy"/> attached to the <see cref="IDocument"/> instance
/// </summary>
IHighlightingStrategy HighlightingStrategy {
get;
set;
}
/// <summary>
/// The <see cref="IBookMarkManager"/> attached to the <see cref="IDocument"/> instance
/// </summary>
BookmarkManager BookmarkManager {
get;
}
MarkerStrategy MarkerStrategy {
get;
}
// /// <summary>
// /// The <see cref="SelectionManager"/> attached to the <see cref="IDocument"/> instance
// /// </summary>
// SelectionManager SelectionManager {
// get;
// }
#region ILineManager interface
/// <value>
/// A collection of all line segments
/// </value>
/// <remarks>
/// The collection should only be used if you're aware
/// of the 'last line ends with a delimiter problem'. Otherwise
/// the <see cref="GetLineSegment"/> method should be used.
/// </remarks>
IList<LineSegment> LineSegmentCollection {
get;
}
/// <value>
/// The total number of lines in the document.
/// </value>
int TotalNumberOfLines {
get;
}
/// <remarks>
/// Returns a valid line number for the given offset.
/// </remarks>
/// <param name="offset">
/// A offset which points to a character in the line which
/// line number is returned.
/// </param>
/// <returns>
/// An int which value is the line number.
/// </returns>
/// <exception cref="System.ArgumentException">If offset points not to a valid position</exception>
int GetLineNumberForOffset(int offset);
/// <remarks>
/// Returns a <see cref="LineSegment"/> for the given offset.
/// </remarks>
/// <param name="offset">
/// A offset which points to a character in the line which
/// is returned.
/// </param>
/// <returns>
/// A <see cref="LineSegment"/> object.
/// </returns>
/// <exception cref="System.ArgumentException">If offset points not to a valid position</exception>
LineSegment GetLineSegmentForOffset(int offset);
/// <remarks>
/// Returns a <see cref="LineSegment"/> for the given line number.
/// This function should be used to get a line instead of getting the
/// line using the <see cref="ArrayList"/>.
/// </remarks>
/// <param name="lineNumber">
/// The line number which is requested.
/// </param>
/// <returns>
/// A <see cref="LineSegment"/> object.
/// </returns>
/// <exception cref="System.ArgumentException">If offset points not to a valid position</exception>
LineSegment GetLineSegment(int lineNumber);
/// <remarks>
/// Get the first logical line for a given visible line.
/// example : lineNumber == 100 foldings are in the linetracker
/// between 0..1 (2 folded, invisible lines) this method returns 102
/// the 'logical' line number
/// </remarks>
int GetFirstLogicalLine(int lineNumber);
/// <remarks>
/// Get the last logical line for a given visible line.
/// example : lineNumber == 100 foldings are in the linetracker
/// between 0..1 (2 folded, invisible lines) this method returns 102
/// the 'logical' line number
/// </remarks>
int GetLastLogicalLine(int lineNumber);
/// <remarks>
/// Get the visible line for a given logical line.
/// example : lineNumber == 100 foldings are in the linetracker
/// between 0..1 (2 folded, invisible lines) this method returns 98
/// the 'visible' line number
/// </remarks>
int GetVisibleLine(int lineNumber);
// /// <remarks>
// /// Get the visible column for a given logical line and logical column.
// /// </remarks>
// int GetVisibleColumn(int logicalLine, int logicalColumn);
/// <remarks>
/// Get the next visible line after lineNumber
/// </remarks>
int GetNextVisibleLineAbove(int lineNumber, int lineCount);
/// <remarks>
/// Get the next visible line below lineNumber
/// </remarks>
int GetNextVisibleLineBelow(int lineNumber, int lineCount);
event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;
event EventHandler<LineCountChangeEventArgs> LineCountChanged;
event EventHandler<LineEventArgs> LineDeleted;
#endregion
#region ITextBufferStrategy interface
/// <value>
/// Get the whole text as string.
/// When setting the text using the TextContent property, the undo stack is cleared.
/// Set TextContent only for actions such as loading a file; if you want to change the current document
/// use the Replace method instead.
/// </value>
string TextContent {
get;
set;
}
/// <value>
/// The current length of the sequence of characters that can be edited.
/// </value>
int TextLength {
get;
}
/// <summary>
/// Inserts a string of characters into the sequence.
/// </summary>
/// <param name="offset">
/// offset where to insert the string.
/// </param>
/// <param name="text">
/// text to be inserted.
/// </param>
void Insert(int offset, string text);
/// <summary>
/// Removes some portion of the sequence.
/// </summary>
/// <param name="offset">
/// offset of the remove.
/// </param>
/// <param name="length">
/// number of characters to remove.
/// </param>
void Remove(int offset, int length);
/// <summary>
/// Replace some portion of the sequence.
/// </summary>
/// <param name="offset">
/// offset.
/// </param>
/// <param name="length">
/// number of characters to replace.
/// </param>
/// <param name="text">
/// text to be replaced with.
/// </param>
void Replace(int offset, int length, string text);
/// <summary>
/// Returns a specific char of the sequence.
/// </summary>
/// <param name="offset">
/// Offset of the char to get.
/// </param>
char GetCharAt(int offset);
/// <summary>
/// Fetches a string of characters contained in the sequence.
/// </summary>
/// <param name="offset">
/// Offset into the sequence to fetch
/// </param>
/// <param name="length">
/// number of characters to copy.
/// </param>
string GetText(int offset, int length);
#endregion
string GetText(ISegment segment);
#region ITextModel interface
/// <summary>
/// returns the logical line/column position from an offset
/// </summary>
TextLocation OffsetToPosition(int offset);
/// <summary>
/// returns the offset from a logical line/column position
/// </summary>
int PositionToOffset(TextLocation p);
#endregion
/// <value>
/// A container where all TextAreaUpdate objects get stored
/// </value>
List<TextAreaUpdate> UpdateQueue {
get;
}
/// <remarks>
/// Requests an update of the textarea
/// </remarks>
void RequestUpdate(TextAreaUpdate update);
/// <remarks>
/// Commits all updates in the queue to the textarea (the
/// textarea will be painted)
/// </remarks>
void CommitUpdate();
/// <summary>
/// Moves, Resizes, Removes a list of segments on insert/remove/replace events.
/// </summary>
void UpdateSegmentListOnDocumentChange<T>(List<T> list, DocumentEventArgs e) where T : ISegment;
/// <summary>
/// Is fired when CommitUpdate is called
/// </summary>
event EventHandler UpdateCommited;
/// <summary>
/// </summary>
event DocumentEventHandler DocumentAboutToBeChanged;
/// <summary>
/// </summary>
event DocumentEventHandler DocumentChanged;
event EventHandler TextContentChanged;
}
}

View File

@@ -0,0 +1,32 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface is used to describe a span inside a text sequence
/// </summary>
public interface ISegment
{
/// <value>
/// The offset where the span begins
/// </value>
int Offset {
get;
set;
}
/// <value>
/// The length of the span
/// </value>
int Length {
get;
set;
}
}
}

View File

@@ -0,0 +1,177 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public interface ITextEditorProperties
{
bool CaretLine
{
get;
set;
}
bool AutoInsertCurlyBracket { // is wrapped in text editor control
get;
set;
}
bool HideMouseCursor { // is wrapped in text editor control
get;
set;
}
bool IsIconBarVisible { // is wrapped in text editor control
get;
set;
}
bool AllowCaretBeyondEOL {
get;
set;
}
bool ShowMatchingBracket { // is wrapped in text editor control
get;
set;
}
bool CutCopyWholeLine {
get;
set;
}
System.Drawing.Text.TextRenderingHint TextRenderingHint { // is wrapped in text editor control
get;
set;
}
bool MouseWheelScrollDown {
get;
set;
}
bool MouseWheelTextZoom {
get;
set;
}
string LineTerminator {
get;
set;
}
LineViewerStyle LineViewerStyle { // is wrapped in text editor control
get;
set;
}
bool ShowInvalidLines { // is wrapped in text editor control
get;
set;
}
int VerticalRulerRow { // is wrapped in text editor control
get;
set;
}
bool ShowSpaces { // is wrapped in text editor control
get;
set;
}
bool ShowTabs { // is wrapped in text editor control
get;
set;
}
bool ShowEOLMarker { // is wrapped in text editor control
get;
set;
}
bool ConvertTabsToSpaces { // is wrapped in text editor control
get;
set;
}
bool ShowHorizontalRuler { // is wrapped in text editor control
get;
set;
}
bool ShowVerticalRuler { // is wrapped in text editor control
get;
set;
}
Encoding Encoding {
get;
set;
}
bool EnableFolding { // is wrapped in text editor control
get;
set;
}
bool ShowLineNumbers { // is wrapped in text editor control
get;
set;
}
/// <summary>
/// The width of a tab.
/// </summary>
int TabIndent { // is wrapped in text editor control
get;
set;
}
/// <summary>
/// The amount of spaces a tab is converted to if ConvertTabsToSpaces is true.
/// </summary>
int IndentationSize {
get;
set;
}
IndentStyle IndentStyle { // is wrapped in text editor control
get;
set;
}
DocumentSelectionMode DocumentSelectionMode {
get;
set;
}
Font Font { // is wrapped in text editor control
get;
set;
}
FontContainer FontContainer {
get;
}
BracketMatchingStyle BracketMatchingStyle { // is wrapped in text editor control
get;
set;
}
bool SupportReadOnlySegments {
get;
set;
}
}
}

View File

@@ -0,0 +1,44 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A list of events that are fired after the line manager has finished working.
/// </summary>
struct DeferredEventList
{
internal List<LineSegment> removedLines;
internal List<TextAnchor> textAnchor;
public void AddRemovedLine(LineSegment line)
{
if (removedLines == null)
removedLines = new List<LineSegment>();
removedLines.Add(line);
}
public void AddDeletedAnchor(TextAnchor anchor)
{
if (textAnchor == null)
textAnchor = new List<TextAnchor>();
textAnchor.Add(anchor);
}
public void RaiseEvents()
{
// removedLines is raised by the LineManager
if (textAnchor != null) {
foreach (TextAnchor a in textAnchor) {
a.RaiseDeleted();
}
}
}
}
}

View File

@@ -0,0 +1,369 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ICSharpCode.TextEditor.Document
{
internal sealed class LineManager
{
LineSegmentTree lineCollection = new LineSegmentTree();
IDocument document;
IHighlightingStrategy highlightingStrategy;
public IList<LineSegment> LineSegmentCollection {
get {
return lineCollection;
}
}
public int TotalNumberOfLines {
get {
return lineCollection.Count;
}
}
public IHighlightingStrategy HighlightingStrategy {
get {
return highlightingStrategy;
}
set {
if (highlightingStrategy != value) {
highlightingStrategy = value;
if (highlightingStrategy != null) {
highlightingStrategy.MarkTokens(document);
}
}
}
}
public LineManager(IDocument document, IHighlightingStrategy highlightingStrategy)
{
this.document = document;
this.highlightingStrategy = highlightingStrategy;
}
public int GetLineNumberForOffset(int offset)
{
return GetLineSegmentForOffset(offset).LineNumber;
}
public LineSegment GetLineSegmentForOffset(int offset)
{
return lineCollection.GetByOffset(offset);
}
public LineSegment GetLineSegment(int lineNr)
{
return lineCollection[lineNr];
}
public void Insert(int offset, string text)
{
Replace(offset, 0, text);
}
public void Remove(int offset, int length)
{
Replace(offset, length, string.Empty);
}
public void Replace(int offset, int length, string text)
{
Debug.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length);
int lineStart = GetLineNumberForOffset(offset);
int oldNumberOfLines = this.TotalNumberOfLines;
DeferredEventList deferredEventList = new DeferredEventList();
RemoveInternal(ref deferredEventList, offset, length);
int numberOfLinesAfterRemoving = this.TotalNumberOfLines;
if (!string.IsNullOrEmpty(text)) {
InsertInternal(offset, text);
}
// #if DEBUG
// Console.WriteLine("New line collection:");
// Console.WriteLine(lineCollection.GetTreeAsString());
// Console.WriteLine("New text:");
// Console.WriteLine("'" + document.TextContent + "'");
// #endif
// Only fire events after RemoveInternal+InsertInternal finished completely:
// Otherwise we would expose inconsistent state to the event handlers.
RunHighlighter(lineStart, 1 + Math.Max(0, this.TotalNumberOfLines - numberOfLinesAfterRemoving));
if (deferredEventList.removedLines != null) {
foreach (LineSegment ls in deferredEventList.removedLines)
OnLineDeleted(new LineEventArgs(document, ls));
}
deferredEventList.RaiseEvents();
if (this.TotalNumberOfLines != oldNumberOfLines) {
OnLineCountChanged(new LineCountChangeEventArgs(document, lineStart, this.TotalNumberOfLines - oldNumberOfLines));
}
}
void RemoveInternal(ref DeferredEventList deferredEventList, int offset, int length)
{
Debug.Assert(length >= 0);
if (length == 0) return;
LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForOffset(offset);
LineSegment startSegment = it.Current;
int startSegmentOffset = startSegment.Offset;
if (offset + length < startSegmentOffset + startSegment.TotalLength) {
// just removing a part of this line segment
startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, length);
SetSegmentLength(startSegment, startSegment.TotalLength - length);
return;
}
// merge startSegment with another line segment because startSegment's delimiter was deleted
// possibly remove lines in between if multiple delimiters were deleted
int charactersRemovedInStartLine = startSegmentOffset + startSegment.TotalLength - offset;
Debug.Assert(charactersRemovedInStartLine > 0);
startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, charactersRemovedInStartLine);
LineSegment endSegment = lineCollection.GetByOffset(offset + length);
if (endSegment == startSegment) {
// special case: we are removing a part of the last line up to the
// end of the document
SetSegmentLength(startSegment, startSegment.TotalLength - length);
return;
}
int endSegmentOffset = endSegment.Offset;
int charactersLeftInEndLine = endSegmentOffset + endSegment.TotalLength - (offset + length);
endSegment.RemovedLinePart(ref deferredEventList, 0, endSegment.TotalLength - charactersLeftInEndLine);
startSegment.MergedWith(endSegment, offset - startSegmentOffset);
SetSegmentLength(startSegment, startSegment.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine);
startSegment.DelimiterLength = endSegment.DelimiterLength;
// remove all segments between startSegment (excl.) and endSegment (incl.)
it.MoveNext();
LineSegment segmentToRemove;
do {
segmentToRemove = it.Current;
it.MoveNext();
lineCollection.RemoveSegment(segmentToRemove);
segmentToRemove.Deleted(ref deferredEventList);
} while (segmentToRemove != endSegment);
}
void InsertInternal(int offset, string text)
{
LineSegment segment = lineCollection.GetByOffset(offset);
DelimiterSegment ds = NextDelimiter(text, 0);
if (ds == null) {
// no newline is being inserted, all text is inserted in a single line
segment.InsertedLinePart(offset - segment.Offset, text.Length);
SetSegmentLength(segment, segment.TotalLength + text.Length);
return;
}
LineSegment firstLine = segment;
firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset);
int lastDelimiterEnd = 0;
while (ds != null) {
// split line segment at line delimiter
int lineBreakOffset = offset + ds.Offset + ds.Length;
int segmentOffset = segment.Offset;
int lengthAfterInsertionPos = segmentOffset + segment.TotalLength - (offset + lastDelimiterEnd);
lineCollection.SetSegmentLength(segment, lineBreakOffset - segmentOffset);
LineSegment newSegment = lineCollection.InsertSegmentAfter(segment, lengthAfterInsertionPos);
segment.DelimiterLength = ds.Length;
segment = newSegment;
lastDelimiterEnd = ds.Offset + ds.Length;
ds = NextDelimiter(text, lastDelimiterEnd);
}
firstLine.SplitTo(segment);
// insert rest after last delimiter
if (lastDelimiterEnd != text.Length) {
segment.InsertedLinePart(0, text.Length - lastDelimiterEnd);
SetSegmentLength(segment, segment.TotalLength + text.Length - lastDelimiterEnd);
}
}
void SetSegmentLength(LineSegment segment, int newTotalLength)
{
int delta = newTotalLength - segment.TotalLength;
if (delta != 0) {
lineCollection.SetSegmentLength(segment, newTotalLength);
OnLineLengthChanged(new LineLengthChangeEventArgs(document, segment, delta));
}
}
void RunHighlighter(int firstLine, int lineCount)
{
if (highlightingStrategy != null) {
List<LineSegment> markLines = new List<LineSegment>();
LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForIndex(firstLine);
for (int i = 0; i < lineCount && it.IsValid; i++) {
markLines.Add(it.Current);
it.MoveNext();
}
highlightingStrategy.MarkTokens(document, markLines);
}
}
public void SetContent(string text)
{
lineCollection.Clear();
if (text != null) {
Replace(0, 0, text);
}
}
public int GetVisibleLine(int logicalLineNumber)
{
if (!document.TextEditorProperties.EnableFolding) {
return logicalLineNumber;
}
int visibleLine = 0;
int foldEnd = 0;
List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
foreach (FoldMarker fm in foldings) {
if (fm.StartLine >= logicalLineNumber) {
break;
}
if (fm.StartLine >= foldEnd) {
visibleLine += fm.StartLine - foldEnd;
if (fm.EndLine > logicalLineNumber) {
return visibleLine;
}
foldEnd = fm.EndLine;
}
}
// Debug.Assert(logicalLineNumber >= foldEnd);
visibleLine += logicalLineNumber - foldEnd;
return visibleLine;
}
public int GetFirstLogicalLine(int visibleLineNumber)
{
if (!document.TextEditorProperties.EnableFolding) {
return visibleLineNumber;
}
int v = 0;
int foldEnd = 0;
List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
foreach (FoldMarker fm in foldings) {
if (fm.StartLine >= foldEnd) {
if (v + fm.StartLine - foldEnd >= visibleLineNumber) {
break;
}
v += fm.StartLine - foldEnd;
foldEnd = fm.EndLine;
}
}
// help GC
foldings.Clear();
foldings = null;
return foldEnd + visibleLineNumber - v;
}
public int GetLastLogicalLine(int visibleLineNumber)
{
if (!document.TextEditorProperties.EnableFolding) {
return visibleLineNumber;
}
return GetFirstLogicalLine(visibleLineNumber + 1) - 1;
}
// TODO : speedup the next/prev visible line search
// HOW? : save the foldings in a sorted list and lookup the
// line numbers in this list
public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
{
int curLineNumber = lineNumber;
if (document.TextEditorProperties.EnableFolding) {
for (int i = 0; i < lineCount && curLineNumber < TotalNumberOfLines; ++i) {
++curLineNumber;
while (curLineNumber < TotalNumberOfLines && (curLineNumber >= lineCollection.Count || !document.FoldingManager.IsLineVisible(curLineNumber))) {
++curLineNumber;
}
}
} else {
curLineNumber += lineCount;
}
return Math.Min(TotalNumberOfLines - 1, curLineNumber);
}
public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
{
int curLineNumber = lineNumber;
if (document.TextEditorProperties.EnableFolding) {
for (int i = 0; i < lineCount; ++i) {
--curLineNumber;
while (curLineNumber >= 0 && !document.FoldingManager.IsLineVisible(curLineNumber)) {
--curLineNumber;
}
}
} else {
curLineNumber -= lineCount;
}
return Math.Max(0, curLineNumber);
}
// use always the same DelimiterSegment object for the NextDelimiter
DelimiterSegment delimiterSegment = new DelimiterSegment();
DelimiterSegment NextDelimiter(string text, int offset)
{
for (int i = offset; i < text.Length; i++) {
switch (text[i]) {
case '\r':
if (i + 1 < text.Length) {
if (text[i + 1] == '\n') {
delimiterSegment.Offset = i;
delimiterSegment.Length = 2;
return delimiterSegment;
}
}
#if DATACONSISTENCYTEST
Debug.Assert(false, "Found lone \\r, data consistency problems?");
#endif
goto case '\n';
case '\n':
delimiterSegment.Offset = i;
delimiterSegment.Length = 1;
return delimiterSegment;
}
}
return null;
}
void OnLineCountChanged(LineCountChangeEventArgs e)
{
if (LineCountChanged != null) {
LineCountChanged(this, e);
}
}
void OnLineLengthChanged(LineLengthChangeEventArgs e)
{
if (LineLengthChanged != null) {
LineLengthChanged(this, e);
}
}
void OnLineDeleted(LineEventArgs e)
{
if (LineDeleted != null) {
LineDeleted(this, e);
}
}
public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;
public event EventHandler<LineCountChangeEventArgs> LineCountChanged;
public event EventHandler<LineEventArgs> LineDeleted;
sealed class DelimiterSegment
{
internal int Offset;
internal int Length;
}
}
}

View File

@@ -0,0 +1,97 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public class LineCountChangeEventArgs : EventArgs
{
IDocument document;
int start;
int moved;
/// <returns>
/// always a valid Document which is related to the Event.
/// </returns>
public IDocument Document {
get {
return document;
}
}
/// <returns>
/// -1 if no offset was specified for this event
/// </returns>
public int LineStart {
get {
return start;
}
}
/// <returns>
/// -1 if no length was specified for this event
/// </returns>
public int LinesMoved {
get {
return moved;
}
}
public LineCountChangeEventArgs(IDocument document, int lineStart, int linesMoved)
{
this.document = document;
this.start = lineStart;
this.moved = linesMoved;
}
}
public class LineEventArgs : EventArgs
{
IDocument document;
LineSegment lineSegment;
public IDocument Document {
get { return document; }
}
public LineSegment LineSegment {
get { return lineSegment; }
}
public LineEventArgs(IDocument document, LineSegment lineSegment)
{
this.document = document;
this.lineSegment = lineSegment;
}
public override string ToString()
{
return string.Format("[LineEventArgs Document={0} LineSegment={1}]", this.document, this.lineSegment);
}
}
public class LineLengthChangeEventArgs : LineEventArgs
{
int lengthDelta;
public int LengthDelta {
get { return lengthDelta; }
}
public LineLengthChangeEventArgs(IDocument document, LineSegment lineSegment, int moved)
: base(document, lineSegment)
{
this.lengthDelta = moved;
}
public override string ToString()
{
return string.Format("[LineLengthEventArgs Document={0} LineSegment={1} LengthDelta={2}]", this.Document, this.LineSegment, this.lengthDelta);
}
}
}

View File

@@ -0,0 +1,259 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Diagnostics;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public sealed class LineSegment : ISegment
{
internal LineSegmentTree.Enumerator treeEntry;
int totalLength, delimiterLength;
List<TextWord> words;
SpanStack highlightSpanStack;
public TextWord GetWord(int column)
{
int curColumn = 0;
foreach (TextWord word in words) {
if (column < curColumn + word.Length) {
return word;
}
curColumn += word.Length;
}
return null;
}
public bool IsDeleted {
get { return !treeEntry.IsValid; }
}
public int LineNumber {
get { return treeEntry.CurrentIndex; }
}
public int Offset {
get { return treeEntry.CurrentOffset; }
}
public int Length {
get { return totalLength - delimiterLength; }
}
int ISegment.Offset {
get { return this.Offset; }
set { throw new NotSupportedException(); }
}
int ISegment.Length {
get { return this.Length; }
set { throw new NotSupportedException(); }
}
public int TotalLength {
get { return totalLength; }
internal set { totalLength = value; }
}
public int DelimiterLength {
get { return delimiterLength; }
internal set { delimiterLength = value; }
}
// highlighting information
public List<TextWord> Words {
get {
return words;
}
set {
words = value;
}
}
public HighlightColor GetColorForPosition(int x)
{
if (Words != null) {
int xPos = 0;
foreach (TextWord word in Words) {
if (x < xPos + word.Length) {
return word.SyntaxColor;
}
xPos += word.Length;
}
}
return new HighlightColor(Color.Black, false, false);
}
public SpanStack HighlightSpanStack {
get {
return highlightSpanStack;
}
set {
highlightSpanStack = value;
}
}
/// <summary>
/// Converts a <see cref="LineSegment"/> instance to string (for debug purposes)
/// </summary>
public override string ToString()
{
if (IsDeleted)
return "[LineSegment: (deleted) Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]";
else
return "[LineSegment: LineNumber=" + LineNumber + ", Offset = "+ Offset +", Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]";
}
#region Anchor management
Util.WeakCollection<TextAnchor> anchors;
public TextAnchor CreateAnchor(int column)
{
if (column < 0 || column > Length)
throw new ArgumentOutOfRangeException("column");
TextAnchor anchor = new TextAnchor(this, column);
AddAnchor(anchor);
return anchor;
}
void AddAnchor(TextAnchor anchor)
{
Debug.Assert(anchor.Line == this);
if (anchors == null)
anchors = new Util.WeakCollection<TextAnchor>();
anchors.Add(anchor);
}
/// <summary>
/// Is called when the LineSegment is deleted.
/// </summary>
internal void Deleted(ref DeferredEventList deferredEventList)
{
//Console.WriteLine("Deleted");
treeEntry = LineSegmentTree.Enumerator.Invalid;
if (anchors != null) {
foreach (TextAnchor a in anchors) {
a.Delete(ref deferredEventList);
}
anchors = null;
}
}
/// <summary>
/// Is called when a part of the line is removed.
/// </summary>
internal void RemovedLinePart(ref DeferredEventList deferredEventList, int startColumn, int length)
{
if (length == 0)
return;
Debug.Assert(length > 0);
//Console.WriteLine("RemovedLinePart " + startColumn + ", " + length);
if (anchors != null) {
List<TextAnchor> deletedAnchors = null;
foreach (TextAnchor a in anchors) {
if (a.ColumnNumber > startColumn) {
if (a.ColumnNumber >= startColumn + length) {
a.ColumnNumber -= length;
} else {
if (deletedAnchors == null)
deletedAnchors = new List<TextAnchor>();
a.Delete(ref deferredEventList);
deletedAnchors.Add(a);
}
}
}
if (deletedAnchors != null) {
foreach (TextAnchor a in deletedAnchors) {
anchors.Remove(a);
}
}
}
}
/// <summary>
/// Is called when a part of the line is inserted.
/// </summary>
internal void InsertedLinePart(int startColumn, int length)
{
if (length == 0)
return;
Debug.Assert(length > 0);
//Console.WriteLine("InsertedLinePart " + startColumn + ", " + length);
if (anchors != null) {
foreach (TextAnchor a in anchors) {
if (a.MovementType == AnchorMovementType.BeforeInsertion
? a.ColumnNumber > startColumn
: a.ColumnNumber >= startColumn)
{
a.ColumnNumber += length;
}
}
}
}
/// <summary>
/// Is called after another line's content is appended to this line because the newline in between
/// was deleted.
/// The DefaultLineManager will call Deleted() on the deletedLine after the MergedWith call.
///
/// firstLineLength: the length of the line before the merge.
/// </summary>
internal void MergedWith(LineSegment deletedLine, int firstLineLength)
{
//Console.WriteLine("MergedWith");
if (deletedLine.anchors != null) {
foreach (TextAnchor a in deletedLine.anchors) {
a.Line = this;
AddAnchor(a);
a.ColumnNumber += firstLineLength;
}
deletedLine.anchors = null;
}
}
/// <summary>
/// Is called after a newline was inserted into this line, splitting it into this and followingLine.
/// </summary>
internal void SplitTo(LineSegment followingLine)
{
//Console.WriteLine("SplitTo");
if (anchors != null) {
List<TextAnchor> movedAnchors = null;
foreach (TextAnchor a in anchors) {
if (a.MovementType == AnchorMovementType.BeforeInsertion
? a.ColumnNumber > this.Length
: a.ColumnNumber >= this.Length)
{
a.Line = followingLine;
followingLine.AddAnchor(a);
a.ColumnNumber -= this.Length;
if (movedAnchors == null)
movedAnchors = new List<TextAnchor>();
movedAnchors.Add(a);
}
}
if (movedAnchors != null) {
foreach (TextAnchor a in movedAnchors) {
anchors.Remove(a);
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,477 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Data structure for efficient management of the line segments (most operations are O(lg n)).
/// This implements an augmented red-black tree where each node has fields for the number of
/// nodes in its subtree (like an order statistics tree) for access by index(=line number).
/// Additionally, each node knows the total length of all segments in its subtree.
/// This means we can find nodes by offset in O(lg n) time. Since the offset itself is not stored in
/// the line segment but computed from the lengths stored in the tree, we adjusting the offsets when
/// text is inserted in one line means we just have to increment the totalLength of the affected line and
/// its parent nodes - an O(lg n) operation.
/// However this means getting the line number or offset from a LineSegment is not a constant time
/// operation, but takes O(lg n).
///
/// NOTE: The tree is never empty, Clear() causes it to contain an empty segment.
/// </summary>
sealed class LineSegmentTree : IList<LineSegment>
{
internal struct RBNode
{
internal LineSegment lineSegment;
internal int count;
internal int totalLength;
public RBNode(LineSegment lineSegment)
{
this.lineSegment = lineSegment;
this.count = 1;
this.totalLength = lineSegment.TotalLength;
}
public override string ToString()
{
return "[RBNode count=" + count + " totalLength="+totalLength
+ " lineSegment.LineNumber=" + lineSegment.LineNumber
+ " lineSegment.Offset=" + lineSegment.Offset
+ " lineSegment.TotalLength=" + lineSegment.TotalLength
+ " lineSegment.DelimiterLength=" + lineSegment.DelimiterLength + "]";
}
}
struct MyHost : IRedBlackTreeHost<RBNode>
{
public int Compare(RBNode x, RBNode y)
{
throw new NotImplementedException();
}
public bool Equals(RBNode a, RBNode b)
{
throw new NotImplementedException();
}
public void UpdateAfterChildrenChange(RedBlackTreeNode<RBNode> node)
{
int count = 1;
int totalLength = node.val.lineSegment.TotalLength;
if (node.left != null) {
count += node.left.val.count;
totalLength += node.left.val.totalLength;
}
if (node.right != null) {
count += node.right.val.count;
totalLength += node.right.val.totalLength;
}
if (count != node.val.count || totalLength != node.val.totalLength) {
node.val.count = count;
node.val.totalLength = totalLength;
if (node.parent != null) UpdateAfterChildrenChange(node.parent);
}
}
public void UpdateAfterRotateLeft(RedBlackTreeNode<RBNode> node)
{
UpdateAfterChildrenChange(node);
UpdateAfterChildrenChange(node.parent);
}
public void UpdateAfterRotateRight(RedBlackTreeNode<RBNode> node)
{
UpdateAfterChildrenChange(node);
UpdateAfterChildrenChange(node.parent);
}
}
readonly AugmentableRedBlackTree<RBNode, MyHost> tree = new AugmentableRedBlackTree<RBNode, MyHost>(new MyHost());
RedBlackTreeNode<RBNode> GetNode(int index)
{
if (index < 0 || index >= tree.Count)
throw new ArgumentOutOfRangeException("index", index, "index should be between 0 and " + (tree.Count-1));
RedBlackTreeNode<RBNode> node = tree.root;
while (true) {
if (node.left != null && index < node.left.val.count) {
node = node.left;
} else {
if (node.left != null) {
index -= node.left.val.count;
}
if (index == 0)
return node;
index--;
node = node.right;
}
}
}
static int GetIndexFromNode(RedBlackTreeNode<RBNode> node)
{
int index = (node.left != null) ? node.left.val.count : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
index += node.parent.left.val.count;
index++;
}
node = node.parent;
}
return index;
}
RedBlackTreeNode<RBNode> GetNodeByOffset(int offset)
{
if (offset < 0 || offset > this.TotalLength)
throw new ArgumentOutOfRangeException("offset", offset, "offset should be between 0 and " + this.TotalLength);
if (offset == this.TotalLength) {
if (tree.root == null)
throw new InvalidOperationException("Cannot call GetNodeByOffset while tree is empty.");
return tree.root.RightMost;
}
RedBlackTreeNode<RBNode> node = tree.root;
while (true) {
if (node.left != null && offset < node.left.val.totalLength) {
node = node.left;
} else {
if (node.left != null) {
offset -= node.left.val.totalLength;
}
offset -= node.val.lineSegment.TotalLength;
if (offset < 0)
return node;
node = node.right;
}
}
}
static int GetOffsetFromNode(RedBlackTreeNode<RBNode> node)
{
int offset = (node.left != null) ? node.left.val.totalLength : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
offset += node.parent.left.val.totalLength;
offset += node.parent.val.lineSegment.TotalLength;
}
node = node.parent;
}
return offset;
}
public LineSegment GetByOffset(int offset)
{
return GetNodeByOffset(offset).val.lineSegment;
}
/// <summary>
/// Gets the total length of all line segments. Runs in O(1).
/// </summary>
public int TotalLength {
get {
if (tree.root == null)
return 0;
else
return tree.root.val.totalLength;
}
}
/// <summary>
/// Updates the length of a line segment. Runs in O(lg n).
/// </summary>
public void SetSegmentLength(LineSegment segment, int newTotalLength)
{
if (segment == null)
throw new ArgumentNullException("segment");
RedBlackTreeNode<RBNode> node = segment.treeEntry.it.node;
segment.TotalLength = newTotalLength;
default(MyHost).UpdateAfterChildrenChange(node);
#if DEBUG
CheckProperties();
#endif
}
public void RemoveSegment(LineSegment segment)
{
tree.RemoveAt(segment.treeEntry.it);
#if DEBUG
CheckProperties();
#endif
}
public LineSegment InsertSegmentAfter(LineSegment segment, int length)
{
LineSegment newSegment = new LineSegment();
newSegment.TotalLength = length;
newSegment.DelimiterLength = segment.DelimiterLength;
newSegment.treeEntry = InsertAfter(segment.treeEntry.it.node, newSegment);
return newSegment;
}
Enumerator InsertAfter(RedBlackTreeNode<RBNode> node, LineSegment newSegment)
{
RedBlackTreeNode<RBNode> newNode = new RedBlackTreeNode<RBNode>(new RBNode(newSegment));
if (node.right == null) {
tree.InsertAsRight(node, newNode);
} else {
tree.InsertAsLeft(node.right.LeftMost, newNode);
}
#if DEBUG
CheckProperties();
#endif
return new Enumerator(new RedBlackTreeIterator<RBNode>(newNode));
}
/// <summary>
/// Gets the number of items in the collections. Runs in O(1).
/// </summary>
public int Count {
get { return tree.Count; }
}
/// <summary>
/// Gets or sets an item by index. Runs in O(lg n).
/// </summary>
public LineSegment this[int index] {
get {
return GetNode(index).val.lineSegment;
}
set {
throw new NotSupportedException();
}
}
bool ICollection<LineSegment>.IsReadOnly {
get { return true; }
}
/// <summary>
/// Gets the index of an item. Runs in O(lg n).
/// </summary>
public int IndexOf(LineSegment item)
{
int index = item.LineNumber;
if (index < 0 || index >= this.Count)
return -1;
if (item != this[index])
return -1;
return index;
}
void IList<LineSegment>.RemoveAt(int index)
{
throw new NotSupportedException();
}
#if DEBUG
[Conditional("DATACONSISTENCYTEST")]
void CheckProperties()
{
if (tree.root == null) {
Debug.Assert(this.Count == 0);
} else {
Debug.Assert(tree.root.val.count == this.Count);
CheckProperties(tree.root);
}
}
void CheckProperties(RedBlackTreeNode<RBNode> node)
{
int count = 1;
int totalLength = node.val.lineSegment.TotalLength;
if (node.left != null) {
CheckProperties(node.left);
count += node.left.val.count;
totalLength += node.left.val.totalLength;
}
if (node.right != null) {
CheckProperties(node.right);
count += node.right.val.count;
totalLength += node.right.val.totalLength;
}
Debug.Assert(node.val.count == count);
Debug.Assert(node.val.totalLength == totalLength);
}
public string GetTreeAsString()
{
return tree.GetTreeAsString();
}
#endif
public LineSegmentTree()
{
Clear();
}
/// <summary>
/// Clears the list. Runs in O(1).
/// </summary>
public void Clear()
{
tree.Clear();
LineSegment emptySegment = new LineSegment();
emptySegment.TotalLength = 0;
emptySegment.DelimiterLength = 0;
tree.Add(new RBNode(emptySegment));
emptySegment.treeEntry = GetEnumeratorForIndex(0);
#if DEBUG
CheckProperties();
#endif
}
/// <summary>
/// Tests whether an item is in the list. Runs in O(n).
/// </summary>
public bool Contains(LineSegment item)
{
return IndexOf(item) >= 0;
}
/// <summary>
/// Copies all elements from the list to the array.
/// </summary>
public void CopyTo(LineSegment[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException("array");
foreach (LineSegment val in this)
array[arrayIndex++] = val;
}
IEnumerator<LineSegment> IEnumerable<LineSegment>.GetEnumerator()
{
return this.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public Enumerator GetEnumerator()
{
return new Enumerator(tree.GetEnumerator());
}
public Enumerator GetEnumeratorForIndex(int index)
{
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNode(index)));
}
public Enumerator GetEnumeratorForOffset(int offset)
{
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNodeByOffset(offset)));
}
public struct Enumerator : IEnumerator<LineSegment>
{
/// <summary>
/// An invalid enumerator value. Calling MoveNext on the invalid enumerator
/// will always return false, accessing Current will throw an exception.
/// </summary>
public static readonly Enumerator Invalid = default(Enumerator);
internal RedBlackTreeIterator<RBNode> it;
internal Enumerator(RedBlackTreeIterator<RBNode> it)
{
this.it = it;
}
/// <summary>
/// Gets the current value. Runs in O(1).
/// </summary>
public LineSegment Current {
get {
return it.Current.lineSegment;
}
}
public bool IsValid {
get {
return it.IsValid;
}
}
/// <summary>
/// Gets the index of the current value. Runs in O(lg n).
/// </summary>
public int CurrentIndex {
get {
if (it.node == null)
throw new InvalidOperationException();
return GetIndexFromNode(it.node);
}
}
/// <summary>
/// Gets the offset of the current value. Runs in O(lg n).
/// </summary>
public int CurrentOffset {
get {
if (it.node == null)
throw new InvalidOperationException();
return GetOffsetFromNode(it.node);
}
}
object System.Collections.IEnumerator.Current {
get {
return it.Current.lineSegment;
}
}
public void Dispose()
{
}
/// <summary>
/// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
/// </summary>
public bool MoveNext()
{
return it.MoveNext();
}
/// <summary>
/// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
/// </summary>
public bool MoveBack()
{
return it.MoveBack();
}
void System.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
void IList<LineSegment>.Insert(int index, LineSegment item)
{
throw new NotSupportedException();
}
void ICollection<LineSegment>.Add(LineSegment item)
{
throw new NotSupportedException();
}
bool ICollection<LineSegment>.Remove(LineSegment item)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,119 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Manages the list of markers and provides ways to retrieve markers for specific positions.
/// </summary>
public sealed class MarkerStrategy
{
List<TextMarker> textMarker = new List<TextMarker>();
IDocument document;
public IDocument Document {
get {
return document;
}
}
public IEnumerable<TextMarker> TextMarker {
get {
return textMarker.AsReadOnly();
}
}
public void AddMarker(TextMarker item)
{
markersTable.Clear();
textMarker.Add(item);
}
public void InsertMarker(int index, TextMarker item)
{
markersTable.Clear();
textMarker.Insert(index, item);
}
public void RemoveMarker(TextMarker item)
{
markersTable.Clear();
textMarker.Remove(item);
}
public void RemoveAll(Predicate<TextMarker> match)
{
markersTable.Clear();
textMarker.RemoveAll(match);
}
public MarkerStrategy(IDocument document)
{
this.document = document;
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
}
Dictionary<int, List<TextMarker>> markersTable = new Dictionary<int, List<TextMarker>>();
public List<TextMarker> GetMarkers(int offset)
{
if (!markersTable.ContainsKey(offset)) {
List<TextMarker> markers = new List<TextMarker>();
for (int i = 0; i < textMarker.Count; ++i) {
TextMarker marker = (TextMarker)textMarker[i];
if (marker.Offset <= offset && offset <= marker.EndOffset) {
markers.Add(marker);
}
}
markersTable[offset] = markers;
}
return markersTable[offset];
}
public List<TextMarker> GetMarkers(int offset, int length)
{
int endOffset = offset + length - 1;
List<TextMarker> markers = new List<TextMarker>();
for (int i = 0; i < textMarker.Count; ++i) {
TextMarker marker = (TextMarker)textMarker[i];
if (// start in marker region
marker.Offset <= offset && offset <= marker.EndOffset ||
// end in marker region
marker.Offset <= endOffset && endOffset <= marker.EndOffset ||
// marker start in region
offset <= marker.Offset && marker.Offset <= endOffset ||
// marker end in region
offset <= marker.EndOffset && marker.EndOffset <= endOffset
)
{
markers.Add(marker);
}
}
return markers;
}
public List<TextMarker> GetMarkers(TextLocation position)
{
if (position.Y >= document.TotalNumberOfLines || position.Y < 0) {
return new List<TextMarker>();
}
LineSegment segment = document.GetLineSegment(position.Y);
return GetMarkers(segment.Offset + position.X);
}
void DocumentChanged(object sender, DocumentEventArgs e)
{
// reset markers table
markersTable.Clear();
document.UpdateSegmentListOnDocumentChange(textMarker, e);
}
}
}

View File

@@ -0,0 +1,103 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
namespace ICSharpCode.TextEditor.Document
{
public enum TextMarkerType
{
Invisible,
SolidBlock,
Underlined,
WaveLine
}
/// <summary>
/// Marks a part of a document.
/// </summary>
public class TextMarker : AbstractSegment
{
TextMarkerType textMarkerType;
Color color;
Color foreColor;
string toolTip = null;
bool overrideForeColor = false;
public TextMarkerType TextMarkerType {
get {
return textMarkerType;
}
}
public Color Color {
get {
return color;
}
}
public Color ForeColor {
get {
return foreColor;
}
}
public bool OverrideForeColor {
get {
return overrideForeColor;
}
}
/// <summary>
/// Marks the text segment as read-only.
/// </summary>
public bool IsReadOnly { get; set; }
public string ToolTip {
get {
return toolTip;
}
set {
toolTip = value;
}
}
/// <summary>
/// Gets the last offset that is inside the marker region.
/// </summary>
public int EndOffset {
get {
return Offset + Length - 1;
}
}
public TextMarker(int offset, int length, TextMarkerType textMarkerType) : this(offset, length, textMarkerType, Color.Red)
{
}
public TextMarker(int offset, int length, TextMarkerType textMarkerType, Color color)
{
if (length < 1) length = 1;
this.offset = offset;
this.length = length;
this.textMarkerType = textMarkerType;
this.color = color;
}
public TextMarker(int offset, int length, TextMarkerType textMarkerType, Color color, Color foreColor)
{
if (length < 1) length = 1;
this.offset = offset;
this.length = length;
this.textMarkerType = textMarkerType;
this.color = color;
this.foreColor = foreColor;
this.overrideForeColor = true;
}
}
}

View File

@@ -0,0 +1,66 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public class ColumnRange
{
public static readonly ColumnRange NoColumn = new ColumnRange(-2, -2);
public static readonly ColumnRange WholeColumn = new ColumnRange(-1, -1);
int startColumn;
int endColumn;
public int StartColumn {
get {
return startColumn;
}
set {
startColumn = value;
}
}
public int EndColumn {
get {
return endColumn;
}
set {
endColumn = value;
}
}
public ColumnRange(int startColumn, int endColumn)
{
this.startColumn = startColumn;
this.endColumn = endColumn;
}
public override int GetHashCode()
{
return startColumn + (endColumn << 16);
}
public override bool Equals(object obj)
{
if (obj is ColumnRange) {
return ((ColumnRange)obj).startColumn == startColumn &&
((ColumnRange)obj).endColumn == endColumn;
}
return false;
}
public override string ToString()
{
return string.Format("[ColumnRange: StartColumn={0}, EndColumn={1}]", startColumn, endColumn);
}
}
}

View File

@@ -0,0 +1,133 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Drawing;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Default implementation of the <see cref="ICSharpCode.TextEditor.Document.ISelection"/> interface.
/// </summary>
public class DefaultSelection : ISelection
{
IDocument document;
bool isRectangularSelection;
TextLocation startPosition;
TextLocation endPosition;
public TextLocation StartPosition {
get {
return startPosition;
}
set {
DefaultDocument.ValidatePosition(document, value);
startPosition = value;
}
}
public TextLocation EndPosition {
get {
return endPosition;
}
set {
DefaultDocument.ValidatePosition(document, value);
endPosition = value;
}
}
public int Offset {
get {
return document.PositionToOffset(startPosition);
}
}
public int EndOffset {
get {
return document.PositionToOffset(endPosition);
}
}
public int Length {
get {
return EndOffset - Offset;
}
}
/// <value>
/// Returns true, if the selection is empty
/// </value>
public bool IsEmpty {
get {
return startPosition == endPosition;
}
}
/// <value>
/// Returns true, if the selection is rectangular
/// </value>
// TODO : make this unused property used.
public bool IsRectangularSelection {
get {
return isRectangularSelection;
}
set {
isRectangularSelection = value;
}
}
/// <value>
/// The text which is selected by this selection.
/// </value>
public string SelectedText {
get {
if (document != null) {
if (Length < 0) {
return null;
}
return document.GetText(Offset, Length);
}
return null;
}
}
/// <summary>
/// Creates a new instance of <see cref="DefaultSelection"/>
/// </summary>
public DefaultSelection(IDocument document, TextLocation startPosition, TextLocation endPosition)
{
DefaultDocument.ValidatePosition(document, startPosition);
DefaultDocument.ValidatePosition(document, endPosition);
Debug.Assert(startPosition <= endPosition);
this.document = document;
this.startPosition = startPosition;
this.endPosition = endPosition;
}
/// <summary>
/// Converts a <see cref="DefaultSelection"/> instance to string (for debug purposes)
/// </summary>
public override string ToString()
{
return string.Format("[DefaultSelection : StartPosition={0}, EndPosition={1}]", startPosition, endPosition);
}
public bool ContainsPosition(TextLocation position)
{
if (this.IsEmpty)
return false;
return startPosition.Y < position.Y && position.Y < endPosition.Y ||
startPosition.Y == position.Y && startPosition.X <= position.X && (startPosition.Y != endPosition.Y || position.X <= endPosition.X) ||
endPosition.Y == position.Y && startPosition.Y != endPosition.Y && position.X <= endPosition.X;
}
public bool ContainsOffset(int offset)
{
return Offset <= offset && offset <= EndOffset;
}
}
}

View File

@@ -0,0 +1,64 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System.Drawing;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// An interface representing a portion of the current selection.
/// </summary>
public interface ISelection
{
TextLocation StartPosition {
get;
set;
}
TextLocation EndPosition {
get;
set;
}
int Offset {
get;
}
int EndOffset {
get;
}
int Length {
get;
}
/// <value>
/// Returns true, if the selection is rectangular
/// </value>
bool IsRectangularSelection {
get;
}
/// <value>
/// Returns true, if the selection is empty
/// </value>
bool IsEmpty {
get;
}
/// <value>
/// The text which is selected by this selection.
/// </value>
string SelectedText {
get;
}
bool ContainsOffset(int offset);
bool ContainsPosition(TextLocation position);
}
}

View File

@@ -0,0 +1,466 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This class manages the selections in a document.
/// </summary>
public class SelectionManager : IDisposable
{
TextLocation selectionStart;
internal TextLocation SelectionStart {
get { return selectionStart; }
set {
DefaultDocument.ValidatePosition(document, value);
selectionStart = value;
}
}
IDocument document;
TextArea textArea;
internal SelectFrom selectFrom = new SelectFrom();
internal List<ISelection> selectionCollection = new List<ISelection>();
/// <value>
/// A collection containing all selections.
/// </value>
public List<ISelection> SelectionCollection {
get {
return selectionCollection;
}
}
/// <value>
/// true if the <see cref="SelectionCollection"/> is not empty, false otherwise.
/// </value>
public bool HasSomethingSelected {
get {
return selectionCollection.Count > 0;
}
}
public bool SelectionIsReadonly {
get {
if (document.ReadOnly)
return true;
foreach (ISelection sel in selectionCollection) {
if (SelectionIsReadOnly(document, sel))
return true;
}
return false;
}
}
internal static bool SelectionIsReadOnly(IDocument document, ISelection sel)
{
if (document.TextEditorProperties.SupportReadOnlySegments)
return document.MarkerStrategy.GetMarkers(sel.Offset, sel.Length).Exists(m=>m.IsReadOnly);
else
return false;
}
/// <value>
/// The text that is currently selected.
/// </value>
public string SelectedText {
get {
StringBuilder builder = new StringBuilder();
// PriorityQueue queue = new PriorityQueue();
foreach (ISelection s in selectionCollection) {
builder.Append(s.SelectedText);
// queue.Insert(-s.Offset, s);
}
// while (queue.Count > 0) {
// ISelection s = ((ISelection)queue.Remove());
// builder.Append(s.SelectedText);
// }
return builder.ToString();
}
}
/// <summary>
/// Creates a new instance of <see cref="SelectionManager"/>
/// </summary>
public SelectionManager(IDocument document)
{
this.document = document;
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
}
/// <summary>
/// Creates a new instance of <see cref="SelectionManager"/>
/// </summary>
public SelectionManager(IDocument document, TextArea textArea)
{
this.document = document;
this.textArea = textArea;
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
}
public void Dispose()
{
if (this.document != null) {
document.DocumentChanged -= new DocumentEventHandler(DocumentChanged);
this.document = null;
}
}
void DocumentChanged(object sender, DocumentEventArgs e)
{
if (e.Text == null) {
Remove(e.Offset, e.Length);
} else {
if (e.Length < 0) {
Insert(e.Offset, e.Text);
} else {
Replace(e.Offset, e.Length, e.Text);
}
}
}
/// <remarks>
/// Clears the selection and sets a new selection
/// using the given <see cref="ISelection"/> object.
/// </remarks>
public void SetSelection(ISelection selection)
{
// autoClearSelection = false;
if (selection != null) {
if (SelectionCollection.Count == 1 &&
selection.StartPosition == SelectionCollection[0].StartPosition &&
selection.EndPosition == SelectionCollection[0].EndPosition ) {
return;
}
ClearWithoutUpdate();
selectionCollection.Add(selection);
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
document.CommitUpdate();
OnSelectionChanged(EventArgs.Empty);
} else {
ClearSelection();
}
}
public void SetSelection(TextLocation startPosition, TextLocation endPosition)
{
SetSelection(new DefaultSelection(document, startPosition, endPosition));
}
public bool GreaterEqPos(TextLocation p1, TextLocation p2)
{
return p1.Y > p2.Y || p1.Y == p2.Y && p1.X >= p2.X;
}
public void ExtendSelection(TextLocation oldPosition, TextLocation newPosition)
{
// where oldposition is where the cursor was,
// and newposition is where it has ended up from a click (both zero based)
if (oldPosition == newPosition)
{
return;
}
TextLocation min;
TextLocation max;
int oldnewX = newPosition.X;
bool oldIsGreater = GreaterEqPos(oldPosition, newPosition);
if (oldIsGreater) {
min = newPosition;
max = oldPosition;
} else {
min = oldPosition;
max = newPosition;
}
if (min == max) {
return;
}
if (!HasSomethingSelected)
{
SetSelection(new DefaultSelection(document, min, max));
// initialise selectFrom for a cursor selection
if (selectFrom.where == WhereFrom.None)
SelectionStart = oldPosition; //textArea.Caret.Position;
return;
}
ISelection selection = this.selectionCollection[0];
if (min == max) {
//selection.StartPosition = newPosition;
return;
} else {
// changed selection via gutter
if (selectFrom.where == WhereFrom.Gutter)
{
// selection new position is always at the left edge for gutter selections
newPosition.X = 0;
}
if (GreaterEqPos(newPosition, SelectionStart)) // selecting forward
{
selection.StartPosition = SelectionStart;
// this handles last line selection
if (selectFrom.where == WhereFrom.Gutter ) //&& newPosition.Y != oldPosition.Y)
selection.EndPosition = new TextLocation(textArea.Caret.Column, textArea.Caret.Line);
else {
newPosition.X = oldnewX;
selection.EndPosition = newPosition;
}
} else { // selecting back
if (selectFrom.where == WhereFrom.Gutter && selectFrom.first == WhereFrom.Gutter)
{ // gutter selection
selection.EndPosition = NextValidPosition(SelectionStart.Y);
} else { // internal text selection
selection.EndPosition = SelectionStart; //selection.StartPosition;
}
selection.StartPosition = newPosition;
}
}
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, min.Y, max.Y));
document.CommitUpdate();
OnSelectionChanged(EventArgs.Empty);
}
// retrieve the next available line
// - checks that there are more lines available after the current one
// - if there are then the next line is returned
// - if there are NOT then the last position on the given line is returned
public TextLocation NextValidPosition(int line)
{
if (line < document.TotalNumberOfLines - 1)
return new TextLocation(0, line + 1);
else
return new TextLocation(document.GetLineSegment(document.TotalNumberOfLines - 1).Length + 1, line);
}
void ClearWithoutUpdate()
{
while (selectionCollection.Count > 0) {
ISelection selection = selectionCollection[selectionCollection.Count - 1];
selectionCollection.RemoveAt(selectionCollection.Count - 1);
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
OnSelectionChanged(EventArgs.Empty);
}
}
/// <remarks>
/// Clears the selection.
/// </remarks>
public void ClearSelection()
{
Point mousepos;
mousepos = textArea.mousepos;
// this is the most logical place to reset selection starting
// positions because it is always called before a new selection
selectFrom.first = selectFrom.where;
TextLocation newSelectionStart = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (selectFrom.where == WhereFrom.Gutter) {
newSelectionStart.X = 0;
// selectionStart.Y = -1;
}
if (newSelectionStart.Line >= document.TotalNumberOfLines) {
newSelectionStart.Line = document.TotalNumberOfLines-1;
newSelectionStart.Column = document.GetLineSegment(document.TotalNumberOfLines-1).Length;
}
this.SelectionStart = newSelectionStart;
ClearWithoutUpdate();
document.CommitUpdate();
}
/// <remarks>
/// Removes the selected text from the buffer and clears
/// the selection.
/// </remarks>
public void RemoveSelectedText()
{
if (SelectionIsReadonly) {
ClearSelection();
return;
}
List<int> lines = new List<int>();
int offset = -1;
bool oneLine = true;
// PriorityQueue queue = new PriorityQueue();
foreach (ISelection s in selectionCollection) {
// ISelection s = ((ISelection)queue.Remove());
if (oneLine) {
int lineBegin = s.StartPosition.Y;
if (lineBegin != s.EndPosition.Y) {
oneLine = false;
} else {
lines.Add(lineBegin);
}
}
offset = s.Offset;
document.Remove(s.Offset, s.Length);
// queue.Insert(-s.Offset, s);
}
ClearSelection();
if (offset >= 0) {
// TODO:
// document.Caret.Offset = offset;
}
if (offset != -1) {
if (oneLine) {
foreach (int i in lines) {
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, i));
}
} else {
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
}
document.CommitUpdate();
}
}
bool SelectionsOverlap(ISelection s1, ISelection s2)
{
return (s1.Offset <= s2.Offset && s2.Offset <= s1.Offset + s1.Length) ||
(s1.Offset <= s2.Offset + s2.Length && s2.Offset + s2.Length <= s1.Offset + s1.Length) ||
(s1.Offset >= s2.Offset && s1.Offset + s1.Length <= s2.Offset + s2.Length);
}
/// <remarks>
/// Returns true if the given offset points to a section which is
/// selected.
/// </remarks>
public bool IsSelected(int offset)
{
return GetSelectionAt(offset) != null;
}
/// <remarks>
/// Returns a <see cref="ISelection"/> object giving the selection in which
/// the offset points to.
/// </remarks>
/// <returns>
/// <code>null</code> if the offset doesn't point to a selection
/// </returns>
public ISelection GetSelectionAt(int offset)
{
foreach (ISelection s in selectionCollection) {
if (s.ContainsOffset(offset)) {
return s;
}
}
return null;
}
/// <remarks>
/// Used internally, do not call.
/// </remarks>
internal void Insert(int offset, string text)
{
// foreach (ISelection selection in SelectionCollection) {
// if (selection.Offset > offset) {
// selection.Offset += text.Length;
// } else if (selection.Offset + selection.Length > offset) {
// selection.Length += text.Length;
// }
// }
}
/// <remarks>
/// Used internally, do not call.
/// </remarks>
internal void Remove(int offset, int length)
{
// foreach (ISelection selection in selectionCollection) {
// if (selection.Offset > offset) {
// selection.Offset -= length;
// } else if (selection.Offset + selection.Length > offset) {
// selection.Length -= length;
// }
// }
}
/// <remarks>
/// Used internally, do not call.
/// </remarks>
internal void Replace(int offset, int length, string text)
{
// foreach (ISelection selection in selectionCollection) {
// if (selection.Offset > offset) {
// selection.Offset = selection.Offset - length + text.Length;
// } else if (selection.Offset + selection.Length > offset) {
// selection.Length = selection.Length - length + text.Length;
// }
// }
}
public ColumnRange GetSelectionAtLine(int lineNumber)
{
foreach (ISelection selection in selectionCollection) {
int startLine = selection.StartPosition.Y;
int endLine = selection.EndPosition.Y;
if (startLine < lineNumber && lineNumber < endLine) {
return ColumnRange.WholeColumn;
}
if (startLine == lineNumber) {
LineSegment line = document.GetLineSegment(startLine);
int startColumn = selection.StartPosition.X;
int endColumn = endLine == lineNumber ? selection.EndPosition.X : line.Length + 1;
return new ColumnRange(startColumn, endColumn);
}
if (endLine == lineNumber) {
int endColumn = selection.EndPosition.X;
return new ColumnRange(0, endColumn);
}
}
return ColumnRange.NoColumn;
}
public void FireSelectionChanged()
{
OnSelectionChanged(EventArgs.Empty);
}
protected virtual void OnSelectionChanged(EventArgs e)
{
if (SelectionChanged != null) {
SelectionChanged(this, e);
}
}
public event EventHandler SelectionChanged;
}
// selection initiated from...
internal class SelectFrom {
public int where = WhereFrom.None; // last selection initiator
public int first = WhereFrom.None; // first selection initiator
public SelectFrom()
{
}
}
// selection initiated from type...
internal class WhereFrom {
public const int None = 0;
public const int Gutter = 1;
public const int TArea = 2;
}
}

View File

@@ -0,0 +1,118 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public enum AnchorMovementType
{
/// <summary>
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay
/// before the inserted text.
/// </summary>
BeforeInsertion,
/// <summary>
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move
/// after the inserted text.
/// </summary>
AfterInsertion
}
/// <summary>
/// An anchor that can be put into a document and moves around when the document is changed.
/// </summary>
public sealed class TextAnchor
{
static Exception AnchorDeletedError()
{
return new InvalidOperationException("The text containing the anchor was deleted");
}
LineSegment lineSegment;
int columnNumber;
public LineSegment Line {
get {
if (lineSegment == null) throw AnchorDeletedError();
return lineSegment;
}
internal set {
lineSegment = value;
}
}
public bool IsDeleted {
get {
return lineSegment == null;
}
}
public int LineNumber {
get {
return this.Line.LineNumber;
}
}
public int ColumnNumber {
get {
if (lineSegment == null) throw AnchorDeletedError();
return columnNumber;
}
internal set {
columnNumber = value;
}
}
public TextLocation Location {
get {
return new TextLocation(this.ColumnNumber, this.LineNumber);
}
}
public int Offset {
get {
return this.Line.Offset + columnNumber;
}
}
/// <summary>
/// Controls how the anchor moves.
/// </summary>
public AnchorMovementType MovementType { get; set; }
public event EventHandler Deleted;
internal void Delete(ref DeferredEventList deferredEventList)
{
// we cannot fire an event here because this method is called while the LineManager adjusts the
// lineCollection, so an event handler could see inconsistent state
lineSegment = null;
deferredEventList.AddDeletedAnchor(this);
}
internal void RaiseDeleted()
{
if (Deleted != null)
Deleted(this, EventArgs.Empty);
}
internal TextAnchor(LineSegment lineSegment, int columnNumber)
{
this.lineSegment = lineSegment;
this.columnNumber = columnNumber;
}
public override string ToString()
{
if (this.IsDeleted)
return "[TextAnchor (deleted)]";
else
return "[TextAnchor " + this.Location.ToString() + "]";
}
}
}

View File

@@ -0,0 +1,194 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public class GapTextBufferStrategy : ITextBufferStrategy
{
#if DEBUG
int creatorThread = System.Threading.Thread.CurrentThread.ManagedThreadId;
void CheckThread()
{
if (System.Threading.Thread.CurrentThread.ManagedThreadId != creatorThread)
throw new InvalidOperationException("GapTextBufferStategy is not thread-safe!");
}
#endif
char[] buffer = new char[0];
string cachedContent;
int gapBeginOffset = 0;
int gapEndOffset = 0;
int gapLength = 0; // gapLength == gapEndOffset - gapBeginOffset
const int minGapLength = 128;
const int maxGapLength = 2048;
public int Length {
get {
return buffer.Length - gapLength;
}
}
public void SetContent(string text)
{
if (text == null) {
text = string.Empty;
}
cachedContent = text;
buffer = text.ToCharArray();
gapBeginOffset = gapEndOffset = gapLength = 0;
}
public char GetCharAt(int offset)
{
#if DEBUG
CheckThread();
#endif
if (offset < 0 || offset >= Length) {
throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset < " + Length.ToString());
}
return offset < gapBeginOffset ? buffer[offset] : buffer[offset + gapLength];
}
public string GetText(int offset, int length)
{
#if DEBUG
CheckThread();
#endif
if (offset < 0 || offset > Length) {
throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + Length.ToString());
}
if (length < 0 || offset + length > Length) {
throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + Length.ToString());
}
if (offset == 0 && length == Length) {
if (cachedContent != null)
return cachedContent;
else
return cachedContent = GetTextInternal(offset, length);
} else {
return GetTextInternal(offset, length);
}
}
string GetTextInternal(int offset, int length)
{
int end = offset + length;
if (end < gapBeginOffset) {
return new string(buffer, offset, length);
}
if (offset > gapBeginOffset) {
return new string(buffer, offset + gapLength, length);
}
int block1Size = gapBeginOffset - offset;
int block2Size = end - gapBeginOffset;
StringBuilder buf = new StringBuilder(block1Size + block2Size);
buf.Append(buffer, offset, block1Size);
buf.Append(buffer, gapEndOffset, block2Size);
return buf.ToString();
}
public void Insert(int offset, string text)
{
Replace(offset, 0, text);
}
public void Remove(int offset, int length)
{
Replace(offset, length, string.Empty);
}
public void Replace(int offset, int length, string text)
{
if (text == null) {
text = string.Empty;
}
#if DEBUG
CheckThread();
#endif
if (offset < 0 || offset > Length) {
throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + Length.ToString());
}
if (length < 0 || offset + length > Length) {
throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset+length <= " + Length.ToString());
}
cachedContent = null;
// Math.Max is used so that if we need to resize the array
// the new array has enough space for all old chars
PlaceGap(offset, text.Length - length);
gapEndOffset += length; // delete removed text
text.CopyTo(0, buffer, gapBeginOffset, text.Length);
gapBeginOffset += text.Length;
gapLength = gapEndOffset - gapBeginOffset;
if (gapLength > maxGapLength) {
MakeNewBuffer(gapBeginOffset, minGapLength);
}
}
void PlaceGap(int newGapOffset, int minRequiredGapLength)
{
if (gapLength < minRequiredGapLength) {
// enlarge gap
MakeNewBuffer(newGapOffset, minRequiredGapLength);
} else {
while (newGapOffset < gapBeginOffset) {
buffer[--gapEndOffset] = buffer[--gapBeginOffset];
}
while (newGapOffset > gapBeginOffset) {
buffer[gapBeginOffset++] = buffer[gapEndOffset++];
}
}
}
void MakeNewBuffer(int newGapOffset, int newGapLength)
{
if (newGapLength < minGapLength) newGapLength = minGapLength;
char[] newBuffer = new char[Length + newGapLength];
if (newGapOffset < gapBeginOffset) {
// gap is moving backwards
// first part:
Array.Copy(buffer, 0, newBuffer, 0, newGapOffset);
// moving middle part:
Array.Copy(buffer, newGapOffset, newBuffer, newGapOffset + newGapLength, gapBeginOffset - newGapOffset);
// last part:
Array.Copy(buffer, gapEndOffset, newBuffer, newBuffer.Length - (buffer.Length - gapEndOffset), buffer.Length - gapEndOffset);
} else {
// gap is moving forwards
// first part:
Array.Copy(buffer, 0, newBuffer, 0, gapBeginOffset);
// moving middle part:
Array.Copy(buffer, gapEndOffset, newBuffer, gapBeginOffset, newGapOffset - gapBeginOffset);
// last part:
int lastPartLength = newBuffer.Length - (newGapOffset + newGapLength);
Array.Copy(buffer, buffer.Length - lastPartLength, newBuffer, newGapOffset + newGapLength, lastPartLength);
}
gapBeginOffset = newGapOffset;
gapEndOffset = newGapOffset + newGapLength;
gapLength = newGapLength;
buffer = newBuffer;
}
}
}

View File

@@ -0,0 +1,85 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Interface to describe a sequence of characters that can be edited.
/// </summary>
public interface ITextBufferStrategy
{
/// <value>
/// The current length of the sequence of characters that can be edited.
/// </value>
int Length {
get;
}
/// <summary>
/// Inserts a string of characters into the sequence.
/// </summary>
/// <param name="offset">
/// offset where to insert the string.
/// </param>
/// <param name="text">
/// text to be inserted.
/// </param>
void Insert(int offset, string text);
/// <summary>
/// Removes some portion of the sequence.
/// </summary>
/// <param name="offset">
/// offset of the remove.
/// </param>
/// <param name="length">
/// number of characters to remove.
/// </param>
void Remove(int offset, int length);
/// <summary>
/// Replace some portion of the sequence.
/// </summary>
/// <param name="offset">
/// offset.
/// </param>
/// <param name="length">
/// number of characters to replace.
/// </param>
/// <param name="text">
/// text to be replaced with.
/// </param>
void Replace(int offset, int length, string text);
/// <summary>
/// Fetches a string of characters contained in the sequence.
/// </summary>
/// <param name="offset">
/// Offset into the sequence to fetch
/// </param>
/// <param name="length">
/// number of characters to copy.
/// </param>
string GetText(int offset, int length);
/// <summary>
/// Returns a specific char of the sequence.
/// </summary>
/// <param name="offset">
/// Offset of the char to get.
/// </param>
char GetCharAt(int offset);
/// <summary>
/// This method sets the stored content.
/// </summary>
/// <param name="text">
/// The string that represents the character sequence.
/// </param>
void SetContent(string text);
}
}

View File

@@ -0,0 +1,85 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.IO;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// Simple implementation of the ITextBuffer interface implemented using a
/// string.
/// Only for fall-back purposes.
/// </summary>
public class StringTextBufferStrategy : ITextBufferStrategy
{
string storedText = "";
public int Length {
get {
return storedText.Length;
}
}
public void Insert(int offset, string text)
{
if (text != null) {
storedText = storedText.Insert(offset, text);
}
}
public void Remove(int offset, int length)
{
storedText = storedText.Remove(offset, length);
}
public void Replace(int offset, int length, string text)
{
Remove(offset, length);
Insert(offset, text);
}
public string GetText(int offset, int length)
{
if (length == 0) {
return "";
}
if (offset == 0 && length >= storedText.Length) {
return storedText;
}
return storedText.Substring(offset, Math.Min(length, storedText.Length - offset));
}
public char GetCharAt(int offset)
{
if (offset == Length) {
return '\0';
}
return storedText[offset];
}
public void SetContent(string text)
{
storedText = text;
}
public StringTextBufferStrategy()
{
}
public static ITextBufferStrategy CreateTextBufferFromFile(string fileName)
{
if (!File.Exists(fileName)) {
throw new System.IO.FileNotFoundException(fileName);
}
StringTextBufferStrategy s = new StringTextBufferStrategy();
s.SetContent(Util.FileReader.ReadFileContent(fileName, Encoding.Default));
return s;
}
}
}

View File

@@ -0,0 +1,128 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision: 2658$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// A line/column position.
/// Text editor lines/columns are counting from zero.
/// </summary>
public struct TextLocation : IComparable<TextLocation>, IEquatable<TextLocation>
{
/// <summary>
/// Represents no text location (-1, -1).
/// </summary>
public static readonly TextLocation Empty = new TextLocation(-1, -1);
public TextLocation(int column, int line)
{
x = column;
y = line;
}
int x, y;
public int X {
get { return x; }
set { x = value; }
}
public int Y {
get { return y; }
set { y = value; }
}
public int Line {
get { return y; }
set { y = value; }
}
public int Column {
get { return x; }
set { x = value; }
}
public bool IsEmpty {
get {
return x <= 0 && y <= 0;
}
}
public override string ToString()
{
return string.Format("(Line {1}, Col {0})", this.x, this.y);
}
public override int GetHashCode()
{
return unchecked (87 * x.GetHashCode() ^ y.GetHashCode());
}
public override bool Equals(object obj)
{
if (!(obj is TextLocation)) return false;
return (TextLocation)obj == this;
}
public bool Equals(TextLocation other)
{
return this == other;
}
public static bool operator ==(TextLocation a, TextLocation b)
{
return a.x == b.x && a.y == b.y;
}
public static bool operator !=(TextLocation a, TextLocation b)
{
return a.x != b.x || a.y != b.y;
}
public static bool operator <(TextLocation a, TextLocation b)
{
if (a.y < b.y)
return true;
else if (a.y == b.y)
return a.x < b.x;
else
return false;
}
public static bool operator >(TextLocation a, TextLocation b)
{
if (a.y > b.y)
return true;
else if (a.y == b.y)
return a.x > b.x;
else
return false;
}
public static bool operator <=(TextLocation a, TextLocation b)
{
return !(a > b);
}
public static bool operator >=(TextLocation a, TextLocation b)
{
return !(a < b);
}
public int CompareTo(TextLocation other)
{
if (this == other)
return 0;
if (this < other)
return -1;
else
return 1;
}
}
}

View File

@@ -0,0 +1,313 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public sealed class TextUtilities
{
/// <remarks>
/// This function takes a string and converts the whitespace in front of
/// it to tabs. If the length of the whitespace at the start of the string
/// was not a whole number of tabs then there will still be some spaces just
/// before the text starts.
/// the output string will be of the form:
/// 1. zero or more tabs
/// 2. zero or more spaces (less than tabIndent)
/// 3. the rest of the line
/// </remarks>
public static string LeadingWhiteSpaceToTabs(string line, int tabIndent) {
StringBuilder sb = new StringBuilder(line.Length);
int consecutiveSpaces = 0;
int i = 0;
for(i = 0; i < line.Length; i++) {
if(line[i] == ' ') {
consecutiveSpaces++;
if(consecutiveSpaces == tabIndent) {
sb.Append('\t');
consecutiveSpaces = 0;
}
}
else if(line[i] == '\t') {
sb.Append('\t');
// if we had say 3 spaces then a tab and tabIndent was 4 then
// we would want to simply replace all of that with 1 tab
consecutiveSpaces = 0;
}
else {
break;
}
}
if(i < line.Length) {
sb.Append(line.Substring(i-consecutiveSpaces));
}
return sb.ToString();
}
public static bool IsLetterDigitOrUnderscore(char c)
{
if(!char.IsLetterOrDigit(c)) {
return c == '_';
}
return true;
}
public enum CharacterType {
LetterDigitOrUnderscore,
WhiteSpace,
Other
}
/// <remarks>
/// This method returns the expression before a specified offset.
/// That method is used in code completion to determine the expression given
/// to the parser for type resolve.
/// </remarks>
public static string GetExpressionBeforeOffset(TextArea textArea, int initialOffset)
{
IDocument document = textArea.Document;
int offset = initialOffset;
while (offset - 1 > 0) {
switch (document.GetCharAt(offset - 1)) {
case '\n':
case '\r':
case '}':
goto done;
// offset = SearchBracketBackward(document, offset - 2, '{','}');
// break;
case ']':
offset = SearchBracketBackward(document, offset - 2, '[',']');
break;
case ')':
offset = SearchBracketBackward(document, offset - 2, '(',')');
break;
case '.':
--offset;
break;
case '"':
if (offset < initialOffset - 1) {
return null;
}
return "\"\"";
case '\'':
if (offset < initialOffset - 1) {
return null;
}
return "'a'";
case '>':
if (document.GetCharAt(offset - 2) == '-') {
offset -= 2;
break;
}
goto done;
default:
if (char.IsWhiteSpace(document.GetCharAt(offset - 1))) {
--offset;
break;
}
int start = offset - 1;
if (!IsLetterDigitOrUnderscore(document.GetCharAt(start))) {
goto done;
}
while (start > 0 && IsLetterDigitOrUnderscore(document.GetCharAt(start - 1))) {
--start;
}
string word = document.GetText(start, offset - start).Trim();
switch (word) {
case "ref":
case "out":
case "in":
case "return":
case "throw":
case "case":
goto done;
}
if (word.Length > 0 && !IsLetterDigitOrUnderscore(word[0])) {
goto done;
}
offset = start;
break;
}
}
done:
//// simple exit fails when : is inside comment line or any other character
//// we have to check if we got several ids in resulting line, which usually happens when
//// id. is typed on next line after comment one
//// Would be better if lexer would parse properly such expressions. However this will cause
//// modifications in this area too - to get full comment line and remove it afterwards
if (offset < 0)
return string.Empty;
string resText=document.GetText(offset, textArea.Caret.Offset - offset ).Trim();
int pos=resText.LastIndexOf('\n');
if (pos>=0) {
offset+=pos+1;
//// whitespaces and tabs, which might be inside, will be skipped by trim below
}
string expression = document.GetText(offset, textArea.Caret.Offset - offset ).Trim();
return expression;
}
public static CharacterType GetCharacterType(char c)
{
if(IsLetterDigitOrUnderscore(c))
return CharacterType.LetterDigitOrUnderscore;
if(char.IsWhiteSpace(c))
return CharacterType.WhiteSpace;
return CharacterType.Other;
}
public static int GetFirstNonWSChar(IDocument document, int offset)
{
while (offset < document.TextLength && char.IsWhiteSpace(document.GetCharAt(offset))) {
++offset;
}
return offset;
}
public static int FindWordEnd(IDocument document, int offset)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
int endPos = line.Offset + line.Length;
while (offset < endPos && IsLetterDigitOrUnderscore(document.GetCharAt(offset))) {
++offset;
}
return offset;
}
public static int FindWordStart(IDocument document, int offset)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
int lineOffset = line.Offset;
while (offset > lineOffset && IsLetterDigitOrUnderscore(document.GetCharAt(offset - 1))) {
--offset;
}
return offset;
}
// go forward to the start of the next word
// if the cursor is at the start or in the middle of a word we move to the end of the word
// and then past any whitespace that follows it
// if the cursor is at the start or in the middle of some whitespace we move to the start of the
// next word
public static int FindNextWordStart(IDocument document, int offset)
{
int originalOffset = offset;
LineSegment line = document.GetLineSegmentForOffset(offset);
int endPos = line.Offset + line.Length;
// lets go to the end of the word, whitespace or operator
CharacterType t = GetCharacterType(document.GetCharAt(offset));
while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == t) {
++offset;
}
// now we're at the end of the word, lets find the start of the next one by skipping whitespace
while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == CharacterType.WhiteSpace) {
++offset;
}
return offset;
}
// go back to the start of the word we are on
// if we are already at the start of a word or if we are in whitespace, then go back
// to the start of the previous word
public static int FindPrevWordStart(IDocument document, int offset)
{
int originalOffset = offset;
if (offset > 0) {
LineSegment line = document.GetLineSegmentForOffset(offset);
CharacterType t = GetCharacterType(document.GetCharAt(offset - 1));
while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t) {
--offset;
}
// if we were in whitespace, and now we're at the end of a word or operator, go back to the beginning of it
if(t == CharacterType.WhiteSpace && offset > line.Offset) {
t = GetCharacterType(document.GetCharAt(offset - 1));
while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t) {
--offset;
}
}
}
return offset;
}
public static string GetLineAsString(IDocument document, int lineNumber)
{
LineSegment line = document.GetLineSegment(lineNumber);
return document.GetText(line.Offset, line.Length);
}
public static int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket)
{
return document.FormattingStrategy.SearchBracketBackward(document, offset, openBracket, closingBracket);
}
public static int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket)
{
return document.FormattingStrategy.SearchBracketForward(document, offset, openBracket, closingBracket);
}
/// <remarks>
/// Returns true, if the line lineNumber is empty or filled with whitespaces.
/// </remarks>
public static bool IsEmptyLine(IDocument document, int lineNumber)
{
return IsEmptyLine(document, document.GetLineSegment(lineNumber));
}
/// <remarks>
/// Returns true, if the line lineNumber is empty or filled with whitespaces.
/// </remarks>
public static bool IsEmptyLine(IDocument document, LineSegment line)
{
for (int i = line.Offset; i < line.Offset + line.Length; ++i) {
char ch = document.GetCharAt(i);
if (!char.IsWhiteSpace(ch)) {
return false;
}
}
return true;
}
static bool IsWordPart(char ch)
{
return IsLetterDigitOrUnderscore(ch) || ch == '.';
}
public static string GetWordAt(IDocument document, int offset)
{
if (offset < 0 || offset >= document.TextLength - 1 || !IsWordPart(document.GetCharAt(offset))) {
return string.Empty;
}
int startOffset = offset;
int endOffset = offset;
while (startOffset > 0 && IsWordPart(document.GetCharAt(startOffset - 1))) {
--startOffset;
}
while (endOffset < document.TextLength - 1 && IsWordPart(document.GetCharAt(endOffset + 1))) {
++endOffset;
}
Debug.Assert(endOffset >= startOffset);
return document.GetText(startOffset, endOffset - startOffset + 1);
}
}
}