first commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 < 0 and bookmarks > 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
457
ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs
Normal file
457
ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs
Normal file
103
ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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("&", "&");
|
||||
encodedValue.Replace("<", "<");
|
||||
encodedValue.Replace(">", ">");
|
||||
|
||||
if (quoteChar == '"')
|
||||
{
|
||||
encodedValue.Replace("\"", """);
|
||||
}
|
||||
else
|
||||
{
|
||||
encodedValue.Replace("'", "'");
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Span>, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
315
ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs
Normal file
315
ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
32
ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs
Normal file
32
ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
118
ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs
Normal file
118
ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs
Normal 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() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs
Normal file
128
ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
313
ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs
Normal file
313
ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user