first commit

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

View File

@@ -0,0 +1,80 @@
// <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 ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Actions
{
public class ToggleBookmark : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
textArea.Document.BookmarkManager.ToggleMarkAt(textArea.Caret.Position);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, textArea.Caret.Line));
textArea.Document.CommitUpdate();
}
}
public class GotoPrevBookmark : AbstractEditAction
{
Predicate<Bookmark> predicate = null;
public GotoPrevBookmark(Predicate<Bookmark> predicate)
{
this.predicate = predicate;
}
public override void Execute(TextArea textArea)
{
Bookmark mark = textArea.Document.BookmarkManager.GetPrevMark(textArea.Caret.Line, predicate);
if (mark != null) {
textArea.Caret.Position = mark.Location;
textArea.SelectionManager.ClearSelection();
textArea.SetDesiredColumn();
}
}
}
public class GotoNextBookmark : AbstractEditAction
{
Predicate<Bookmark> predicate = null;
public GotoNextBookmark(Predicate<Bookmark> predicate)
{
this.predicate = predicate;
}
public override void Execute(TextArea textArea)
{
Bookmark mark = textArea.Document.BookmarkManager.GetNextMark(textArea.Caret.Line, predicate);
if (mark != null) {
textArea.Caret.Position = mark.Location;
textArea.SelectionManager.ClearSelection();
textArea.SetDesiredColumn();
}
}
}
public class ClearAllBookmarks : AbstractEditAction
{
Predicate<Bookmark> predicate = null;
public ClearAllBookmarks(Predicate<Bookmark> predicate)
{
this.predicate = predicate;
}
public override void Execute(TextArea textArea)
{
textArea.Document.BookmarkManager.RemoveMarks(predicate);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
textArea.Document.CommitUpdate();
}
}
}

View File

@@ -0,0 +1,203 @@
// <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.Document;
namespace ICSharpCode.TextEditor.Actions
{
public class CaretLeft : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
TextLocation position = textArea.Caret.Position;
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldedFoldingsWithEnd(position.Y);
FoldMarker justBeforeCaret = null;
foreach (FoldMarker fm in foldings) {
if (fm.EndColumn == position.X) {
justBeforeCaret = fm;
break; // the first folding found is the folding with the smallest Startposition
}
}
if (justBeforeCaret != null) {
position.Y = justBeforeCaret.StartLine;
position.X = justBeforeCaret.StartColumn;
} else {
if (position.X > 0) {
--position.X;
} else if (position.Y > 0) {
LineSegment lineAbove = textArea.Document.GetLineSegment(position.Y - 1);
position = new TextLocation(lineAbove.Length, position.Y - 1);
}
}
textArea.Caret.Position = position;
textArea.SetDesiredColumn();
}
}
public class CaretRight : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
LineSegment curLine = textArea.Document.GetLineSegment(textArea.Caret.Line);
TextLocation position = textArea.Caret.Position;
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldedFoldingsWithStart(position.Y);
FoldMarker justBehindCaret = null;
foreach (FoldMarker fm in foldings) {
if (fm.StartColumn == position.X) {
justBehindCaret = fm;
break;
}
}
if (justBehindCaret != null) {
position.Y = justBehindCaret.EndLine;
position.X = justBehindCaret.EndColumn;
} else { // no folding is interesting
if (position.X < curLine.Length || textArea.TextEditorProperties.AllowCaretBeyondEOL) {
++position.X;
} else if (position.Y + 1 < textArea.Document.TotalNumberOfLines) {
++position.Y;
position.X = 0;
}
}
textArea.Caret.Position = position;
textArea.SetDesiredColumn();
}
}
public class CaretUp : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
TextLocation position = textArea.Caret.Position;
int lineNr = position.Y;
int visualLine = textArea.Document.GetVisibleLine(lineNr);
if (visualLine > 0) {
Point pos = new Point(textArea.TextView.GetDrawingXPos(lineNr, position.X),
textArea.TextView.DrawingPosition.Y + (visualLine - 1) * textArea.TextView.FontHeight - textArea.TextView.TextArea.VirtualTop.Y);
textArea.Caret.Position = textArea.TextView.GetLogicalPosition(pos);
textArea.SetCaretToDesiredColumn();
}
// if (textArea.Caret.Line > 0) {
// textArea.SetCaretToDesiredColumn(textArea.Caret.Line - 1);
// }
}
}
public class CaretDown : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
TextLocation position = textArea.Caret.Position;
int lineNr = position.Y;
int visualLine = textArea.Document.GetVisibleLine(lineNr);
if (visualLine < textArea.Document.GetVisibleLine(textArea.Document.TotalNumberOfLines)) {
Point pos = new Point(textArea.TextView.GetDrawingXPos(lineNr, position.X),
textArea.TextView.DrawingPosition.Y
+ (visualLine + 1) * textArea.TextView.FontHeight
- textArea.TextView.TextArea.VirtualTop.Y);
textArea.Caret.Position = textArea.TextView.GetLogicalPosition(pos);
textArea.SetCaretToDesiredColumn();
}
// if (textArea.Caret.Line + 1 < textArea.Document.TotalNumberOfLines) {
// textArea.SetCaretToDesiredColumn(textArea.Caret.Line + 1);
// }
}
}
public class WordRight : CaretRight
{
public override void Execute(TextArea textArea)
{
LineSegment line = textArea.Document.GetLineSegment(textArea.Caret.Position.Y);
TextLocation oldPos = textArea.Caret.Position;
TextLocation newPos;
if (textArea.Caret.Column >= line.Length) {
newPos = new TextLocation(0, textArea.Caret.Line + 1);
} else {
int nextWordStart = TextUtilities.FindNextWordStart(textArea.Document, textArea.Caret.Offset);
newPos = textArea.Document.OffsetToPosition(nextWordStart);
}
// handle fold markers
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X);
foreach (FoldMarker marker in foldings) {
if (marker.IsFolded) {
if (oldPos.X == marker.StartColumn && oldPos.Y == marker.StartLine) {
newPos = new TextLocation(marker.EndColumn, marker.EndLine);
} else {
newPos = new TextLocation(marker.StartColumn, marker.StartLine);
}
break;
}
}
textArea.Caret.Position = newPos;
textArea.SetDesiredColumn();
}
}
public class WordLeft : CaretLeft
{
public override void Execute(TextArea textArea)
{
TextLocation oldPos = textArea.Caret.Position;
if (textArea.Caret.Column == 0) {
base.Execute(textArea);
} else {
LineSegment line = textArea.Document.GetLineSegment(textArea.Caret.Position.Y);
int prevWordStart = TextUtilities.FindPrevWordStart(textArea.Document, textArea.Caret.Offset);
TextLocation newPos = textArea.Document.OffsetToPosition(prevWordStart);
// handle fold markers
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X);
foreach (FoldMarker marker in foldings) {
if (marker.IsFolded) {
if (oldPos.X == marker.EndColumn && oldPos.Y == marker.EndLine) {
newPos = new TextLocation(marker.StartColumn, marker.StartLine);
} else {
newPos = new TextLocation(marker.EndColumn, marker.EndLine);
}
break;
}
}
textArea.Caret.Position = newPos;
textArea.SetDesiredColumn();
}
}
}
public class ScrollLineUp : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
textArea.AutoClearSelection = false;
textArea.MotherTextAreaControl.VScrollBar.Value = Math.Max(textArea.MotherTextAreaControl.VScrollBar.Minimum,
textArea.VirtualTop.Y - textArea.TextView.FontHeight);
}
}
public class ScrollLineDown : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
textArea.AutoClearSelection = false;
textArea.MotherTextAreaControl.VScrollBar.Value = Math.Min(textArea.MotherTextAreaControl.VScrollBar.Maximum,
textArea.VirtualTop.Y + textArea.TextView.FontHeight);
}
}
}

View File

@@ -0,0 +1,42 @@
// <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.Actions
{
public class Cut : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
textArea.ClipboardHandler.Cut(null, null);
}
}
public class Copy : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
textArea.AutoClearSelection = false;
textArea.ClipboardHandler.Copy(null, null);
}
}
public class Paste : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
textArea.ClipboardHandler.Paste(null, null);
}
}
}

View File

@@ -0,0 +1,69 @@
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.UserControls;
namespace ICSharpCode.TextEditor.Src.Actions
{
abstract class FindAndReplaceFormActions : AbstractEditAction
{
protected readonly TextEditorControlEx Control;
protected readonly FindAndReplaceForm FindForm;
protected FindAndReplaceFormActions(FindAndReplaceForm findForm, TextEditorControlEx control)
{
FindForm = findForm;
Control = control;
}
}
class FindAgainReverseAction : FindAndReplaceFormActions
{
public FindAgainReverseAction(FindAndReplaceForm findForm, TextEditorControlEx control)
: base(findForm, control)
{
}
public override void Execute(TextArea textArea)
{
FindForm.FindNext(true, true, string.Format("Search text «{0}» not found.", FindForm.LookFor));
}
}
class FindAgainAction : FindAndReplaceFormActions
{
public FindAgainAction(FindAndReplaceForm findForm, TextEditorControlEx control)
: base(findForm, control)
{
}
public override void Execute(TextArea textArea)
{
FindForm.FindNext(true, false, string.Format("Search text «{0}» not found.", FindForm.LookFor));
}
}
class EditReplaceAction : FindAndReplaceFormActions
{
public EditReplaceAction(FindAndReplaceForm findForm, TextEditorControlEx control)
: base(findForm, control)
{
}
public override void Execute(TextArea textArea)
{
FindForm.ShowFor(Control, true);
}
}
class EditFindAction : FindAndReplaceFormActions
{
public EditFindAction(FindAndReplaceForm findForm, TextEditorControlEx control)
: base(findForm, control)
{
}
public override void Execute(TextArea textArea)
{
FindForm.ShowFor(Control, false);
}
}
}

View File

@@ -0,0 +1,68 @@
// <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 ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Actions
{
public class ToggleFolding : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(textArea.Caret.Line);
if (foldMarkers.Count != 0) {
foreach (FoldMarker fm in foldMarkers)
fm.IsFolded = !fm.IsFolded;
} else {
foldMarkers = textArea.Document.FoldingManager.GetFoldingsContainsLineNumber(textArea.Caret.Line);
if (foldMarkers.Count != 0) {
FoldMarker innerMost = foldMarkers[0];
for (int i = 1; i < foldMarkers.Count; i++) {
if (new TextLocation(foldMarkers[i].StartColumn, foldMarkers[i].StartLine) >
new TextLocation(innerMost.StartColumn, innerMost.StartLine))
{
innerMost = foldMarkers[i];
}
}
innerMost.IsFolded = !innerMost.IsFolded;
}
}
textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);
}
}
public class ToggleAllFoldings : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
bool doFold = true;
foreach (FoldMarker fm in textArea.Document.FoldingManager.FoldMarker) {
if (fm.IsFolded) {
doFold = false;
break;
}
}
foreach (FoldMarker fm in textArea.Document.FoldingManager.FoldMarker) {
fm.IsFolded = doFold;
}
textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);
}
}
public class ShowDefinitionsOnly : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
foreach (FoldMarker fm in textArea.Document.FoldingManager.FoldMarker) {
fm.IsFolded = fm.FoldType == FoldType.MemberBody || fm.FoldType == FoldType.Region;
}
textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);
}
}
}

View File

@@ -0,0 +1,219 @@
// <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;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Actions
{
public abstract class AbstractLineFormatAction : AbstractEditAction
{
protected TextArea textArea;
abstract protected void Convert(IDocument document, int startLine, int endLine);
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.SelectionIsReadonly) {
return;
}
this.textArea = textArea;
textArea.BeginUpdate();
textArea.Document.UndoStack.StartUndoGroup();
if (textArea.SelectionManager.HasSomethingSelected) {
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
Convert(textArea.Document, selection.StartPosition.Y, selection.EndPosition.Y);
}
} else {
Convert(textArea.Document, 0, textArea.Document.TotalNumberOfLines - 1);
}
textArea.Document.UndoStack.EndUndoGroup();
textArea.Caret.ValidateCaretPos();
textArea.EndUpdate();
textArea.Refresh();
}
}
public abstract class AbstractSelectionFormatAction : AbstractEditAction
{
protected TextArea textArea;
abstract protected void Convert(IDocument document, int offset, int length);
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.SelectionIsReadonly) {
return;
}
this.textArea = textArea;
textArea.BeginUpdate();
if (textArea.SelectionManager.HasSomethingSelected) {
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
Convert(textArea.Document, selection.Offset, selection.Length);
}
} else {
Convert(textArea.Document, 0, textArea.Document.TextLength);
}
textArea.Caret.ValidateCaretPos();
textArea.EndUpdate();
textArea.Refresh();
}
}
public class RemoveLeadingWS : AbstractLineFormatAction
{
protected override void Convert(IDocument document, int y1, int y2)
{
for (int i = y1; i < y2; ++i) {
LineSegment line = document.GetLineSegment(i);
int removeNumber = 0;
for (int x = line.Offset; x < line.Offset + line.Length && char.IsWhiteSpace(document.GetCharAt(x)); ++x) {
++removeNumber;
}
if (removeNumber > 0) {
document.Remove(line.Offset, removeNumber);
}
}
}
}
public class RemoveTrailingWS : AbstractLineFormatAction
{
protected override void Convert(IDocument document, int y1, int y2)
{
for (int i = y2 - 1; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
int removeNumber = 0;
for (int x = line.Offset + line.Length - 1; x >= line.Offset && char.IsWhiteSpace(document.GetCharAt(x)); --x) {
++removeNumber;
}
if (removeNumber > 0) {
document.Remove(line.Offset + line.Length - removeNumber, removeNumber);
}
}
}
}
public class ToUpperCase : AbstractSelectionFormatAction
{
protected override void Convert(IDocument document, int startOffset, int length)
{
string what = document.GetText(startOffset, length).ToUpper();
document.Replace(startOffset, length, what);
}
}
public class ToLowerCase : AbstractSelectionFormatAction
{
protected override void Convert(IDocument document, int startOffset, int length)
{
string what = document.GetText(startOffset, length).ToLower();
document.Replace(startOffset, length, what);
}
}
public class InvertCaseAction : AbstractSelectionFormatAction
{
protected override void Convert(IDocument document, int startOffset, int length)
{
StringBuilder what = new StringBuilder(document.GetText(startOffset, length));
for (int i = 0; i < what.Length; ++i) {
what[i] = char.IsUpper(what[i]) ? char.ToLower(what[i]) : char.ToUpper(what[i]);
}
document.Replace(startOffset, length, what.ToString());
}
}
public class CapitalizeAction : AbstractSelectionFormatAction
{
protected override void Convert(IDocument document, int startOffset, int length)
{
StringBuilder what = new StringBuilder(document.GetText(startOffset, length));
for (int i = 0; i < what.Length; ++i) {
if (!char.IsLetter(what[i]) && i < what.Length - 1) {
what[i + 1] = char.ToUpper(what[i + 1]);
}
}
document.Replace(startOffset, length, what.ToString());
}
}
public class ConvertTabsToSpaces : AbstractSelectionFormatAction
{
protected override void Convert(IDocument document, int startOffset, int length)
{
string what = document.GetText(startOffset, length);
string spaces = new string(' ', document.TextEditorProperties.TabIndent);
document.Replace(startOffset, length, what.Replace("\t", spaces));
}
}
public class ConvertSpacesToTabs : AbstractSelectionFormatAction
{
protected override void Convert(IDocument document, int startOffset, int length)
{
string what = document.GetText(startOffset, length);
string spaces = new string(' ', document.TextEditorProperties.TabIndent);
document.Replace(startOffset, length, what.Replace(spaces, "\t"));
}
}
public class ConvertLeadingTabsToSpaces : AbstractLineFormatAction
{
protected override void Convert(IDocument document, int y1, int y2)
{
for (int i = y2; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
if(line.Length > 0) {
// count how many whitespace characters there are at the start
int whiteSpace = 0;
for(whiteSpace = 0; whiteSpace < line.Length && char.IsWhiteSpace(document.GetCharAt(line.Offset + whiteSpace)); whiteSpace++) {
// deliberately empty
}
if(whiteSpace > 0) {
string newLine = document.GetText(line.Offset,whiteSpace);
string newPrefix = newLine.Replace("\t",new string(' ', document.TextEditorProperties.TabIndent));
document.Replace(line.Offset,whiteSpace,newPrefix);
}
}
}
}
}
public class ConvertLeadingSpacesToTabs : AbstractLineFormatAction
{
protected override void Convert(IDocument document, int y1, int y2)
{
for (int i = y2; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
if(line.Length > 0) {
// note: some users may prefer a more radical ConvertLeadingSpacesToTabs that
// means there can be no spaces before the first character even if the spaces
// didn't add up to a whole number of tabs
string newLine = TextUtilities.LeadingWhiteSpaceToTabs(document.GetText(line.Offset,line.Length), document.TextEditorProperties.TabIndent);
document.Replace(line.Offset,line.Length,newLine);
}
}
}
}
/// <summary>
/// This is a sample editaction plugin, it indents the selected area.
/// </summary>
public class IndentSelection : AbstractLineFormatAction
{
protected override void Convert(IDocument document, int startLine, int endLine)
{
document.FormattingStrategy.IndentLines(textArea, startLine, endLine);
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Windows.Forms;
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.UserControls;
namespace ICSharpCode.TextEditor.Src.Actions
{
class GoToLineNumberAction : AbstractEditAction
{
private readonly GotoForm _gotoForm;
public GoToLineNumberAction()
{
_gotoForm = new GotoForm();
}
public override void Execute(TextArea textArea)
{
_gotoForm.FirstLineNumber = 1;
_gotoForm.LastLineNumber = textArea.Document.TotalNumberOfLines;
_gotoForm.SelectedLineNumber = textArea.Caret.Line + 1;
if (DialogResult.OK == _gotoForm.ShowDialogEx() && _gotoForm.SelectedLineNumber > 0)
{
textArea.Caret.Position = new TextLocation(0, _gotoForm.SelectedLineNumber - 1);
}
}
}
}

View File

@@ -0,0 +1,114 @@
// <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.Document;
namespace ICSharpCode.TextEditor.Actions
{
public class Home : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
LineSegment curLine;
TextLocation newPos = textArea.Caret.Position;
bool jumpedIntoFolding = false;
do {
curLine = textArea.Document.GetLineSegment(newPos.Y);
if (TextUtilities.IsEmptyLine(textArea.Document, newPos.Y)) {
if (newPos.X != 0) {
newPos.X = 0;
} else {
newPos.X = curLine.Length;
}
} else {
int firstCharOffset = TextUtilities.GetFirstNonWSChar(textArea.Document, curLine.Offset);
int firstCharColumn = firstCharOffset - curLine.Offset;
if (newPos.X == firstCharColumn) {
newPos.X = 0;
} else {
newPos.X = firstCharColumn;
}
}
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X);
jumpedIntoFolding = false;
foreach (FoldMarker foldMarker in foldings) {
if (foldMarker.IsFolded) {
newPos = new TextLocation(foldMarker.StartColumn, foldMarker.StartLine);
jumpedIntoFolding = true;
break;
}
}
} while (jumpedIntoFolding);
if (newPos != textArea.Caret.Position) {
textArea.Caret.Position = newPos;
textArea.SetDesiredColumn();
}
}
}
public class End : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
LineSegment curLine;
TextLocation newPos = textArea.Caret.Position;
bool jumpedIntoFolding = false;
do {
curLine = textArea.Document.GetLineSegment(newPos.Y);
newPos.X = curLine.Length;
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X);
jumpedIntoFolding = false;
foreach (FoldMarker foldMarker in foldings) {
if (foldMarker.IsFolded) {
newPos = new TextLocation(foldMarker.EndColumn, foldMarker.EndLine);
jumpedIntoFolding = true;
break;
}
}
} while (jumpedIntoFolding);
if (newPos != textArea.Caret.Position) {
textArea.Caret.Position = newPos;
textArea.SetDesiredColumn();
}
}
}
public class MoveToStart : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
if (textArea.Caret.Line != 0 || textArea.Caret.Column != 0) {
textArea.Caret.Position = new TextLocation(0, 0);
textArea.SetDesiredColumn();
}
}
}
public class MoveToEnd : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
TextLocation endPos = textArea.Document.OffsetToPosition(textArea.Document.TextLength);
if (textArea.Caret.Position != endPos) {
textArea.Caret.Position = endPos;
textArea.SetDesiredColumn();
}
}
}
}

View File

@@ -0,0 +1,58 @@
// <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.Windows.Forms;
namespace ICSharpCode.TextEditor.Actions
{
/// <summary>
/// To define a new key for the textarea, you must write a class which
/// implements this interface.
/// </summary>
public interface IEditAction
{
/// <value>
/// An array of keys on which this edit action occurs.
/// </value>
Keys[] Keys {
get;
set;
}
/// <remarks>
/// When the key which is defined per XML is pressed, this method will be launched.
/// </remarks>
void Execute(TextArea textArea);
}
/// <summary>
/// To define a new key for the textarea, you must write a class which
/// implements this interface.
/// </summary>
public abstract class AbstractEditAction : IEditAction
{
Keys[] keys = null;
/// <value>
/// An array of keys on which this edit action occurs.
/// </value>
public Keys[] Keys {
get {
return keys;
}
set {
keys = value;
}
}
/// <remarks>
/// When the key which is defined per XML is pressed, this method will be launched.
/// </remarks>
public abstract void Execute(TextArea textArea);
}
}

View File

@@ -0,0 +1,902 @@
// <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.Text;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Actions
{
public class Tab : AbstractEditAction
{
public static string GetIndentationString(IDocument document)
{
return GetIndentationString(document, null);
}
public static string GetIndentationString(IDocument document, TextArea textArea)
{
StringBuilder indent = new StringBuilder();
if (document.TextEditorProperties.ConvertTabsToSpaces) {
int tabIndent = document.TextEditorProperties.IndentationSize;
if (textArea != null) {
int column = textArea.TextView.GetVisualColumn(textArea.Caret.Line, textArea.Caret.Column);
indent.Append(new string(' ', tabIndent - column % tabIndent));
} else {
indent.Append(new string(' ', tabIndent));
}
} else {
indent.Append('\t');
}
return indent.ToString();
}
void InsertTabs(IDocument document, ISelection selection, int y1, int y2)
{
string indentationString = GetIndentationString(document);
for (int i = y2; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
if (i == y2 && i == selection.EndPosition.Y && selection.EndPosition.X == 0) {
continue;
}
// this bit is optional - but useful if you are using block tabbing to sort out
// a source file with a mixture of tabs and spaces
// string newLine = document.GetText(line.Offset,line.Length);
// document.Replace(line.Offset,line.Length,newLine);
document.Insert(line.Offset, indentationString);
}
}
void InsertTabAtCaretPosition(TextArea textArea)
{
switch (textArea.Caret.CaretMode) {
case CaretMode.InsertMode:
textArea.InsertString(GetIndentationString(textArea.Document, textArea));
break;
case CaretMode.OverwriteMode:
string indentStr = GetIndentationString(textArea.Document, textArea);
textArea.ReplaceChar(indentStr[0]);
if (indentStr.Length > 1) {
textArea.InsertString(indentStr.Substring(1));
}
break;
}
textArea.SetDesiredColumn();
}
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.SelectionIsReadonly) {
return;
}
textArea.Document.UndoStack.StartUndoGroup();
if (textArea.SelectionManager.HasSomethingSelected) {
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
int startLine = selection.StartPosition.Y;
int endLine = selection.EndPosition.Y;
if (startLine != endLine) {
textArea.BeginUpdate();
InsertTabs(textArea.Document, selection, startLine, endLine);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, startLine, endLine));
textArea.EndUpdate();
} else {
InsertTabAtCaretPosition(textArea);
break;
}
}
textArea.Document.CommitUpdate();
textArea.AutoClearSelection = false;
} else {
InsertTabAtCaretPosition(textArea);
}
textArea.Document.UndoStack.EndUndoGroup();
}
}
public class ShiftTab : AbstractEditAction
{
void RemoveTabs(IDocument document, ISelection selection, int y1, int y2)
{
document.UndoStack.StartUndoGroup();
for (int i = y2; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
if (i == y2 && line.Offset == selection.EndOffset) {
continue;
}
if (line.Length > 0) {
/**** TextPad Strategy:
/// first convert leading whitespace to tabs (controversial! - not all editors work like this)
string newLine = TextUtilities.LeadingWhiteSpaceToTabs(document.GetText(line.Offset,line.Length),document.Properties.Get("TabIndent", 4));
if(newLine.Length > 0 && newLine[0] == '\t') {
document.Replace(line.Offset,line.Length,newLine.Substring(1));
++redocounter;
}
else if(newLine.Length > 0 && newLine[0] == ' ') {
/// there were just some leading spaces but less than TabIndent of them
int leadingSpaces = 1;
for(leadingSpaces = 1; leadingSpaces < newLine.Length && newLine[leadingSpaces] == ' '; leadingSpaces++) {
/// deliberately empty
}
document.Replace(line.Offset,line.Length,newLine.Substring(leadingSpaces));
++redocounter;
}
/// else
/// there were no leading tabs or spaces on this line so do nothing
/// MS Visual Studio 6 strategy:
****/
// string temp = document.GetText(line.Offset,line.Length);
if (line.Length > 0) {
int charactersToRemove = 0;
if(document.GetCharAt(line.Offset) == '\t') { // first character is a tab - just remove it
charactersToRemove = 1;
} else if(document.GetCharAt(line.Offset) == ' ') {
int leadingSpaces = 1;
int tabIndent = document.TextEditorProperties.IndentationSize;
for (leadingSpaces = 1; leadingSpaces < line.Length && document.GetCharAt(line.Offset + leadingSpaces) == ' '; leadingSpaces++) {
// deliberately empty
}
if(leadingSpaces >= tabIndent) {
// just remove tabIndent
charactersToRemove = tabIndent;
}
else if(line.Length > leadingSpaces && document.GetCharAt(line.Offset + leadingSpaces) == '\t') {
// remove the leading spaces and the following tab as they add up
// to just one tab stop
charactersToRemove = leadingSpaces+1;
}
else {
// just remove the leading spaces
charactersToRemove = leadingSpaces;
}
}
if (charactersToRemove > 0) {
document.Remove(line.Offset,charactersToRemove);
}
}
}
}
document.UndoStack.EndUndoGroup();
}
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.HasSomethingSelected) {
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
int startLine = selection.StartPosition.Y;
int endLine = selection.EndPosition.Y;
textArea.BeginUpdate();
RemoveTabs(textArea.Document, selection, startLine, endLine);
textArea.Document.UpdateQueue.Clear();
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, startLine, endLine));
textArea.EndUpdate();
}
textArea.AutoClearSelection = false;
} else {
// Pressing Shift-Tab with nothing selected the cursor will move back to the
// previous tab stop. It will stop at the beginning of the line. Also, the desired
// column is updated to that column.
LineSegment line = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
string startOfLine = textArea.Document.GetText(line.Offset,textArea.Caret.Offset - line.Offset);
int tabIndent = textArea.Document.TextEditorProperties.IndentationSize;
int currentColumn = textArea.Caret.Column;
int remainder = currentColumn % tabIndent;
if (remainder == 0) {
textArea.Caret.DesiredColumn = Math.Max(0, currentColumn - tabIndent);
} else {
textArea.Caret.DesiredColumn = Math.Max(0, currentColumn - remainder);
}
textArea.SetCaretToDesiredColumn();
}
}
}
public class ToggleComment : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("LineComment")) {
new ToggleLineComment().Execute(textArea);
} else if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin") &&
textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin")) {
new ToggleBlockComment().Execute(textArea);
}
}
}
public class ToggleLineComment : AbstractEditAction
{
int firstLine;
int lastLine;
void RemoveCommentAt(IDocument document, string comment, ISelection selection, int y1, int y2)
{
firstLine = y1;
lastLine = y2;
for (int i = y2; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
if (selection != null && i == y2 && line.Offset == selection.Offset + selection.Length) {
--lastLine;
continue;
}
string lineText = document.GetText(line.Offset, line.Length);
if (lineText.Trim().StartsWith(comment)) {
document.Remove(line.Offset + lineText.IndexOf(comment), comment.Length);
}
}
}
void SetCommentAt(IDocument document, string comment, ISelection selection, int y1, int y2)
{
firstLine = y1;
lastLine = y2;
for (int i = y2; i >= y1; --i) {
LineSegment line = document.GetLineSegment(i);
if (selection != null && i == y2 && line.Offset == selection.Offset + selection.Length) {
--lastLine;
continue;
}
string lineText = document.GetText(line.Offset, line.Length);
document.Insert(line.Offset, comment);
}
}
bool ShouldComment(IDocument document, string comment, ISelection selection, int startLine, int endLine)
{
for (int i = endLine; i >= startLine; --i) {
LineSegment line = document.GetLineSegment(i);
if (selection != null && i == endLine && line.Offset == selection.Offset + selection.Length) {
--lastLine;
continue;
}
string lineText = document.GetText(line.Offset, line.Length);
if (!lineText.Trim().StartsWith(comment)) {
return true;
}
}
return false;
}
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
string comment = null;
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("LineComment")) {
comment = textArea.Document.HighlightingStrategy.Properties["LineComment"].ToString();
}
if (comment == null || comment.Length == 0) {
return;
}
textArea.Document.UndoStack.StartUndoGroup();
if (textArea.SelectionManager.HasSomethingSelected) {
bool shouldComment = true;
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
if (!ShouldComment(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y)) {
shouldComment = false;
break;
}
}
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
textArea.BeginUpdate();
if (shouldComment) {
SetCommentAt(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y);
} else {
RemoveCommentAt(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y);
}
textArea.Document.UpdateQueue.Clear();
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, firstLine, lastLine));
textArea.EndUpdate();
}
textArea.Document.CommitUpdate();
textArea.AutoClearSelection = false;
} else {
textArea.BeginUpdate();
int caretLine = textArea.Caret.Line;
if (ShouldComment(textArea.Document, comment, null, caretLine, caretLine)) {
SetCommentAt(textArea.Document, comment, null, caretLine, caretLine);
} else {
RemoveCommentAt(textArea.Document, comment, null, caretLine, caretLine);
}
textArea.Document.UpdateQueue.Clear();
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, caretLine));
textArea.EndUpdate();
}
textArea.Document.UndoStack.EndUndoGroup();
}
}
public class ToggleBlockComment : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
string commentStart = null;
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin")) {
commentStart = textArea.Document.HighlightingStrategy.Properties["BlockCommentBegin"].ToString();
}
string commentEnd = null;
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentEnd")) {
commentEnd = textArea.Document.HighlightingStrategy.Properties["BlockCommentEnd"].ToString();
}
if (commentStart == null || commentStart.Length == 0 || commentEnd == null || commentEnd.Length == 0) {
return;
}
int selectionStartOffset;
int selectionEndOffset;
if (textArea.SelectionManager.HasSomethingSelected) {
selectionStartOffset = textArea.SelectionManager.SelectionCollection[0].Offset;
selectionEndOffset = textArea.SelectionManager.SelectionCollection[textArea.SelectionManager.SelectionCollection.Count - 1].EndOffset;
} else {
selectionStartOffset = textArea.Caret.Offset;
selectionEndOffset = selectionStartOffset;
}
BlockCommentRegion commentRegion = FindSelectedCommentRegion(textArea.Document, commentStart, commentEnd, selectionStartOffset, selectionEndOffset);
textArea.Document.UndoStack.StartUndoGroup();
if (commentRegion != null) {
RemoveComment(textArea.Document, commentRegion);
} else if (textArea.SelectionManager.HasSomethingSelected) {
SetCommentAt(textArea.Document, selectionStartOffset, selectionEndOffset, commentStart, commentEnd);
}
textArea.Document.UndoStack.EndUndoGroup();
textArea.Document.CommitUpdate();
textArea.AutoClearSelection = false;
}
public static BlockCommentRegion FindSelectedCommentRegion(IDocument document, string commentStart, string commentEnd, int selectionStartOffset, int selectionEndOffset)
{
if (document.TextLength == 0) {
return null;
}
// Find start of comment in selected text.
int commentEndOffset = -1;
string selectedText = document.GetText(selectionStartOffset, selectionEndOffset - selectionStartOffset);
int commentStartOffset = selectedText.IndexOf(commentStart);
if (commentStartOffset >= 0) {
commentStartOffset += selectionStartOffset;
}
// Find end of comment in selected text.
if (commentStartOffset >= 0) {
commentEndOffset = selectedText.IndexOf(commentEnd, commentStartOffset + commentStart.Length - selectionStartOffset);
} else {
commentEndOffset = selectedText.IndexOf(commentEnd);
}
if (commentEndOffset >= 0) {
commentEndOffset += selectionStartOffset;
}
// Find start of comment before or partially inside the
// selected text.
int commentEndBeforeStartOffset = -1;
if (commentStartOffset == -1) {
int offset = selectionEndOffset + commentStart.Length - 1;
if (offset > document.TextLength) {
offset = document.TextLength;
}
string text = document.GetText(0, offset);
commentStartOffset = text.LastIndexOf(commentStart);
if (commentStartOffset >= 0) {
// Find end of comment before comment start.
commentEndBeforeStartOffset = text.IndexOf(commentEnd, commentStartOffset, selectionStartOffset - commentStartOffset);
if (commentEndBeforeStartOffset > commentStartOffset) {
commentStartOffset = -1;
}
}
}
// Find end of comment after or partially after the
// selected text.
if (commentEndOffset == -1) {
int offset = selectionStartOffset + 1 - commentEnd.Length;
if (offset < 0) {
offset = selectionStartOffset;
}
string text = document.GetText(offset, document.TextLength - offset);
commentEndOffset = text.IndexOf(commentEnd);
if (commentEndOffset >= 0) {
commentEndOffset += offset;
}
}
if (commentStartOffset != -1 && commentEndOffset != -1) {
return new BlockCommentRegion(commentStart, commentEnd, commentStartOffset, commentEndOffset);
}
return null;
}
void SetCommentAt(IDocument document, int offsetStart, int offsetEnd, string commentStart, string commentEnd)
{
document.Insert(offsetEnd, commentEnd);
document.Insert(offsetStart, commentStart);
}
void RemoveComment(IDocument document, BlockCommentRegion commentRegion)
{
document.Remove(commentRegion.EndOffset, commentRegion.CommentEnd.Length);
document.Remove(commentRegion.StartOffset, commentRegion.CommentStart.Length);
}
}
public class BlockCommentRegion
{
string commentStart = string.Empty;
string commentEnd = string.Empty;
int startOffset = -1;
int endOffset = -1;
/// <summary>
/// The end offset is the offset where the comment end string starts from.
/// </summary>
public BlockCommentRegion(string commentStart, string commentEnd, int startOffset, int endOffset)
{
this.commentStart = commentStart;
this.commentEnd = commentEnd;
this.startOffset = startOffset;
this.endOffset = endOffset;
}
public string CommentStart {
get {
return commentStart;
}
}
public string CommentEnd {
get {
return commentEnd;
}
}
public int StartOffset {
get {
return startOffset;
}
}
public int EndOffset {
get {
return endOffset;
}
}
public override int GetHashCode()
{
int hashCode = 0;
unchecked {
if (commentStart != null) hashCode += 1000000007 * commentStart.GetHashCode();
if (commentEnd != null) hashCode += 1000000009 * commentEnd.GetHashCode();
hashCode += 1000000021 * startOffset.GetHashCode();
hashCode += 1000000033 * endOffset.GetHashCode();
}
return hashCode;
}
public override bool Equals(object obj)
{
BlockCommentRegion other = obj as BlockCommentRegion;
if (other == null) return false;
return this.commentStart == other.commentStart && this.commentEnd == other.commentEnd && this.startOffset == other.startOffset && this.endOffset == other.endOffset;
}
}
public class Backspace : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.HasSomethingSelected) {
Delete.DeleteSelection(textArea);
} else {
if (textArea.Caret.Offset > 0 && !textArea.IsReadOnly(textArea.Caret.Offset - 1)) {
textArea.BeginUpdate();
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
int curLineOffset = textArea.Document.GetLineSegment(curLineNr).Offset;
if (curLineOffset == textArea.Caret.Offset) {
LineSegment line = textArea.Document.GetLineSegment(curLineNr - 1);
bool lastLine = curLineNr == textArea.Document.TotalNumberOfLines;
int lineEndOffset = line.Offset + line.Length;
int lineLength = line.Length;
textArea.Document.Remove(lineEndOffset, curLineOffset - lineEndOffset);
textArea.Caret.Position = new TextLocation(lineLength, curLineNr - 1);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr - 1)));
} else {
int caretOffset = textArea.Caret.Offset - 1;
textArea.Caret.Position = textArea.Document.OffsetToPosition(caretOffset);
textArea.Document.Remove(caretOffset, 1);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToLineEnd, new TextLocation(textArea.Caret.Offset - textArea.Document.GetLineSegment(curLineNr).Offset, curLineNr)));
}
textArea.EndUpdate();
}
}
}
}
public class Delete : AbstractEditAction
{
internal static void DeleteSelection(TextArea textArea)
{
Debug.Assert(textArea.SelectionManager.HasSomethingSelected);
if (textArea.SelectionManager.SelectionIsReadonly)
return;
textArea.BeginUpdate();
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
textArea.SelectionManager.RemoveSelectedText();
textArea.ScrollToCaret();
textArea.EndUpdate();
}
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.HasSomethingSelected) {
DeleteSelection(textArea);
} else {
if (textArea.IsReadOnly(textArea.Caret.Offset))
return;
if (textArea.Caret.Offset < textArea.Document.TextLength) {
textArea.BeginUpdate();
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
LineSegment curLine = textArea.Document.GetLineSegment(curLineNr);
if (curLine.Offset + curLine.Length == textArea.Caret.Offset) {
if (curLineNr + 1 < textArea.Document.TotalNumberOfLines) {
LineSegment nextLine = textArea.Document.GetLineSegment(curLineNr + 1);
textArea.Document.Remove(textArea.Caret.Offset, nextLine.Offset - textArea.Caret.Offset);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr)));
}
} else {
textArea.Document.Remove(textArea.Caret.Offset, 1);
// textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToLineEnd, new TextLocation(textArea.Caret.Offset - textArea.Document.GetLineSegment(curLineNr).Offset, curLineNr)));
}
textArea.UpdateMatchingBracket();
textArea.EndUpdate();
}
}
}
}
public class MovePageDown : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
int curLineNr = textArea.Caret.Line;
int requestedLineNumber = Math.Min(textArea.Document.GetNextVisibleLineAbove(curLineNr, textArea.TextView.VisibleLineCount), textArea.Document.TotalNumberOfLines - 1);
if (curLineNr != requestedLineNumber) {
textArea.Caret.Position = new TextLocation(0, requestedLineNumber);
textArea.SetCaretToDesiredColumn();
}
}
}
public class MovePageUp : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
int curLineNr = textArea.Caret.Line;
int requestedLineNumber = Math.Max(textArea.Document.GetNextVisibleLineBelow(curLineNr, textArea.TextView.VisibleLineCount), 0);
if (curLineNr != requestedLineNumber) {
textArea.Caret.Position = new TextLocation(0, requestedLineNumber);
textArea.SetCaretToDesiredColumn();
}
}
}
public class Return : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="TextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
textArea.BeginUpdate();
textArea.Document.UndoStack.StartUndoGroup();
try {
if (textArea.HandleKeyPress('\n'))
return;
textArea.InsertString(Environment.NewLine);
int curLineNr = textArea.Caret.Line;
textArea.Document.FormattingStrategy.FormatLine(textArea, curLineNr, textArea.Caret.Offset, '\n');
textArea.SetDesiredColumn();
textArea.Document.UpdateQueue.Clear();
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr - 1)));
} finally {
textArea.Document.UndoStack.EndUndoGroup();
textArea.EndUpdate();
}
}
}
public class ToggleEditMode : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.Document.ReadOnly) {
return;
}
switch (textArea.Caret.CaretMode) {
case CaretMode.InsertMode:
textArea.Caret.CaretMode = CaretMode.OverwriteMode;
break;
case CaretMode.OverwriteMode:
textArea.Caret.CaretMode = CaretMode.InsertMode;
break;
}
}
}
public class Undo : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
textArea.MotherTextEditorControl.Undo();
}
}
public class Redo : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
textArea.MotherTextEditorControl.Redo();
}
}
/// <summary>
/// handles the ctrl-backspace key
/// functionality attempts to roughly mimic MS Developer studio
/// I will implement this as deleting back to the point that ctrl-leftarrow would
/// take you to
/// </summary>
public class WordBackspace : AbstractEditAction
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
// if anything is selected we will just delete it first
if (textArea.SelectionManager.HasSomethingSelected) {
Delete.DeleteSelection(textArea);
return;
}
textArea.BeginUpdate();
// now delete from the caret to the beginning of the word
LineSegment line =
textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
// if we are not at the beginning of a line
if (textArea.Caret.Offset > line.Offset) {
int prevWordStart = TextUtilities.FindPrevWordStart(textArea.Document,
textArea.Caret.Offset);
if (prevWordStart < textArea.Caret.Offset) {
if (!textArea.IsReadOnly(prevWordStart, textArea.Caret.Offset - prevWordStart)) {
textArea.Document.Remove(prevWordStart,
textArea.Caret.Offset - prevWordStart);
textArea.Caret.Position = textArea.Document.OffsetToPosition(prevWordStart);
}
}
}
// if we are now at the beginning of a line
if (textArea.Caret.Offset == line.Offset) {
// if we are not on the first line
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
if (curLineNr > 0) {
// move to the end of the line above
LineSegment lineAbove = textArea.Document.GetLineSegment(curLineNr - 1);
int endOfLineAbove = lineAbove.Offset + lineAbove.Length;
int charsToDelete = textArea.Caret.Offset - endOfLineAbove;
if (!textArea.IsReadOnly(endOfLineAbove, charsToDelete)) {
textArea.Document.Remove(endOfLineAbove, charsToDelete);
textArea.Caret.Position = textArea.Document.OffsetToPosition(endOfLineAbove);
}
}
}
textArea.SetDesiredColumn();
textArea.EndUpdate();
// if there are now less lines, we need this or there are redraw problems
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset))));
textArea.Document.CommitUpdate();
}
}
/// <summary>
/// handles the ctrl-delete key
/// functionality attempts to mimic MS Developer studio
/// I will implement this as deleting forwardto the point that
/// ctrl-leftarrow would take you to
/// </summary>
public class DeleteWord : Delete
{
/// <remarks>
/// Executes this edit action
/// </remarks>
/// <param name="textArea">The <see cref="ItextArea"/> which is used for callback purposes</param>
public override void Execute(TextArea textArea)
{
if (textArea.SelectionManager.HasSomethingSelected) {
DeleteSelection(textArea);
return;
}
// if anything is selected we will just delete it first
textArea.BeginUpdate();
// now delete from the caret to the beginning of the word
LineSegment line = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
if(textArea.Caret.Offset == line.Offset + line.Length) {
// if we are at the end of a line
base.Execute(textArea);
} else {
int nextWordStart = TextUtilities.FindNextWordStart(textArea.Document,
textArea.Caret.Offset);
if(nextWordStart > textArea.Caret.Offset) {
if (!textArea.IsReadOnly(textArea.Caret.Offset, nextWordStart - textArea.Caret.Offset)) {
textArea.Document.Remove(textArea.Caret.Offset, nextWordStart - textArea.Caret.Offset);
// cursor never moves with this command
}
}
}
textArea.UpdateMatchingBracket();
textArea.EndUpdate();
// if there are now less lines, we need this or there are redraw problems
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset))));
textArea.Document.CommitUpdate();
}
}
public class DeleteLine : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
int lineNr = textArea.Caret.Line;
LineSegment line = textArea.Document.GetLineSegment(lineNr);
if (textArea.IsReadOnly(line.Offset, line.Length))
return;
textArea.Document.Remove(line.Offset, line.TotalLength);
textArea.Caret.Position = textArea.Document.OffsetToPosition(line.Offset);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, lineNr)));
textArea.UpdateMatchingBracket();
textArea.Document.CommitUpdate();
}
}
public class DeleteToLineEnd : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
int lineNr = textArea.Caret.Line;
LineSegment line = textArea.Document.GetLineSegment(lineNr);
int numRemove = (line.Offset + line.Length) - textArea.Caret.Offset;
if (numRemove > 0 && !textArea.IsReadOnly(textArea.Caret.Offset, numRemove)) {
textArea.Document.Remove(textArea.Caret.Offset, numRemove);
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, new TextLocation(0, lineNr)));
textArea.Document.CommitUpdate();
}
}
}
public class GotoMatchingBrace : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
Highlight highlight = textArea.FindMatchingBracketHighlight();
if (highlight != null) {
TextLocation p1 = new TextLocation(highlight.CloseBrace.X + 1, highlight.CloseBrace.Y);
TextLocation p2 = new TextLocation(highlight.OpenBrace.X + 1, highlight.OpenBrace.Y);
if (p1 == textArea.Caret.Position) {
if (textArea.Document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) {
textArea.Caret.Position = p2;
} else {
textArea.Caret.Position = new TextLocation(p2.X - 1, p2.Y);
}
} else {
if (textArea.Document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) {
textArea.Caret.Position = p1;
} else {
textArea.Caret.Position = new TextLocation(p1.X - 1, p1.Y);
}
}
textArea.SetDesiredColumn();
}
}
}
}

View File

@@ -0,0 +1,176 @@
// <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 ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Actions
{
public class ShiftCaretRight : CaretRight
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftCaretLeft : CaretLeft
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftCaretUp : CaretUp
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftCaretDown : CaretDown
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftWordRight : WordRight
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftWordLeft : WordLeft
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftHome : Home
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftEnd : End
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftMoveToStart : MoveToStart
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftMoveToEnd : MoveToEnd
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftMovePageUp : MovePageUp
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class ShiftMovePageDown : MovePageDown
{
public override void Execute(TextArea textArea)
{
TextLocation oldCaretPos = textArea.Caret.Position;
base.Execute(textArea);
textArea.AutoClearSelection = false;
textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position);
}
}
public class SelectWholeDocument : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
textArea.AutoClearSelection = false;
TextLocation startPoint = new TextLocation(0, 0);
TextLocation endPoint = textArea.Document.OffsetToPosition(textArea.Document.TextLength);
if (textArea.SelectionManager.HasSomethingSelected) {
if (textArea.SelectionManager.SelectionCollection[0].StartPosition == startPoint &&
textArea.SelectionManager.SelectionCollection[0].EndPosition == endPoint) {
return;
}
}
textArea.Caret.Position = textArea.SelectionManager.NextValidPosition(endPoint.Y);
textArea.SelectionManager.ExtendSelection(startPoint, endPoint);
// after a SelectWholeDocument selection, the caret is placed correctly,
// but it is not positioned internally. The effect is when the cursor
// is moved up or down a line, the caret will take on the column that
// it was in before the SelectWholeDocument
textArea.SetDesiredColumn();
}
}
public class ClearAllSelections : AbstractEditAction
{
public override void Execute(TextArea textArea)
{
textArea.SelectionManager.ClearSelection();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,115 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
public delegate void MarginMouseEventHandler(AbstractMargin sender, Point mousepos, MouseButtons mouseButtons);
public delegate void MarginPaintEventHandler(AbstractMargin sender, Graphics g, Rectangle rect);
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public abstract class AbstractMargin
{
Cursor cursor = Cursors.Default;
[CLSCompliant(false)]
protected Rectangle drawingPosition = new Rectangle(0, 0, 0, 0);
[CLSCompliant(false)]
protected TextArea textArea;
public Rectangle DrawingPosition {
get {
return drawingPosition;
}
set {
drawingPosition = value;
}
}
public TextArea TextArea {
get {
return textArea;
}
}
public IDocument Document {
get {
return textArea.Document;
}
}
public ITextEditorProperties TextEditorProperties {
get {
return textArea.Document.TextEditorProperties;
}
}
public virtual Cursor Cursor {
get {
return cursor;
}
set {
cursor = value;
}
}
public virtual Size Size {
get {
return new Size(-1, -1);
}
}
public virtual bool IsVisible {
get {
return true;
}
}
protected AbstractMargin(TextArea textArea)
{
this.textArea = textArea;
}
public virtual void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
{
if (MouseDown != null) {
MouseDown(this, mousepos, mouseButtons);
}
}
public virtual void HandleMouseMove(Point mousepos, MouseButtons mouseButtons)
{
if (MouseMove != null) {
MouseMove(this, mousepos, mouseButtons);
}
}
public virtual void HandleMouseLeave(EventArgs e)
{
if (MouseLeave != null) {
MouseLeave(this, e);
}
}
public virtual void Paint(Graphics g, Rectangle rect)
{
if (Painted != null) {
Painted(this, g, rect);
}
}
public event MarginPaintEventHandler Painted;
public event MarginMouseEventHandler MouseDown;
public event MarginMouseEventHandler MouseMove;
public event EventHandler MouseLeave;
}
}

View File

@@ -0,0 +1,86 @@
// <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 ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
public class Highlight
{
public TextLocation OpenBrace { get; set; }
public TextLocation CloseBrace { get; set; }
public Highlight(TextLocation openBrace, TextLocation closeBrace)
{
this.OpenBrace = openBrace;
this.CloseBrace = closeBrace;
}
}
public class BracketHighlightingSheme
{
char opentag;
char closingtag;
public char OpenTag {
get {
return opentag;
}
set {
opentag = value;
}
}
public char ClosingTag {
get {
return closingtag;
}
set {
closingtag = value;
}
}
public BracketHighlightingSheme(char opentag, char closingtag)
{
this.opentag = opentag;
this.closingtag = closingtag;
}
public Highlight GetHighlight(IDocument document, int offset)
{
int searchOffset;
if (document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) {
searchOffset = offset;
} else {
searchOffset = offset + 1;
}
char word = document.GetCharAt(Math.Max(0, Math.Min(document.TextLength - 1, searchOffset)));
TextLocation endP = document.OffsetToPosition(searchOffset);
if (word == opentag) {
if (searchOffset < document.TextLength) {
int bracketOffset = TextUtilities.SearchBracketForward(document, searchOffset + 1, opentag, closingtag);
if (bracketOffset >= 0) {
TextLocation p = document.OffsetToPosition(bracketOffset);
return new Highlight(p, endP);
}
}
} else if (word == closingtag) {
if (searchOffset > 0) {
int bracketOffset = TextUtilities.SearchBracketBackward(document, searchOffset - 1, opentag, closingtag);
if (bracketOffset >= 0) {
TextLocation p = document.OffsetToPosition(bracketOffset);
return new Highlight(p, endP);
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,65 @@
// <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.Drawing;
using System.Drawing.Drawing2D;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// Contains brushes/pens for the text editor to speed up drawing. Re-Creation of brushes and pens
/// seems too costly.
/// </summary>
public class BrushRegistry
{
static Dictionary<Color, Brush> brushes = new Dictionary<Color, Brush>();
static Dictionary<Color, Pen> pens = new Dictionary<Color, Pen>();
static Dictionary<Color, Pen> dotPens = new Dictionary<Color, Pen>();
public static Brush GetBrush(Color color)
{
lock (brushes) {
Brush brush;
if (!brushes.TryGetValue(color, out brush)) {
brush = new SolidBrush(color);
brushes.Add(color, brush);
}
return brush;
}
}
public static Pen GetPen(Color color)
{
lock (pens) {
Pen pen;
if (!pens.TryGetValue(color, out pen)) {
pen = new Pen(color);
pens.Add(color, pen);
}
return pen;
}
}
static readonly float[] dotPattern = { 1, 1, 1, 1 };
public static Pen GetDotPen(Color color)
{
lock (dotPens) {
Pen pen;
if (!dotPens.TryGetValue(color, out pen)) {
pen = new Pen(color);
pen.DashPattern = dotPattern;
dotPens.Add(color, pen);
}
return pen;
}
}
}
}

View File

@@ -0,0 +1,510 @@
// <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.Runtime.InteropServices;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// In this enumeration are all caret modes listed.
/// </summary>
public enum CaretMode {
/// <summary>
/// If the caret is in insert mode typed characters will be
/// inserted at the caret position
/// </summary>
InsertMode,
/// <summary>
/// If the caret is in overwirte mode typed characters will
/// overwrite the character at the caret position
/// </summary>
OverwriteMode
}
public class Caret : System.IDisposable
{
int line = 0;
int column = 0;
int desiredXPos = 0;
CaretMode caretMode;
static bool caretCreated = false;
bool hidden = true;
TextArea textArea;
Point currentPos = new Point(-1, -1);
Ime ime = null;
CaretImplementation caretImplementation;
/// <value>
/// The 'prefered' xPos in which the caret moves, when it is moved
/// up/down. Measured in pixels, not in characters!
/// </value>
public int DesiredColumn {
get {
return desiredXPos;
}
set {
desiredXPos = value;
}
}
/// <value>
/// The current caret mode.
/// </value>
public CaretMode CaretMode {
get {
return caretMode;
}
set {
caretMode = value;
OnCaretModeChanged(EventArgs.Empty);
}
}
public int Line {
get {
return line;
}
set {
line = value;
ValidateCaretPos();
UpdateCaretPosition();
OnPositionChanged(EventArgs.Empty);
}
}
public int Column {
get {
return column;
}
set {
column = value;
ValidateCaretPos();
UpdateCaretPosition();
OnPositionChanged(EventArgs.Empty);
}
}
public TextLocation Position {
get {
return new TextLocation(column, line);
}
set {
line = value.Y;
column = value.X;
ValidateCaretPos();
UpdateCaretPosition();
OnPositionChanged(EventArgs.Empty);
}
}
public int Offset {
get {
return textArea.Document.PositionToOffset(Position);
}
}
public Caret(TextArea textArea)
{
this.textArea = textArea;
textArea.GotFocus += new EventHandler(GotFocus);
textArea.LostFocus += new EventHandler(LostFocus);
if (Environment.OSVersion.Platform == PlatformID.Unix)
caretImplementation = new ManagedCaret(this);
else
caretImplementation = new Win32Caret(this);
}
public void Dispose()
{
textArea.GotFocus -= new EventHandler(GotFocus);
textArea.LostFocus -= new EventHandler(LostFocus);
textArea = null;
caretImplementation.Dispose();
}
public TextLocation ValidatePosition(TextLocation pos)
{
int line = Math.Max(0, Math.Min(textArea.Document.TotalNumberOfLines - 1, pos.Y));
int column = Math.Max(0, pos.X);
if (column == int.MaxValue || !textArea.TextEditorProperties.AllowCaretBeyondEOL) {
LineSegment lineSegment = textArea.Document.GetLineSegment(line);
column = Math.Min(column, lineSegment.Length);
}
return new TextLocation(column, line);
}
/// <remarks>
/// If the caret position is outside the document text bounds
/// it is set to the correct position by calling ValidateCaretPos.
/// </remarks>
public void ValidateCaretPos()
{
line = Math.Max(0, Math.Min(textArea.Document.TotalNumberOfLines - 1, line));
column = Math.Max(0, column);
if (column == int.MaxValue || !textArea.TextEditorProperties.AllowCaretBeyondEOL) {
LineSegment lineSegment = textArea.Document.GetLineSegment(line);
column = Math.Min(column, lineSegment.Length);
}
}
void CreateCaret()
{
while (!caretCreated) {
switch (caretMode) {
case CaretMode.InsertMode:
caretCreated = caretImplementation.Create(2, textArea.TextView.FontHeight);
break;
case CaretMode.OverwriteMode:
caretCreated = caretImplementation.Create((int)textArea.TextView.SpaceWidth, textArea.TextView.FontHeight);
break;
}
}
if (currentPos.X < 0) {
ValidateCaretPos();
currentPos = ScreenPosition;
}
caretImplementation.SetPosition(currentPos.X, currentPos.Y);
caretImplementation.Show();
}
public void RecreateCaret()
{
Log("RecreateCaret");
DisposeCaret();
if (!hidden) {
CreateCaret();
}
}
void DisposeCaret()
{
if (caretCreated) {
caretCreated = false;
caretImplementation.Hide();
caretImplementation.Destroy();
}
}
void GotFocus(object sender, EventArgs e)
{
Log("GotFocus, IsInUpdate=" + textArea.MotherTextEditorControl.IsInUpdate);
hidden = false;
if (!textArea.MotherTextEditorControl.IsInUpdate) {
CreateCaret();
UpdateCaretPosition();
}
}
void LostFocus(object sender, EventArgs e)
{
Log("LostFocus");
hidden = true;
DisposeCaret();
}
public Point ScreenPosition {
get {
int xpos = textArea.TextView.GetDrawingXPos(this.line, this.column);
return new Point(textArea.TextView.DrawingPosition.X + xpos,
textArea.TextView.DrawingPosition.Y
+ (textArea.Document.GetVisibleLine(this.line)) * textArea.TextView.FontHeight
- textArea.TextView.TextArea.VirtualTop.Y);
}
}
int oldLine = -1;
bool outstandingUpdate;
internal void OnEndUpdate()
{
if (outstandingUpdate)
UpdateCaretPosition();
}
void PaintCaretLine(Graphics g)
{
if (!textArea.Document.TextEditorProperties.CaretLine)
return;
HighlightColor caretLineColor = textArea.Document.HighlightingStrategy.GetColorFor("CaretLine");
g.DrawLine(BrushRegistry.GetDotPen(caretLineColor.Color),
currentPos.X,
0,
currentPos.X,
textArea.DisplayRectangle.Height);
}
public void UpdateCaretPosition()
{
Log("UpdateCaretPosition");
if (textArea.TextEditorProperties.CaretLine) {
textArea.Invalidate();
} else {
if (caretImplementation.RequireRedrawOnPositionChange) {
textArea.UpdateLine(oldLine);
if (line != oldLine)
textArea.UpdateLine(line);
} else {
if (textArea.MotherTextAreaControl.TextEditorProperties.LineViewerStyle == LineViewerStyle.FullRow && oldLine != line) {
textArea.UpdateLine(oldLine);
textArea.UpdateLine(line);
}
}
}
oldLine = line;
if (hidden || textArea.MotherTextEditorControl.IsInUpdate) {
outstandingUpdate = true;
return;
} else {
outstandingUpdate = false;
}
ValidateCaretPos();
int lineNr = this.line;
int xpos = textArea.TextView.GetDrawingXPos(lineNr, this.column);
//LineSegment lineSegment = textArea.Document.GetLineSegment(lineNr);
Point pos = ScreenPosition;
if (xpos >= 0) {
CreateCaret();
bool success = caretImplementation.SetPosition(pos.X, pos.Y);
if (!success) {
caretImplementation.Destroy();
caretCreated = false;
UpdateCaretPosition();
}
} else {
caretImplementation.Destroy();
}
// set the input method editor location
if (ime == null) {
ime = new Ime(textArea.Handle, textArea.Document.TextEditorProperties.Font);
} else {
ime.HWnd = textArea.Handle;
ime.Font = textArea.Document.TextEditorProperties.Font;
}
ime.SetIMEWindowLocation(pos.X, pos.Y);
currentPos = pos;
}
[Conditional("DEBUG")]
static void Log(string text)
{
//Console.WriteLine(text);
}
#region Caret implementation
internal void PaintCaret(Graphics g)
{
caretImplementation.PaintCaret(g);
PaintCaretLine(g);
}
abstract class CaretImplementation : IDisposable
{
public bool RequireRedrawOnPositionChange;
public abstract bool Create(int width, int height);
public abstract void Hide();
public abstract void Show();
public abstract bool SetPosition(int x, int y);
public abstract void PaintCaret(Graphics g);
public abstract void Destroy();
public virtual void Dispose()
{
Destroy();
}
}
class ManagedCaret : CaretImplementation
{
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer { Interval = 300 };
bool visible;
bool blink = true;
int x, y, width, height;
TextArea textArea;
Caret parentCaret;
public ManagedCaret(Caret caret)
{
base.RequireRedrawOnPositionChange = true;
this.textArea = caret.textArea;
this.parentCaret = caret;
timer.Tick += CaretTimerTick;
}
void CaretTimerTick(object sender, EventArgs e)
{
blink = !blink;
if (visible)
textArea.UpdateLine(parentCaret.Line);
}
public override bool Create(int width, int height)
{
this.visible = true;
this.width = width - 2;
this.height = height;
timer.Enabled = true;
return true;
}
public override void Hide()
{
visible = false;
}
public override void Show()
{
visible = true;
}
public override bool SetPosition(int x, int y)
{
this.x = x - 1;
this.y = y;
return true;
}
public override void PaintCaret(Graphics g)
{
if (visible && blink)
g.DrawRectangle(Pens.Gray, x, y, width, height);
}
public override void Destroy()
{
visible = false;
timer.Enabled = false;
}
public override void Dispose()
{
base.Dispose();
timer.Dispose();
}
}
class Win32Caret : CaretImplementation
{
[DllImport("User32.dll")]
static extern bool CreateCaret(IntPtr hWnd, int hBitmap, int nWidth, int nHeight);
[DllImport("User32.dll")]
static extern bool SetCaretPos(int x, int y);
[DllImport("User32.dll")]
static extern bool DestroyCaret();
[DllImport("User32.dll")]
static extern bool ShowCaret(IntPtr hWnd);
[DllImport("User32.dll")]
static extern bool HideCaret(IntPtr hWnd);
TextArea textArea;
public Win32Caret(Caret caret)
{
this.textArea = caret.textArea;
}
public override bool Create(int width, int height)
{
return CreateCaret(textArea.Handle, 0, width, height);
}
public override void Hide()
{
HideCaret(textArea.Handle);
}
public override void Show()
{
ShowCaret(textArea.Handle);
}
public override bool SetPosition(int x, int y)
{
return SetCaretPos(x, y);
}
public override void PaintCaret(Graphics g)
{
}
public override void Destroy()
{
DestroyCaret();
}
}
#endregion
bool firePositionChangedAfterUpdateEnd;
void FirePositionChangedAfterUpdateEnd(object sender, EventArgs e)
{
OnPositionChanged(EventArgs.Empty);
}
protected virtual void OnPositionChanged(EventArgs e)
{
if (this.textArea.MotherTextEditorControl.IsInUpdate) {
if (firePositionChangedAfterUpdateEnd == false) {
firePositionChangedAfterUpdateEnd = true;
this.textArea.Document.UpdateCommited += FirePositionChangedAfterUpdateEnd;
}
return;
} else if (firePositionChangedAfterUpdateEnd) {
this.textArea.Document.UpdateCommited -= FirePositionChangedAfterUpdateEnd;
firePositionChangedAfterUpdateEnd = false;
}
List<FoldMarker> foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(line, column);
bool shouldUpdate = false;
foreach (FoldMarker foldMarker in foldings) {
shouldUpdate |= foldMarker.IsFolded;
foldMarker.IsFolded = false;
}
if (shouldUpdate) {
textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);
}
if (PositionChanged != null) {
PositionChanged(this, e);
}
textArea.ScrollToCaret();
}
protected virtual void OnCaretModeChanged(EventArgs e)
{
if (CaretModeChanged != null) {
CaretModeChanged(this, e);
}
caretImplementation.Hide();
caretImplementation.Destroy();
caretCreated = false;
CreateCaret();
caretImplementation.Show();
}
/// <remarks>
/// Is called each time the caret is moved.
/// </remarks>
public event EventHandler PositionChanged;
/// <remarks>
/// Is called each time the CaretMode has changed.
/// </remarks>
public event EventHandler CaretModeChanged;
}
}

View File

@@ -0,0 +1,214 @@
// <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.Windows.Forms;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
/// <summary>
/// Description of AbstractCompletionWindow.
/// </summary>
public abstract class AbstractCompletionWindow : System.Windows.Forms.Form
{
protected TextEditorControl control;
protected Size drawingSize;
Rectangle workingScreen;
Form parentForm;
protected AbstractCompletionWindow(Form parentForm, TextEditorControl control)
{
workingScreen = Screen.GetWorkingArea(parentForm);
// SetStyle(ControlStyles.Selectable, false);
this.parentForm = parentForm;
this.control = control;
SetLocation();
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
MinimumSize = new Size(1, 1);
Size = new Size(1, 1);
}
protected virtual void SetLocation()
{
TextArea textArea = control.ActiveTextAreaControl.TextArea;
TextLocation caretPos = textArea.Caret.Position;
int xpos = textArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X);
int rulerHeight = textArea.TextEditorProperties.ShowHorizontalRuler ? textArea.TextView.FontHeight : 0;
Point pos = new Point(textArea.TextView.DrawingPosition.X + xpos,
textArea.TextView.DrawingPosition.Y + (textArea.Document.GetVisibleLine(caretPos.Y)) * textArea.TextView.FontHeight
- textArea.TextView.TextArea.VirtualTop.Y + textArea.TextView.FontHeight + rulerHeight);
Point location = control.ActiveTextAreaControl.PointToScreen(pos);
// set bounds
Rectangle bounds = new Rectangle(location, drawingSize);
if (!workingScreen.Contains(bounds)) {
if (bounds.Right > workingScreen.Right) {
bounds.X = workingScreen.Right - bounds.Width;
}
if (bounds.Left < workingScreen.Left) {
bounds.X = workingScreen.Left;
}
if (bounds.Top < workingScreen.Top) {
bounds.Y = workingScreen.Top;
}
if (bounds.Bottom > workingScreen.Bottom) {
bounds.Y = bounds.Y - bounds.Height - control.ActiveTextAreaControl.TextArea.TextView.FontHeight;
if (bounds.Bottom > workingScreen.Bottom) {
bounds.Y = workingScreen.Bottom - bounds.Height;
}
}
}
Bounds = bounds;
}
protected override CreateParams CreateParams {
get {
CreateParams p = base.CreateParams;
AddShadowToWindow(p);
return p;
}
}
static int shadowStatus;
/// <summary>
/// Adds a shadow to the create params if it is supported by the operating system.
/// </summary>
public static void AddShadowToWindow(CreateParams createParams)
{
if (shadowStatus == 0) {
// Test OS version
shadowStatus = -1; // shadow not supported
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
Version ver = Environment.OSVersion.Version;
if (ver.Major > 5 || ver.Major == 5 && ver.Minor >= 1) {
shadowStatus = 1;
}
}
}
if (shadowStatus == 1) {
createParams.ClassStyle |= 0x00020000; // set CS_DROPSHADOW
}
}
protected override bool ShowWithoutActivation {
get {
return true;
}
}
protected void ShowCompletionWindow()
{
Owner = parentForm;
Enabled = true;
this.Show();
control.Focus();
if (parentForm != null) {
parentForm.LocationChanged += new EventHandler(this.ParentFormLocationChanged);
}
control.ActiveTextAreaControl.VScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged);
control.ActiveTextAreaControl.HScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged);
control.ActiveTextAreaControl.TextArea.DoProcessDialogKey += new DialogKeyProcessor(ProcessTextAreaKey);
control.ActiveTextAreaControl.Caret.PositionChanged += new EventHandler(CaretOffsetChanged);
control.ActiveTextAreaControl.TextArea.LostFocus += new EventHandler(this.TextEditorLostFocus);
control.Resize += new EventHandler(ParentFormLocationChanged);
foreach (Control c in Controls) {
c.MouseMove += ControlMouseMove;
}
}
void ParentFormLocationChanged(object sender, EventArgs e)
{
SetLocation();
}
public virtual bool ProcessKeyEvent(char ch)
{
return false;
}
protected virtual bool ProcessTextAreaKey(Keys keyData)
{
if (!Visible) {
return false;
}
switch (keyData) {
case Keys.Escape:
Close();
return true;
}
return false;
}
protected virtual void CaretOffsetChanged(object sender, EventArgs e)
{
}
protected void TextEditorLostFocus(object sender, EventArgs e)
{
if (!control.ActiveTextAreaControl.TextArea.Focused && !this.ContainsFocus) {
Close();
}
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// take out the inserted methods
parentForm.LocationChanged -= new EventHandler(ParentFormLocationChanged);
foreach (Control c in Controls) {
c.MouseMove -= ControlMouseMove;
}
if (control.ActiveTextAreaControl.VScrollBar != null) {
control.ActiveTextAreaControl.VScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged);
}
if (control.ActiveTextAreaControl.HScrollBar != null) {
control.ActiveTextAreaControl.HScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged);
}
control.ActiveTextAreaControl.TextArea.LostFocus -= new EventHandler(this.TextEditorLostFocus);
control.ActiveTextAreaControl.Caret.PositionChanged -= new EventHandler(CaretOffsetChanged);
control.ActiveTextAreaControl.TextArea.DoProcessDialogKey -= new DialogKeyProcessor(ProcessTextAreaKey);
control.Resize -= new EventHandler(ParentFormLocationChanged);
Dispose();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
ControlMouseMove(this, e);
}
/// <summary>
/// Invoked when the mouse moves over this form or any child control.
/// Shows the mouse cursor on the text area if it has been hidden.
/// </summary>
/// <remarks>
/// Derived classes should attach this handler to the MouseMove event
/// of all created controls which are not added to the Controls
/// collection.
/// </remarks>
protected void ControlMouseMove(object sender, MouseEventArgs e)
{
control.ActiveTextAreaControl.TextArea.ShowHiddenCursor(false);
}
}
}

View File

@@ -0,0 +1,293 @@
// <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.Windows.Forms;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
/// <summary>
/// Description of CodeCompletionListView.
/// </summary>
public class CodeCompletionListView : System.Windows.Forms.UserControl
{
ICompletionData[] completionData;
int firstItem = 0;
int selectedItem = -1;
ImageList imageList;
public ImageList ImageList {
get {
return imageList;
}
set {
imageList = value;
}
}
public int FirstItem {
get {
return firstItem;
}
set {
if (firstItem != value) {
firstItem = value;
OnFirstItemChanged(EventArgs.Empty);
}
}
}
public ICompletionData SelectedCompletionData {
get {
if (selectedItem < 0) {
return null;
}
return completionData[selectedItem];
}
}
public int ItemHeight {
get {
return Math.Max(imageList.ImageSize.Height, (int)(Font.Height * 1.25));
}
}
public int MaxVisibleItem {
get {
return Height / ItemHeight;
}
}
public CodeCompletionListView(ICompletionData[] completionData)
{
Array.Sort(completionData, DefaultCompletionData.Compare);
this.completionData = completionData;
// this.KeyDown += new System.Windows.Forms.KeyEventHandler(OnKey);
// SetStyle(ControlStyles.Selectable, false);
// SetStyle(ControlStyles.UserPaint, true);
// SetStyle(ControlStyles.DoubleBuffer, false);
}
public void Close()
{
if (completionData != null) {
Array.Clear(completionData, 0, completionData.Length);
}
base.Dispose();
}
public void SelectIndex(int index)
{
int oldSelectedItem = selectedItem;
int oldFirstItem = firstItem;
index = Math.Max(0, index);
selectedItem = Math.Max(0, Math.Min(completionData.Length - 1, index));
if (selectedItem < firstItem) {
FirstItem = selectedItem;
}
if (firstItem + MaxVisibleItem <= selectedItem) {
FirstItem = selectedItem - MaxVisibleItem + 1;
}
if (oldSelectedItem != selectedItem) {
if (firstItem != oldFirstItem) {
Invalidate();
} else {
int min = Math.Min(selectedItem, oldSelectedItem) - firstItem;
int max = Math.Max(selectedItem, oldSelectedItem) - firstItem;
Invalidate(new Rectangle(0, 1 + min * ItemHeight, Width, (max - min + 1) * ItemHeight));
}
OnSelectedItemChanged(EventArgs.Empty);
}
}
public void CenterViewOn(int index)
{
int oldFirstItem = this.FirstItem;
int firstItem = index - MaxVisibleItem / 2;
if (firstItem < 0)
this.FirstItem = 0;
else if (firstItem >= completionData.Length - MaxVisibleItem)
this.FirstItem = completionData.Length - MaxVisibleItem;
else
this.FirstItem = firstItem;
if (this.FirstItem != oldFirstItem) {
Invalidate();
}
}
public void ClearSelection()
{
if (selectedItem < 0)
return;
int itemNum = selectedItem - firstItem;
selectedItem = -1;
Invalidate(new Rectangle(0, itemNum * ItemHeight, Width, (itemNum + 1) * ItemHeight + 1));
Update();
OnSelectedItemChanged(EventArgs.Empty);
}
public void PageDown()
{
SelectIndex(selectedItem + MaxVisibleItem);
}
public void PageUp()
{
SelectIndex(selectedItem - MaxVisibleItem);
}
public void SelectNextItem()
{
SelectIndex(selectedItem + 1);
}
public void SelectPrevItem()
{
SelectIndex(selectedItem - 1);
}
public void SelectItemWithStart(string startText)
{
if (startText == null || startText.Length == 0) return;
string originalStartText = startText;
startText = startText.ToLower();
int bestIndex = -1;
int bestQuality = -1;
// Qualities: 0 = match start
// 1 = match start case sensitive
// 2 = full match
// 3 = full match case sensitive
double bestPriority = 0;
for (int i = 0; i < completionData.Length; ++i) {
string itemText = completionData[i].Text;
string lowerText = itemText.ToLower();
if (lowerText.StartsWith(startText)) {
double priority = completionData[i].Priority;
int quality;
if (lowerText == startText) {
if (itemText == originalStartText)
quality = 3;
else
quality = 2;
} else if (itemText.StartsWith(originalStartText)) {
quality = 1;
} else {
quality = 0;
}
bool useThisItem;
if (bestQuality < quality) {
useThisItem = true;
} else {
if (bestIndex == selectedItem) {
useThisItem = false;
} else if (i == selectedItem) {
useThisItem = bestQuality == quality;
} else {
useThisItem = bestQuality == quality && bestPriority < priority;
}
}
if (useThisItem) {
bestIndex = i;
bestPriority = priority;
bestQuality = quality;
}
}
}
if (bestIndex < 0) {
ClearSelection();
} else {
if (bestIndex < firstItem || firstItem + MaxVisibleItem <= bestIndex) {
SelectIndex(bestIndex);
CenterViewOn(bestIndex);
} else {
SelectIndex(bestIndex);
}
}
}
protected override void OnPaint(PaintEventArgs pe)
{
float yPos = 1;
float itemHeight = ItemHeight;
// Maintain aspect ratio
int imageWidth = (int)(itemHeight * imageList.ImageSize.Width / imageList.ImageSize.Height);
int curItem = firstItem;
Graphics g = pe.Graphics;
while (curItem < completionData.Length && yPos < Height) {
RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight);
if (drawingBackground.IntersectsWith(pe.ClipRectangle)) {
// draw Background
if (curItem == selectedItem) {
g.FillRectangle(SystemBrushes.Highlight, drawingBackground);
} else {
g.FillRectangle(SystemBrushes.Window, drawingBackground);
}
// draw Icon
int xPos = 0;
if (imageList != null && completionData[curItem].ImageIndex < imageList.Images.Count) {
g.DrawImage(imageList.Images[completionData[curItem].ImageIndex], new RectangleF(1, yPos, imageWidth, itemHeight));
xPos = imageWidth;
}
// draw text
if (curItem == selectedItem) {
g.DrawString(completionData[curItem].Text, Font, SystemBrushes.HighlightText, xPos, yPos);
} else {
g.DrawString(completionData[curItem].Text, Font, SystemBrushes.WindowText, xPos, yPos);
}
}
yPos += itemHeight;
++curItem;
}
g.DrawRectangle(SystemPens.Control, new Rectangle(0, 0, Width - 1, Height - 1));
}
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
float yPos = 1;
int curItem = firstItem;
float itemHeight = ItemHeight;
while (curItem < completionData.Length && yPos < Height) {
RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight);
if (drawingBackground.Contains(e.X, e.Y)) {
SelectIndex(curItem);
break;
}
yPos += itemHeight;
++curItem;
}
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
}
protected virtual void OnSelectedItemChanged(EventArgs e)
{
if (SelectedItemChanged != null) {
SelectedItemChanged(this, e);
}
}
protected virtual void OnFirstItemChanged(EventArgs e)
{
if (FirstItemChanged != null) {
FirstItemChanged(this, e);
}
}
public event EventHandler SelectedItemChanged;
public event EventHandler FirstItemChanged;
}
}

View File

@@ -0,0 +1,364 @@
// <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.Diagnostics;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public class CodeCompletionWindow : AbstractCompletionWindow
{
ICompletionData[] completionData;
CodeCompletionListView codeCompletionListView;
VScrollBar vScrollBar = new VScrollBar();
ICompletionDataProvider dataProvider;
IDocument document;
bool showDeclarationWindow = true;
bool fixedListViewWidth = true;
const int ScrollbarWidth = 16;
const int MaxListLength = 10;
int startOffset;
int endOffset;
DeclarationViewWindow declarationViewWindow = null;
Rectangle workingScreen;
public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar)
{
return ShowCompletionWindow(parent, control, fileName, completionDataProvider, firstChar, true, true);
}
public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar, bool showDeclarationWindow, bool fixedListViewWidth)
{
ICompletionData[] completionData = completionDataProvider.GenerateCompletionData(fileName, control.ActiveTextAreaControl.TextArea, firstChar);
if (completionData == null || completionData.Length == 0) {
return null;
}
CodeCompletionWindow codeCompletionWindow = new CodeCompletionWindow(completionDataProvider, completionData, parent, control, showDeclarationWindow, fixedListViewWidth);
codeCompletionWindow.CloseWhenCaretAtBeginning = firstChar == '\0';
codeCompletionWindow.ShowCompletionWindow();
return codeCompletionWindow;
}
CodeCompletionWindow(ICompletionDataProvider completionDataProvider, ICompletionData[] completionData, Form parentForm, TextEditorControl control, bool showDeclarationWindow, bool fixedListViewWidth) : base(parentForm, control)
{
this.dataProvider = completionDataProvider;
this.completionData = completionData;
this.document = control.Document;
this.showDeclarationWindow = showDeclarationWindow;
this.fixedListViewWidth = fixedListViewWidth;
workingScreen = Screen.GetWorkingArea(Location);
startOffset = control.ActiveTextAreaControl.Caret.Offset + 1;
endOffset = startOffset;
if (completionDataProvider.PreSelection != null) {
startOffset -= completionDataProvider.PreSelection.Length + 1;
endOffset--;
}
codeCompletionListView = new CodeCompletionListView(completionData);
codeCompletionListView.ImageList = completionDataProvider.ImageList;
codeCompletionListView.Dock = DockStyle.Fill;
codeCompletionListView.SelectedItemChanged += new EventHandler(CodeCompletionListViewSelectedItemChanged);
codeCompletionListView.DoubleClick += new EventHandler(CodeCompletionListViewDoubleClick);
codeCompletionListView.Click += new EventHandler(CodeCompletionListViewClick);
Controls.Add(codeCompletionListView);
if (completionData.Length > MaxListLength) {
vScrollBar.Dock = DockStyle.Right;
vScrollBar.Minimum = 0;
vScrollBar.Maximum = completionData.Length - 1;
vScrollBar.SmallChange = 1;
vScrollBar.LargeChange = MaxListLength;
codeCompletionListView.FirstItemChanged += new EventHandler(CodeCompletionListViewFirstItemChanged);
Controls.Add(vScrollBar);
}
this.drawingSize = GetListViewSize();
SetLocation();
if (declarationViewWindow == null) {
declarationViewWindow = new DeclarationViewWindow(parentForm);
}
SetDeclarationViewLocation();
declarationViewWindow.ShowDeclarationViewWindow();
declarationViewWindow.MouseMove += ControlMouseMove;
control.Focus();
CodeCompletionListViewSelectedItemChanged(this, EventArgs.Empty);
if (completionDataProvider.DefaultIndex >= 0) {
codeCompletionListView.SelectIndex(completionDataProvider.DefaultIndex);
}
if (completionDataProvider.PreSelection != null) {
CaretOffsetChanged(this, EventArgs.Empty);
}
vScrollBar.ValueChanged += VScrollBarValueChanged;
document.DocumentAboutToBeChanged += DocumentAboutToBeChanged;
}
bool inScrollUpdate;
void CodeCompletionListViewFirstItemChanged(object sender, EventArgs e)
{
if (inScrollUpdate) return;
inScrollUpdate = true;
vScrollBar.Value = Math.Min(vScrollBar.Maximum, codeCompletionListView.FirstItem);
inScrollUpdate = false;
}
void VScrollBarValueChanged(object sender, EventArgs e)
{
if (inScrollUpdate) return;
inScrollUpdate = true;
codeCompletionListView.FirstItem = vScrollBar.Value;
codeCompletionListView.Refresh();
control.ActiveTextAreaControl.TextArea.Focus();
inScrollUpdate = false;
}
void SetDeclarationViewLocation()
{
// This method uses the side with more free space
int leftSpace = Bounds.Left - workingScreen.Left;
int rightSpace = workingScreen.Right - Bounds.Right;
Point pos;
// The declaration view window has better line break when used on
// the right side, so prefer the right side to the left.
if (rightSpace * 2 > leftSpace) {
declarationViewWindow.FixedWidth = false;
pos = new Point(Bounds.Right, Bounds.Top);
if (declarationViewWindow.Location != pos) {
declarationViewWindow.Location = pos;
}
} else {
declarationViewWindow.Width = declarationViewWindow.GetRequiredLeftHandSideWidth(new Point(Bounds.Left, Bounds.Top));
declarationViewWindow.FixedWidth = true;
if (Bounds.Left < declarationViewWindow.Width) {
pos = new Point(0, Bounds.Top);
} else {
pos = new Point(Bounds.Left - declarationViewWindow.Width, Bounds.Top);
}
if (declarationViewWindow.Location != pos) {
declarationViewWindow.Location = pos;
}
declarationViewWindow.Refresh();
}
}
protected override void SetLocation()
{
base.SetLocation();
if (declarationViewWindow != null) {
SetDeclarationViewLocation();
}
}
Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();
public void HandleMouseWheel(MouseEventArgs e)
{
int scrollDistance = mouseWheelHandler.GetScrollAmount(e);
if (scrollDistance == 0)
return;
if (control.TextEditorProperties.MouseWheelScrollDown)
scrollDistance = -scrollDistance;
int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
}
void CodeCompletionListViewSelectedItemChanged(object sender, EventArgs e)
{
ICompletionData data = codeCompletionListView.SelectedCompletionData;
if (showDeclarationWindow && data != null && data.Description != null && data.Description.Length > 0) {
declarationViewWindow.Description = data.Description;
SetDeclarationViewLocation();
} else {
declarationViewWindow.Description = null;
}
}
public override bool ProcessKeyEvent(char ch)
{
switch (dataProvider.ProcessKey(ch)) {
case CompletionDataProviderKeyResult.BeforeStartKey:
// increment start+end, then process as normal char
++startOffset;
++endOffset;
return base.ProcessKeyEvent(ch);
case CompletionDataProviderKeyResult.NormalKey:
// just process normally
return base.ProcessKeyEvent(ch);
case CompletionDataProviderKeyResult.InsertionKey:
return InsertSelectedItem(ch);
default:
throw new InvalidOperationException("Invalid return value of dataProvider.ProcessKey");
}
}
void DocumentAboutToBeChanged(object sender, DocumentEventArgs e)
{
// => startOffset test required so that this startOffset/endOffset are not incremented again
// for BeforeStartKey characters
if (e.Offset >= startOffset && e.Offset <= endOffset) {
if (e.Length > 0) { // length of removed region
endOffset -= e.Length;
}
if (!string.IsNullOrEmpty(e.Text)) {
endOffset += e.Text.Length;
}
}
}
/// <summary>
/// When this flag is set, code completion closes if the caret moves to the
/// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing",
/// but not in dot-completion.
/// </summary>
public bool CloseWhenCaretAtBeginning { get; set; }
protected override void CaretOffsetChanged(object sender, EventArgs e)
{
int offset = control.ActiveTextAreaControl.Caret.Offset;
if (offset == startOffset) {
if (CloseWhenCaretAtBeginning)
Close();
return;
}
if (offset < startOffset || offset > endOffset) {
Close();
} else {
codeCompletionListView.SelectItemWithStart(control.Document.GetText(startOffset, offset - startOffset));
}
}
protected override bool ProcessTextAreaKey(Keys keyData)
{
if (!Visible) {
return false;
}
switch (keyData) {
case Keys.Home:
codeCompletionListView.SelectIndex(0);
return true;
case Keys.End:
codeCompletionListView.SelectIndex(completionData.Length-1);
return true;
case Keys.PageDown:
codeCompletionListView.PageDown();
return true;
case Keys.PageUp:
codeCompletionListView.PageUp();
return true;
case Keys.Down:
codeCompletionListView.SelectNextItem();
return true;
case Keys.Up:
codeCompletionListView.SelectPrevItem();
return true;
case Keys.Tab:
InsertSelectedItem('\t');
return true;
case Keys.Return:
InsertSelectedItem('\n');
return true;
}
return base.ProcessTextAreaKey(keyData);
}
void CodeCompletionListViewDoubleClick(object sender, EventArgs e)
{
InsertSelectedItem('\0');
}
void CodeCompletionListViewClick(object sender, EventArgs e)
{
control.ActiveTextAreaControl.TextArea.Focus();
}
protected override void Dispose(bool disposing)
{
if (disposing) {
document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged;
if (codeCompletionListView != null) {
codeCompletionListView.Dispose();
codeCompletionListView = null;
}
if (declarationViewWindow != null) {
declarationViewWindow.Dispose();
declarationViewWindow = null;
}
}
base.Dispose(disposing);
}
bool InsertSelectedItem(char ch)
{
document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged;
ICompletionData data = codeCompletionListView.SelectedCompletionData;
bool result = false;
if (data != null) {
control.BeginUpdate();
try {
if (endOffset - startOffset > 0) {
control.Document.Remove(startOffset, endOffset - startOffset);
}
Debug.Assert(startOffset <= document.TextLength);
result = dataProvider.InsertAction(data, control.ActiveTextAreaControl.TextArea, startOffset, ch);
} finally {
control.EndUpdate();
}
}
Close();
return result;
}
Size GetListViewSize()
{
int height = codeCompletionListView.ItemHeight * Math.Min(MaxListLength, completionData.Length);
int width = codeCompletionListView.ItemHeight * 10;
if (!fixedListViewWidth) {
width = GetListViewWidth(width, height);
}
return new Size(width, height);
}
/// <summary>
/// Gets the list view width large enough to handle the longest completion data
/// text string.
/// </summary>
/// <param name="defaultWidth">The default width of the list view.</param>
/// <param name="height">The height of the list view. This is
/// used to determine if the scrollbar is visible.</param>
/// <returns>The list view width to accommodate the longest completion
/// data text string; otherwise the default width.</returns>
int GetListViewWidth(int defaultWidth, int height)
{
float width = defaultWidth;
using (Graphics graphics = codeCompletionListView.CreateGraphics()) {
for (int i = 0; i < completionData.Length; ++i) {
float itemWidth = graphics.MeasureString(completionData[i].Text.ToString(), codeCompletionListView.Font).Width;
if(itemWidth > width) {
width = itemWidth;
}
}
}
float totalItemsHeight = codeCompletionListView.ItemHeight * completionData.Length;
if (totalItemsHeight > height) {
width += ScrollbarWidth; // Compensate for scroll bar.
}
return (int)width;
}
}
}

View File

@@ -0,0 +1,125 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public interface IDeclarationViewWindow
{
string Description {
get;
set;
}
void ShowDeclarationViewWindow();
void CloseDeclarationViewWindow();
}
public class DeclarationViewWindow : Form, IDeclarationViewWindow
{
string description = string.Empty;
bool fixedWidth;
public string Description {
get {
return description;
}
set {
description = value;
if (value == null && Visible) {
Visible = false;
} else if (value != null) {
if (!Visible) ShowDeclarationViewWindow();
Refresh();
}
}
}
public bool FixedWidth {
get {
return fixedWidth;
}
set {
fixedWidth = value;
}
}
public int GetRequiredLeftHandSideWidth(Point p) {
if (description != null && description.Length > 0) {
using (Graphics g = CreateGraphics()) {
Size s = TipPainterTools.GetLeftHandSideDrawingSizeHelpTipFromCombinedDescription(this, g, Font, null, description, p);
return s.Width;
}
}
return 0;
}
public bool HideOnClick;
public DeclarationViewWindow(Form parent)
{
SetStyle(ControlStyles.Selectable, false);
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
Owner = parent;
ShowInTaskbar = false;
Size = new Size(0, 0);
base.CreateHandle();
}
protected override CreateParams CreateParams {
get {
CreateParams p = base.CreateParams;
AbstractCompletionWindow.AddShadowToWindow(p);
return p;
}
}
protected override bool ShowWithoutActivation {
get {
return true;
}
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
if (HideOnClick) Hide();
}
public void ShowDeclarationViewWindow()
{
Show();
}
public void CloseDeclarationViewWindow()
{
Close();
Dispose();
}
protected override void OnPaint(PaintEventArgs pe)
{
if (description != null && description.Length > 0) {
if (fixedWidth) {
TipPainterTools.DrawFixedWidthHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description);
} else {
TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description);
}
}
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle);
}
}
}

View File

@@ -0,0 +1,114 @@
// <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.Gui.CompletionWindow
{
public interface ICompletionData
{
int ImageIndex {
get;
}
string Text {
get;
set;
}
string Description {
get;
}
/// <summary>
/// Gets a priority value for the completion data item.
/// When selecting items by their start characters, the item with the highest
/// priority is selected first.
/// </summary>
double Priority {
get;
}
/// <summary>
/// Insert the element represented by the completion data into the text
/// editor.
/// </summary>
/// <param name="textArea">TextArea to insert the completion data in.</param>
/// <param name="ch">Character that should be inserted after the completion data.
/// \0 when no character should be inserted.</param>
/// <returns>Returns true when the insert action has processed the character
/// <paramref name="ch"/>; false when the character was not processed.</returns>
bool InsertAction(TextArea textArea, char ch);
}
public class DefaultCompletionData : ICompletionData
{
string text;
string description;
int imageIndex;
public int ImageIndex {
get {
return imageIndex;
}
}
public string Text {
get {
return text;
}
set {
text = value;
}
}
public virtual string Description {
get {
return description;
}
}
double priority;
public double Priority {
get {
return priority;
}
set {
priority = value;
}
}
public virtual bool InsertAction(TextArea textArea, char ch)
{
textArea.InsertString(text);
return false;
}
public DefaultCompletionData(string text, int imageIndex)
{
this.text = text;
this.imageIndex = imageIndex;
}
public DefaultCompletionData(string text, string description, int imageIndex)
{
this.text = text;
this.description = description;
this.imageIndex = imageIndex;
}
public static int Compare(ICompletionData a, ICompletionData b)
{
if (a == null)
throw new ArgumentNullException("a");
if (b == null)
throw new ArgumentNullException("b");
return string.Compare(a.Text, b.Text, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@@ -0,0 +1,62 @@
// <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.Windows.Forms;
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
{
public interface ICompletionDataProvider
{
ImageList ImageList {
get;
}
string PreSelection {
get;
}
/// <summary>
/// Gets the index of the element in the list that is chosen by default.
/// </summary>
int DefaultIndex {
get;
}
/// <summary>
/// Processes a keypress. Returns the action to be run with the key.
/// </summary>
CompletionDataProviderKeyResult ProcessKey(char key);
/// <summary>
/// Executes the insertion. The provider should set the caret position and then
/// call data.InsertAction.
/// </summary>
bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key);
/// <summary>
/// Generates the completion data. This method is called by the text editor control.
/// </summary>
ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped);
}
public enum CompletionDataProviderKeyResult
{
/// <summary>
/// Normal key, used to choose an entry from the completion list
/// </summary>
NormalKey,
/// <summary>
/// This key triggers insertion of the completed expression
/// </summary>
InsertionKey,
/// <summary>
/// Increment both start and end offset of completion region when inserting this
/// key. Can be used to insert whitespace (or other characters) in front of the expression
/// while the completion window is open.
/// </summary>
BeforeStartKey
}
}

View File

@@ -0,0 +1,190 @@
// <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.Drawing;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// A class that is able to draw a line on any control (outside the text editor)
/// </summary>
public class DrawableLine
{
static StringFormat sf = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();
List<SimpleTextWord> words = new List<SimpleTextWord>();
SizeF spaceSize;
Font monospacedFont;
Font boldMonospacedFont;
private class SimpleTextWord {
internal TextWordType Type;
internal string Word;
internal bool Bold;
internal Color Color;
public SimpleTextWord(TextWordType Type, string Word, bool Bold, Color Color)
{
this.Type = Type;
this.Word = Word;
this.Bold = Bold;
this.Color = Color;
}
internal readonly static SimpleTextWord Space = new SimpleTextWord(TextWordType.Space, " ", false, Color.Black);
internal readonly static SimpleTextWord Tab = new SimpleTextWord(TextWordType.Tab, "\t", false, Color.Black);
}
public DrawableLine(IDocument document, LineSegment line, Font monospacedFont, Font boldMonospacedFont)
{
this.monospacedFont = monospacedFont;
this.boldMonospacedFont = boldMonospacedFont;
if (line.Words != null) {
foreach (TextWord word in line.Words) {
if (word.Type == TextWordType.Space) {
words.Add(SimpleTextWord.Space);
} else if (word.Type == TextWordType.Tab) {
words.Add(SimpleTextWord.Tab);
} else {
words.Add(new SimpleTextWord(TextWordType.Word, word.Word, word.Bold, word.Color));
}
}
} else {
words.Add(new SimpleTextWord(TextWordType.Word, document.GetText(line), false, Color.Black));
}
}
public int LineLength {
get {
int length = 0;
foreach (SimpleTextWord word in words) {
length += word.Word.Length;
}
return length;
}
}
public void SetBold(int startIndex, int endIndex, bool bold)
{
if (startIndex < 0)
throw new ArgumentException("startIndex must be >= 0");
if (startIndex > endIndex)
throw new ArgumentException("startIndex must be <= endIndex");
if (startIndex == endIndex) return;
int pos = 0;
for (int i = 0; i < words.Count; i++) {
SimpleTextWord word = words[i];
if (pos >= endIndex)
break;
int wordEnd = pos + word.Word.Length;
// 3 possibilities:
if (startIndex <= pos && endIndex >= wordEnd) {
// word is fully in region:
word.Bold = bold;
} else if (startIndex <= pos) {
// beginning of word is in region
int inRegionLength = endIndex - pos;
SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(inRegionLength), word.Bold, word.Color);
words.Insert(i + 1, newWord);
word.Bold = bold;
word.Word = word.Word.Substring(0, inRegionLength);
} else if (startIndex < wordEnd) {
// end of word is in region (or middle of word is in region)
int notInRegionLength = startIndex - pos;
SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(notInRegionLength), word.Bold, word.Color);
// newWord.Bold will be set in the next iteration
words.Insert(i + 1, newWord);
word.Word = word.Word.Substring(0, notInRegionLength);
}
pos = wordEnd;
}
}
public static float DrawDocumentWord(Graphics g, string word, PointF position, Font font, Color foreColor)
{
if (word == null || word.Length == 0) {
return 0f;
}
SizeF wordSize = g.MeasureString(word, font, 32768, sf);
g.DrawString(word,
font,
BrushRegistry.GetBrush(foreColor),
position,
sf);
return wordSize.Width;
}
public SizeF GetSpaceSize(Graphics g)
{
if (spaceSize.IsEmpty) {
spaceSize = g.MeasureString("-", boldMonospacedFont, new PointF(0, 0), sf);
}
return spaceSize;
}
public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos, Color c)
{
SizeF spaceSize = GetSpaceSize(g);
foreach (SimpleTextWord word in words) {
switch (word.Type) {
case TextWordType.Space:
xPos += spaceSize.Width;
break;
case TextWordType.Tab:
float tabWidth = spaceSize.Width * 4;
xPos += tabWidth;
xPos = (int)((xPos + 2) / tabWidth) * tabWidth;
break;
case TextWordType.Word:
xPos += DrawDocumentWord(g,
word.Word,
new PointF(xPos + xOffset, yPos),
word.Bold ? boldMonospacedFont : monospacedFont,
c == Color.Empty ? word.Color : c
);
break;
}
}
}
public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos)
{
DrawLine(g, ref xPos, xOffset, yPos, Color.Empty);
}
public float MeasureWidth(Graphics g, float xPos)
{
SizeF spaceSize = GetSpaceSize(g);
foreach (SimpleTextWord word in words) {
switch (word.Type) {
case TextWordType.Space:
xPos += spaceSize.Width;
break;
case TextWordType.Tab:
float tabWidth = spaceSize.Width * 4;
xPos += tabWidth;
xPos = (int)((xPos + 2) / tabWidth) * tabWidth;
break;
case TextWordType.Word:
if (word.Word != null && word.Word.Length > 0) {
xPos += g.MeasureString(word.Word, word.Bold ? boldMonospacedFont : monospacedFont, 32768, sf).Width;
}
break;
}
}
return xPos;
}
}
}

View File

@@ -0,0 +1,276 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public class FoldMargin : AbstractMargin
{
int selectedFoldLine = -1;
public override Size Size {
get {
return new Size((int)(textArea.TextView.FontHeight),
-1);
}
}
public override bool IsVisible {
get {
return textArea.TextEditorProperties.EnableFolding;
}
}
public FoldMargin(TextArea textArea) : base(textArea)
{
}
public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0) {
return;
}
HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers");
for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / textArea.TextView.FontHeight + 1; ++y) {
Rectangle markerRectangle = new Rectangle(DrawingPosition.X,
DrawingPosition.Top + y * textArea.TextView.FontHeight - textArea.TextView.VisibleLineDrawingRemainder,
DrawingPosition.Width,
textArea.TextView.FontHeight);
if (rect.IntersectsWith(markerRectangle)) {
// draw dotted separator line
if (textArea.Document.TextEditorProperties.ShowLineNumbers) {
g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder),
markerRectangle);
g.DrawLine(BrushRegistry.GetDotPen(lineNumberPainterColor.Color),
base.drawingPosition.X,
markerRectangle.Y,
base.drawingPosition.X,
markerRectangle.Bottom);
} else {
g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder), markerRectangle);
}
int currentLine = textArea.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + y);
if (currentLine < textArea.Document.TotalNumberOfLines) {
PaintFoldMarker(g, currentLine, markerRectangle);
}
}
}
}
bool SelectedFoldingFrom(List<FoldMarker> list)
{
if (list != null) {
for (int i = 0; i < list.Count; ++i) {
if (this.selectedFoldLine == list[i].StartLine) {
return true;
}
}
}
return false;
}
void PaintFoldMarker(Graphics g, int lineNumber, Rectangle drawingRectangle)
{
HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");
HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine");
List<FoldMarker> foldingsWithStart = textArea.Document.FoldingManager.GetFoldingsWithStart(lineNumber);
List<FoldMarker> foldingsBetween = textArea.Document.FoldingManager.GetFoldingsContainsLineNumber(lineNumber);
List<FoldMarker> foldingsWithEnd = textArea.Document.FoldingManager.GetFoldingsWithEnd(lineNumber);
bool isFoldStart = foldingsWithStart.Count > 0;
bool isBetween = foldingsBetween.Count > 0;
bool isFoldEnd = foldingsWithEnd.Count > 0;
bool isStartSelected = SelectedFoldingFrom(foldingsWithStart);
bool isBetweenSelected = SelectedFoldingFrom(foldingsBetween);
bool isEndSelected = SelectedFoldingFrom(foldingsWithEnd);
int foldMarkerSize = (int)Math.Round(textArea.TextView.FontHeight * 0.57f);
foldMarkerSize -= (foldMarkerSize) % 2;
int foldMarkerYPos = drawingRectangle.Y + (int)((drawingRectangle.Height - foldMarkerSize) / 2);
int xPos = drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2 + foldMarkerSize / 2;
if (isFoldStart) {
bool isVisible = true;
bool moreLinedOpenFold = false;
foreach (FoldMarker foldMarker in foldingsWithStart) {
if (foldMarker.IsFolded) {
isVisible = false;
} else {
moreLinedOpenFold = foldMarker.EndLine > foldMarker.StartLine;
}
}
bool isFoldEndFromUpperFold = false;
foreach (FoldMarker foldMarker in foldingsWithEnd) {
if (foldMarker.EndLine > foldMarker.StartLine && !foldMarker.IsFolded) {
isFoldEndFromUpperFold = true;
}
}
DrawFoldMarker(g, new RectangleF(drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2,
foldMarkerYPos,
foldMarkerSize,
foldMarkerSize),
isVisible,
isStartSelected
);
// draw line above fold marker
if (isBetween || isFoldEndFromUpperFold) {
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
drawingRectangle.Top,
xPos,
foldMarkerYPos - 1);
}
// draw line below fold marker
if (isBetween || moreLinedOpenFold) {
g.DrawLine(BrushRegistry.GetPen(isEndSelected || (isStartSelected && isVisible) || isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
foldMarkerYPos + foldMarkerSize + 1,
xPos,
drawingRectangle.Bottom);
}
} else {
if (isFoldEnd) {
int midy = drawingRectangle.Top + drawingRectangle.Height / 2;
// draw fold end marker
g.DrawLine(BrushRegistry.GetPen(isEndSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
midy,
xPos + foldMarkerSize / 2,
midy);
// draw line above fold end marker
// must be drawn after fold marker because it might have a different color than the fold marker
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected || isEndSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
drawingRectangle.Top,
xPos,
midy);
// draw line below fold end marker
if (isBetween) {
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
midy + 1,
xPos,
drawingRectangle.Bottom);
}
} else if (isBetween) {
// just draw the line :)
g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
xPos,
drawingRectangle.Top,
xPos,
drawingRectangle.Bottom);
}
}
}
public override void HandleMouseMove(Point mousepos, MouseButtons mouseButtons)
{
bool showFolding = textArea.Document.TextEditorProperties.EnableFolding;
int physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight);
int realline = textArea.Document.GetFirstLogicalLine(physicalLine);
if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) {
return;
}
List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline);
int oldSelection = selectedFoldLine;
if (foldMarkers.Count > 0) {
selectedFoldLine = realline;
} else {
selectedFoldLine = -1;
}
if (oldSelection != selectedFoldLine) {
textArea.Refresh(this);
}
}
public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
{
bool showFolding = textArea.Document.TextEditorProperties.EnableFolding;
int physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight);
int realline = textArea.Document.GetFirstLogicalLine(physicalLine);
// focus the textarea if the user clicks on the line number view
textArea.Focus();
if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) {
return;
}
List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline);
foreach (FoldMarker fm in foldMarkers) {
fm.IsFolded = !fm.IsFolded;
}
textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);
}
public override void HandleMouseLeave(EventArgs e)
{
if (selectedFoldLine != -1) {
selectedFoldLine = -1;
textArea.Refresh(this);
}
}
#region Drawing functions
void DrawFoldMarker(Graphics g, RectangleF rectangle, bool isOpened, bool isSelected)
{
HighlightColor foldMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldMarker");
HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");
HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine");
Rectangle intRect = new Rectangle((int)rectangle.X, (int)rectangle.Y, (int)rectangle.Width, (int)rectangle.Height);
g.FillRectangle(BrushRegistry.GetBrush(foldMarkerColor.BackgroundColor), intRect);
g.DrawRectangle(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldLineColor.Color), intRect);
int space = (int)Math.Round(((double)rectangle.Height) / 8d) + 1;
int mid = intRect.Height / 2 + intRect.Height % 2;
// draw minus
g.DrawLine(BrushRegistry.GetPen(foldMarkerColor.Color),
rectangle.X + space,
rectangle.Y + mid,
rectangle.X + rectangle.Width - space,
rectangle.Y + mid);
// draw plus
if (!isOpened) {
g.DrawLine(BrushRegistry.GetPen(foldMarkerColor.Color),
rectangle.X + mid,
rectangle.Y + space,
rectangle.X + mid,
rectangle.Y + rectangle.Height - space);
}
}
#endregion
}
}

View File

@@ -0,0 +1,159 @@
// <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.IO;
using System.Reflection;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public class GutterMargin : AbstractMargin, IDisposable
{
StringFormat numberStringFormat = (StringFormat)StringFormat.GenericTypographic.Clone();
public static Cursor RightLeftCursor;
static GutterMargin()
{
using (var ms = new MemoryStream(Properties.Resources.RightArrow))
{
RightLeftCursor = new Cursor(ms);
}
}
public void Dispose()
{
numberStringFormat.Dispose();
}
public override Cursor Cursor {
get {
return RightLeftCursor;
}
}
public override Size Size {
get {
return new Size((int)(textArea.TextView.WideSpaceWidth
* Math.Max(3, (int)Math.Log10(textArea.Document.TotalNumberOfLines) + 1)),
-1);
}
}
public override bool IsVisible {
get {
return textArea.TextEditorProperties.ShowLineNumbers;
}
}
public GutterMargin(TextArea textArea) : base(textArea)
{
numberStringFormat.LineAlignment = StringAlignment.Far;
numberStringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.FitBlackBox |
StringFormatFlags.NoWrap | StringFormatFlags.NoClip;
}
public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0) {
return;
}
HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers");
int fontHeight = textArea.TextView.FontHeight;
Brush fillBrush = textArea.Enabled ? BrushRegistry.GetBrush(lineNumberPainterColor.BackgroundColor) : SystemBrushes.InactiveBorder;
Brush drawBrush = BrushRegistry.GetBrush(lineNumberPainterColor.Color);
for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / fontHeight + 1; ++y) {
int ypos = drawingPosition.Y + fontHeight * y - textArea.TextView.VisibleLineDrawingRemainder;
Rectangle backgroundRectangle = new Rectangle(drawingPosition.X, ypos, drawingPosition.Width, fontHeight);
if (rect.IntersectsWith(backgroundRectangle)) {
g.FillRectangle(fillBrush, backgroundRectangle);
int curLine = textArea.Document.GetFirstLogicalLine(textArea.Document.GetVisibleLine(textArea.TextView.FirstVisibleLine) + y);
if (curLine < textArea.Document.TotalNumberOfLines) {
g.DrawString((curLine + 1).ToString(),
lineNumberPainterColor.GetFont(TextEditorProperties.FontContainer),
drawBrush,
backgroundRectangle,
numberStringFormat);
}
}
}
}
public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
{
TextLocation selectionStartPos;
textArea.SelectionManager.selectFrom.where = WhereFrom.Gutter;
int realline = textArea.TextView.GetLogicalLine(mousepos.Y);
if (realline >= 0 && realline < textArea.Document.TotalNumberOfLines) {
// shift-select
if((Control.ModifierKeys & Keys.Shift) != 0) {
if(!textArea.SelectionManager.HasSomethingSelected && realline != textArea.Caret.Position.Y) {
if (realline >= textArea.Caret.Position.Y)
{ // at or below starting selection, place the cursor on the next line
// nothing is selected so make a new selection from cursor
selectionStartPos = textArea.Caret.Position;
// whole line selection - start of line to start of next line
if (realline < textArea.Document.TotalNumberOfLines - 1)
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(0, realline + 1)));
textArea.Caret.Position = new TextLocation(0, realline + 1);
}
else
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, realline)));
textArea.Caret.Position = new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, realline);
}
}
else
{ // prior lines to starting selection, place the cursor on the same line as the new selection
// nothing is selected so make a new selection from cursor
selectionStartPos = textArea.Caret.Position;
// whole line selection - start of line to start of next line
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(selectionStartPos.X, selectionStartPos.Y)));
textArea.SelectionManager.ExtendSelection(new TextLocation(selectionStartPos.X, selectionStartPos.Y), new TextLocation(0, realline));
textArea.Caret.Position = new TextLocation(0, realline);
}
}
else
{
// let MouseMove handle a shift-click in a gutter
MouseEventArgs e = new MouseEventArgs(mouseButtons, 1, mousepos.X, mousepos.Y, 0);
textArea.RaiseMouseMove(e);
}
} else { // this is a new selection with no shift-key
// sync the textareamousehandler mouse location
// (fixes problem with clicking out into a menu then back to the gutter whilst
// there is a selection)
textArea.mousepos = mousepos;
selectionStartPos = new TextLocation(0, realline);
textArea.SelectionManager.ClearSelection();
// whole line selection - start of line to start of next line
if (realline < textArea.Document.TotalNumberOfLines - 1)
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(selectionStartPos.X, selectionStartPos.Y + 1)));
textArea.Caret.Position = new TextLocation(selectionStartPos.X, selectionStartPos.Y + 1);
}
else
{
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, new TextLocation(0, realline), new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, selectionStartPos.Y)));
textArea.Caret.Position = new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, selectionStartPos.Y);
}
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
// <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.Windows.Forms;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// Horizontal ruler - text column measuring ruler at the top of the text area.
/// </summary>
public class HRuler : Control
{
TextArea textArea;
public HRuler(TextArea textArea)
{
this.textArea = textArea;
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
int num = 0;
for (float x = textArea.TextView.DrawingPosition.Left; x < textArea.TextView.DrawingPosition.Right; x += textArea.TextView.WideSpaceWidth) {
int offset = (Height * 2) / 3;
if (num % 5 == 0) {
offset = (Height * 4) / 5;
}
if (num % 10 == 0) {
offset = 1;
}
++num;
g.DrawLine(Pens.Black,
(int)x, offset, (int)x, Height - offset);
}
}
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.White,
new Rectangle(0,
0,
Width,
Height));
}
}
}

View File

@@ -0,0 +1,254 @@
// <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.Drawing.Drawing2D;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class views the line numbers and folding markers.
/// </summary>
public class IconBarMargin : AbstractMargin
{
const int iconBarWidth = 18;
static readonly Size iconBarSize = new Size(iconBarWidth, -1);
public override Size Size {
get {
return iconBarSize;
}
}
public override bool IsVisible {
get {
return textArea.TextEditorProperties.IsIconBarVisible;
}
}
public IconBarMargin(TextArea textArea) : base(textArea)
{
}
public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0) {
return;
}
// paint background
g.FillRectangle(SystemBrushes.Control, new Rectangle(drawingPosition.X, rect.Top, drawingPosition.Width - 1, rect.Height));
g.DrawLine(SystemPens.ControlDark, base.drawingPosition.Right - 1, rect.Top, base.drawingPosition.Right - 1, rect.Bottom);
// paint icons
foreach (Bookmark mark in textArea.Document.BookmarkManager.Marks) {
int lineNumber = textArea.Document.GetVisibleLine(mark.LineNumber);
int lineHeight = textArea.TextView.FontHeight;
int yPos = (int)(lineNumber * lineHeight) - textArea.VirtualTop.Y;
if (IsLineInsideRegion(yPos, yPos + lineHeight, rect.Y, rect.Bottom)) {
if (lineNumber == textArea.Document.GetVisibleLine(mark.LineNumber - 1)) {
// marker is inside folded region, do not draw it
continue;
}
mark.Draw(this, g, new Point(0, yPos));
}
}
base.Paint(g, rect);
}
public override void HandleMouseDown(Point mousePos, MouseButtons mouseButtons)
{
int clickedVisibleLine = (mousePos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight;
int lineNumber = textArea.Document.GetFirstLogicalLine(clickedVisibleLine);
if ((mouseButtons & MouseButtons.Right) == MouseButtons.Right) {
if (textArea.Caret.Line != lineNumber) {
textArea.Caret.Line = lineNumber;
}
}
IList<Bookmark> marks = textArea.Document.BookmarkManager.Marks;
List<Bookmark> marksInLine = new List<Bookmark>();
int oldCount = marks.Count;
foreach (Bookmark mark in marks) {
if (mark.LineNumber == lineNumber) {
marksInLine.Add(mark);
}
}
for (int i = marksInLine.Count - 1; i >= 0; i--) {
Bookmark mark = marksInLine[i];
if (mark.Click(textArea, new MouseEventArgs(mouseButtons, 1, mousePos.X, mousePos.Y, 0))) {
if (oldCount != marks.Count) {
textArea.UpdateLine(lineNumber);
}
return;
}
}
base.HandleMouseDown(mousePos, mouseButtons);
}
#region Drawing functions
public void DrawBreakpoint(Graphics g, int y, bool isEnabled, bool isHealthy)
{
int diameter = Math.Min(iconBarWidth - 2, textArea.TextView.FontHeight);
Rectangle rect = new Rectangle(1,
y + (textArea.TextView.FontHeight - diameter) / 2,
diameter,
diameter);
using (GraphicsPath path = new GraphicsPath()) {
path.AddEllipse(rect);
using (PathGradientBrush pthGrBrush = new PathGradientBrush(path)) {
pthGrBrush.CenterPoint = new PointF(rect.Left + rect.Width / 3 , rect.Top + rect.Height / 3);
pthGrBrush.CenterColor = Color.MistyRose;
Color[] colors = {isHealthy ? Color.Firebrick : Color.Olive};
pthGrBrush.SurroundColors = colors;
if (isEnabled) {
g.FillEllipse(pthGrBrush, rect);
} else {
g.FillEllipse(SystemBrushes.Control, rect);
using (Pen pen = new Pen(pthGrBrush)) {
g.DrawEllipse(pen, new Rectangle(rect.X + 1, rect.Y + 1, rect.Width - 2, rect.Height - 2));
}
}
}
}
}
public void DrawBookmark(Graphics g, int y, bool isEnabled)
{
int delta = textArea.TextView.FontHeight / 8;
Rectangle rect = new Rectangle(1, y + delta, base.drawingPosition.Width - 4, textArea.TextView.FontHeight - delta * 2);
if (isEnabled) {
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.SkyBlue,
Color.White)) {
FillRoundRect(g, brush, rect);
}
} else {
FillRoundRect(g, Brushes.White, rect);
}
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.SkyBlue,
Color.Blue)) {
using (Pen pen = new Pen(brush)) {
DrawRoundRect(g, pen, rect);
}
}
}
public void DrawArrow(Graphics g, int y)
{
int delta = textArea.TextView.FontHeight / 8;
Rectangle rect = new Rectangle(1, y + delta, base.drawingPosition.Width - 4, textArea.TextView.FontHeight - delta * 2);
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.LightYellow,
Color.Yellow)) {
FillArrow(g, brush, rect);
}
using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top),
new Point(rect.Right, rect.Bottom),
Color.Yellow,
Color.Brown)) {
using (Pen pen = new Pen(brush)) {
DrawArrow(g, pen, rect);
}
}
}
GraphicsPath CreateArrowGraphicsPath(Rectangle r)
{
GraphicsPath gp = new GraphicsPath();
int halfX = r.Width / 2;
int halfY = r.Height/ 2;
gp.AddLine(r.X, r.Y + halfY/2, r.X + halfX, r.Y + halfY/2);
gp.AddLine(r.X + halfX, r.Y + halfY/2, r.X + halfX, r.Y);
gp.AddLine(r.X + halfX, r.Y, r.Right, r.Y + halfY);
gp.AddLine(r.Right, r.Y + halfY, r.X + halfX, r.Bottom);
gp.AddLine(r.X + halfX, r.Bottom, r.X + halfX, r.Bottom - halfY/2);
gp.AddLine(r.X + halfX, r.Bottom - halfY/2, r.X, r.Bottom - halfY/2);
gp.AddLine(r.X, r.Bottom - halfY/2, r.X, r.Y + halfY/2);
gp.CloseFigure();
return gp;
}
GraphicsPath CreateRoundRectGraphicsPath(Rectangle r)
{
GraphicsPath gp = new GraphicsPath();
int radius = r.Width / 2;
gp.AddLine(r.X + radius, r.Y, r.Right - radius, r.Y);
gp.AddArc(r.Right - radius, r.Y, radius, radius, 270, 90);
gp.AddLine(r.Right, r.Y + radius, r.Right, r.Bottom - radius);
gp.AddArc(r.Right - radius, r.Bottom - radius, radius, radius, 0, 90);
gp.AddLine(r.Right - radius, r.Bottom, r.X + radius, r.Bottom);
gp.AddArc(r.X, r.Bottom - radius, radius, radius, 90, 90);
gp.AddLine(r.X, r.Bottom - radius, r.X, r.Y + radius);
gp.AddArc(r.X, r.Y, radius, radius, 180, 90);
gp.CloseFigure();
return gp;
}
void DrawRoundRect(Graphics g, Pen p , Rectangle r)
{
using (GraphicsPath gp = CreateRoundRectGraphicsPath(r)) {
g.DrawPath(p, gp);
}
}
void FillRoundRect(Graphics g, Brush b , Rectangle r)
{
using (GraphicsPath gp = CreateRoundRectGraphicsPath(r)) {
g.FillPath(b, gp);
}
}
void DrawArrow(Graphics g, Pen p , Rectangle r)
{
using (GraphicsPath gp = CreateArrowGraphicsPath(r)) {
g.DrawPath(p, gp);
}
}
void FillArrow(Graphics g, Brush b , Rectangle r)
{
using (GraphicsPath gp = CreateArrowGraphicsPath(r)) {
g.FillPath(b, gp);
}
}
#endregion
static bool IsLineInsideRegion(int top, int bottom, int regionTop, int regionBottom)
{
if (top >= regionTop && top <= regionBottom) {
// Region overlaps the line's top edge.
return true;
} else if(regionTop > top && regionTop < bottom) {
// Region's top edge inside line.
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,179 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Shinsaku Nakagawa" email="shinsaku@users.sourceforge.jp"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// Used internally, not for own use.
/// </summary>
internal class Ime
{
public Ime(IntPtr hWnd, Font font)
{
// For unknown reasons, the IME support is causing crashes when used in a WOW64 process
// or when used in .NET 4.0. We'll disable IME support in those cases.
string PROCESSOR_ARCHITEW6432 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
if (PROCESSOR_ARCHITEW6432 == "IA64" || PROCESSOR_ARCHITEW6432 == "AMD64" || Environment.OSVersion.Platform == PlatformID.Unix || Environment.Version >= new Version(4,0)) {
disableIME = true;
} else {
this.hIMEWnd = ImmGetDefaultIMEWnd(hWnd);
}
this.hWnd = hWnd;
this.font = font;
SetIMEWindowFont(font);
}
private Font font = null;
public Font Font
{
get {
return font;
}
set {
if (!value.Equals(font)) {
font = value;
lf = null;
SetIMEWindowFont(value);
}
}
}
public IntPtr HWnd
{
set {
if (this.hWnd != value) {
this.hWnd = value;
if (!disableIME)
this.hIMEWnd = ImmGetDefaultIMEWnd(value);
SetIMEWindowFont(font);
}
}
}
[ DllImport("imm32.dll") ]
private static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);
[ DllImport("user32.dll") ]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, COMPOSITIONFORM lParam);
[ DllImport("user32.dll") ]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, [In, MarshalAs(UnmanagedType.LPStruct)] LOGFONT lParam);
[ StructLayout(LayoutKind.Sequential) ]
private class COMPOSITIONFORM
{
public int dwStyle = 0;
public POINT ptCurrentPos = null;
public RECT rcArea = null;
}
[ StructLayout(LayoutKind.Sequential) ]
private class POINT
{
public int x = 0;
public int y = 0;
}
[ StructLayout(LayoutKind.Sequential) ]
private class RECT
{
public int left = 0;
public int top = 0;
public int right = 0;
public int bottom = 0;
}
private const int WM_IME_CONTROL = 0x0283;
private const int IMC_SETCOMPOSITIONWINDOW = 0x000c;
private IntPtr hIMEWnd;
private IntPtr hWnd;
private const int CFS_POINT = 0x0002;
[ StructLayout(LayoutKind.Sequential) ]
private class LOGFONT
{
public int lfHeight = 0;
public int lfWidth = 0;
public int lfEscapement = 0;
public int lfOrientation = 0;
public int lfWeight = 0;
public byte lfItalic = 0;
public byte lfUnderline = 0;
public byte lfStrikeOut = 0;
public byte lfCharSet = 0;
public byte lfOutPrecision = 0;
public byte lfClipPrecision = 0;
public byte lfQuality = 0;
public byte lfPitchAndFamily = 0;
[ MarshalAs(UnmanagedType.ByValTStr, SizeConst=32) ] public string lfFaceName = null;
}
private const int IMC_SETCOMPOSITIONFONT = 0x000a;
LOGFONT lf = null;
static bool disableIME;
private void SetIMEWindowFont(Font f)
{
if (disableIME || hIMEWnd == IntPtr.Zero) return;
if (lf == null) {
lf = new LOGFONT();
f.ToLogFont(lf);
lf.lfFaceName = f.Name; // This is very important! "Font.ToLogFont" Method sets invalid value to LOGFONT.lfFaceName
}
try {
SendMessage(
hIMEWnd,
WM_IME_CONTROL,
new IntPtr(IMC_SETCOMPOSITIONFONT),
lf
);
} catch (AccessViolationException ex) {
Handle(ex);
}
}
public void SetIMEWindowLocation(int x, int y)
{
if (disableIME || hIMEWnd == IntPtr.Zero) return;
POINT p = new POINT();
p.x = x;
p.y = y;
COMPOSITIONFORM lParam = new COMPOSITIONFORM();
lParam.dwStyle = CFS_POINT;
lParam.ptCurrentPos = p;
lParam.rcArea = new RECT();
try {
SendMessage(
hIMEWnd,
WM_IME_CONTROL,
new IntPtr(IMC_SETCOMPOSITIONWINDOW),
lParam
);
} catch (AccessViolationException ex) {
Handle(ex);
}
}
void Handle(Exception ex)
{
Console.WriteLine(ex);
if (!disableIME) {
disableIME = true;
MessageBox.Show("Error calling IME: " + ex.Message + "\nIME is disabled.", "IME error");
}
}
}
}

View File

@@ -0,0 +1,53 @@
// <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.Gui.InsightWindow
{
public interface IInsightDataProvider
{
/// <summary>
/// Tells the insight provider to prepare its data.
/// </summary>
/// <param name="fileName">The name of the edited file</param>
/// <param name="textArea">The text area in which the file is being edited</param>
void SetupDataProvider(string fileName, TextArea textArea);
/// <summary>
/// Notifies the insight provider that the caret offset has changed.
/// </summary>
/// <returns>Return true to close the insight window (e.g. when the
/// caret was moved outside the region where insight is displayed for).
/// Return false to keep the window open.</returns>
bool CaretOffsetChanged();
/// <summary>
/// Gets the text to display in the insight window.
/// </summary>
/// <param name="number">The number of the active insight entry.
/// Multiple insight entries might be multiple overloads of the same method.</param>
/// <returns>The text to display, e.g. a multi-line string where
/// the first line is the method definition, followed by a description.</returns>
string GetInsightData(int number);
/// <summary>
/// Gets the number of available insight entries, e.g. the number of available
/// overloads to call.
/// </summary>
int InsightDataCount {
get;
}
/// <summary>
/// Gets the index of the entry to initially select.
/// </summary>
int DefaultIndex {
get;
}
}
}

View File

@@ -0,0 +1,199 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Gui.InsightWindow
{
public class InsightWindow : AbstractCompletionWindow
{
public InsightWindow(Form parentForm, TextEditorControl control) : base(parentForm, control)
{
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
public void ShowInsightWindow()
{
if (!Visible) {
if (insightDataProviderStack.Count > 0) {
ShowCompletionWindow();
}
} else {
Refresh();
}
}
#region Event handling routines
protected override bool ProcessTextAreaKey(Keys keyData)
{
if (!Visible) {
return false;
}
switch (keyData) {
case Keys.Down:
if (DataProvider != null && DataProvider.InsightDataCount > 0) {
CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount;
Refresh();
}
return true;
case Keys.Up:
if (DataProvider != null && DataProvider.InsightDataCount > 0) {
CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount;
Refresh();
}
return true;
}
return base.ProcessTextAreaKey(keyData);
}
protected override void CaretOffsetChanged(object sender, EventArgs e)
{
// move the window under the caret (don't change the x position)
TextLocation caretPos = control.ActiveTextAreaControl.Caret.Position;
int y = (int)((1 + caretPos.Y) * control.ActiveTextAreaControl.TextArea.TextView.FontHeight)
- control.ActiveTextAreaControl.TextArea.VirtualTop.Y - 1
+ control.ActiveTextAreaControl.TextArea.TextView.DrawingPosition.Y;
int xpos = control.ActiveTextAreaControl.TextArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X);
int ypos = (control.ActiveTextAreaControl.Document.GetVisibleLine(caretPos.Y) + 1) * control.ActiveTextAreaControl.TextArea.TextView.FontHeight
- control.ActiveTextAreaControl.TextArea.VirtualTop.Y;
int rulerHeight = control.TextEditorProperties.ShowHorizontalRuler ? control.ActiveTextAreaControl.TextArea.TextView.FontHeight : 0;
Point p = control.ActiveTextAreaControl.PointToScreen(new Point(xpos, ypos + rulerHeight));
if (p.Y != Location.Y) {
Location = p;
}
while (DataProvider != null && DataProvider.CaretOffsetChanged()) {
CloseCurrentDataProvider();
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
control.ActiveTextAreaControl.TextArea.Focus();
if (TipPainterTools.DrawingRectangle1.Contains(e.X, e.Y)) {
CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount;
Refresh();
}
if (TipPainterTools.DrawingRectangle2.Contains(e.X, e.Y)) {
CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount;
Refresh();
}
}
#endregion
MouseWheelHandler mouseWheelHandler = new MouseWheelHandler();
public void HandleMouseWheel(MouseEventArgs e)
{
if (DataProvider != null && DataProvider.InsightDataCount > 0) {
int distance = mouseWheelHandler.GetScrollAmount(e);
if (control.TextEditorProperties.MouseWheelScrollDown)
distance = -distance;
if (distance > 0) {
CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount;
} else if (distance < 0) {
CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount;
}
Refresh();
}
}
#region Insight Window Drawing routines
protected override void OnPaint(PaintEventArgs pe)
{
string methodCountMessage = null, description;
if (DataProvider == null || DataProvider.InsightDataCount < 1) {
description = "Unknown Method";
} else {
if (DataProvider.InsightDataCount > 1) {
methodCountMessage = control.GetRangeDescription(CurrentData + 1, DataProvider.InsightDataCount);
}
description = DataProvider.GetInsightData(CurrentData);
}
drawingSize = TipPainterTools.GetDrawingSizeHelpTipFromCombinedDescription(this,
pe.Graphics,
Font,
methodCountMessage,
description);
if (drawingSize != Size) {
SetLocation();
} else {
TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, methodCountMessage, description);
}
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle);
}
#endregion
#region InsightDataProvider handling
Stack<InsightDataProviderStackElement> insightDataProviderStack = new Stack<InsightDataProviderStackElement>();
int CurrentData {
get {
return insightDataProviderStack.Peek().currentData;
}
set {
insightDataProviderStack.Peek().currentData = value;
}
}
IInsightDataProvider DataProvider {
get {
if (insightDataProviderStack.Count == 0) {
return null;
}
return insightDataProviderStack.Peek().dataProvider;
}
}
public void AddInsightDataProvider(IInsightDataProvider provider, string fileName)
{
provider.SetupDataProvider(fileName, control.ActiveTextAreaControl.TextArea);
if (provider.InsightDataCount > 0) {
insightDataProviderStack.Push(new InsightDataProviderStackElement(provider));
}
}
void CloseCurrentDataProvider()
{
insightDataProviderStack.Pop();
if (insightDataProviderStack.Count == 0) {
Close();
} else {
Refresh();
}
}
class InsightDataProviderStackElement
{
public int currentData;
public IInsightDataProvider dataProvider;
public InsightDataProviderStackElement(IInsightDataProvider dataProvider)
{
this.currentData = Math.Max(dataProvider.DefaultIndex, 0);
this.dataProvider = dataProvider;
}
}
#endregion
}
}

View File

@@ -0,0 +1,947 @@
// <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.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Text;
using System.Text;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
namespace ICSharpCode.TextEditor
{
public delegate bool KeyEventHandler(char ch);
public delegate bool DialogKeyProcessor(Keys keyData);
/// <summary>
/// This class paints the textarea.
/// </summary>
[ToolboxItem(false)]
public class TextArea : Control
{
bool hiddenMouseCursor = false;
/// <summary>
/// The position where the mouse cursor was when it was hidden. Sometimes the text editor gets MouseMove
/// events when typing text even if the mouse is not moved.
/// </summary>
Point mouseCursorHidePosition;
Point virtualTop = new Point(0, 0);
TextAreaControl motherTextAreaControl;
TextEditorControl motherTextEditorControl;
List<BracketHighlightingSheme> bracketshemes = new List<BracketHighlightingSheme>();
TextAreaClipboardHandler textAreaClipboardHandler;
bool autoClearSelection = false;
List<AbstractMargin> leftMargins = new List<AbstractMargin>();
TextView textView;
GutterMargin gutterMargin;
FoldMargin foldMargin;
IconBarMargin iconBarMargin;
SelectionManager selectionManager;
Caret caret;
internal Point mousepos = new Point(0, 0);
//public Point selectionStartPos = new Point(0,0);
bool disposed;
[Browsable(false)]
public IList<AbstractMargin> LeftMargins {
get {
return leftMargins.AsReadOnly();
}
}
public void InsertLeftMargin(int index, AbstractMargin margin)
{
leftMargins.Insert(index, margin);
Refresh();
}
public TextEditorControl MotherTextEditorControl {
get {
return motherTextEditorControl;
}
}
public TextAreaControl MotherTextAreaControl {
get {
return motherTextAreaControl;
}
}
public SelectionManager SelectionManager {
get {
return selectionManager;
}
}
public Caret Caret {
get {
return caret;
}
}
public TextView TextView {
get {
return textView;
}
}
public GutterMargin GutterMargin {
get {
return gutterMargin;
}
}
public FoldMargin FoldMargin {
get {
return foldMargin;
}
}
public IconBarMargin IconBarMargin {
get {
return iconBarMargin;
}
}
public Encoding Encoding {
get {
return motherTextEditorControl.Encoding;
}
}
public int MaxVScrollValue {
get {
return (Document.GetVisibleLine(Document.TotalNumberOfLines - 1) + 1 + TextView.VisibleLineCount * 2 / 3) * TextView.FontHeight;
}
}
public Point VirtualTop {
get {
return virtualTop;
}
set {
Point newVirtualTop = new Point(value.X, Math.Min(MaxVScrollValue, Math.Max(0, value.Y)));
if (virtualTop != newVirtualTop) {
virtualTop = newVirtualTop;
motherTextAreaControl.VScrollBar.Value = virtualTop.Y;
Invalidate();
}
caret.UpdateCaretPosition();
}
}
public bool AutoClearSelection {
get {
return autoClearSelection;
}
set {
autoClearSelection = value;
}
}
[Browsable(false)]
public IDocument Document {
get {
return motherTextEditorControl.Document;
}
}
public TextAreaClipboardHandler ClipboardHandler {
get {
return textAreaClipboardHandler;
}
}
public ITextEditorProperties TextEditorProperties {
get {
return motherTextEditorControl.TextEditorProperties;
}
}
public TextArea(TextEditorControl motherTextEditorControl, TextAreaControl motherTextAreaControl)
{
this.motherTextAreaControl = motherTextAreaControl;
this.motherTextEditorControl = motherTextEditorControl;
caret = new Caret(this);
selectionManager = new SelectionManager(Document, this);
this.textAreaClipboardHandler = new TextAreaClipboardHandler(this);
ResizeRedraw = true;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
// SetStyle(ControlStyles.AllPaintingInWmPaint, true);
// SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.Opaque, false);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.Selectable, true);
textView = new TextView(this);
gutterMargin = new GutterMargin(this);
foldMargin = new FoldMargin(this);
iconBarMargin = new IconBarMargin(this);
leftMargins.AddRange(new AbstractMargin[] { iconBarMargin, gutterMargin, foldMargin });
OptionsChanged();
new TextAreaMouseHandler(this).Attach();
new TextAreaDragDropHandler().Attach(this);
bracketshemes.Add(new BracketHighlightingSheme('{', '}'));
bracketshemes.Add(new BracketHighlightingSheme('(', ')'));
bracketshemes.Add(new BracketHighlightingSheme('[', ']'));
caret.PositionChanged += new EventHandler(SearchMatchingBracket);
Document.TextContentChanged += new EventHandler(TextContentChanged);
Document.FoldingManager.FoldingsChanged += new EventHandler(DocumentFoldingsChanged);
}
public void UpdateMatchingBracket()
{
SearchMatchingBracket(null, null);
}
void TextContentChanged(object sender, EventArgs e)
{
Caret.Position = new TextLocation(0, 0);
SelectionManager.SelectionCollection.Clear();
}
void SearchMatchingBracket(object sender, EventArgs e)
{
if (!TextEditorProperties.ShowMatchingBracket) {
textView.Highlight = null;
return;
}
int oldLine1 = -1, oldLine2 = -1;
if (textView.Highlight != null && textView.Highlight.OpenBrace.Y >=0 && textView.Highlight.OpenBrace.Y < Document.TotalNumberOfLines) {
oldLine1 = textView.Highlight.OpenBrace.Y;
}
if (textView.Highlight != null && textView.Highlight.CloseBrace.Y >=0 && textView.Highlight.CloseBrace.Y < Document.TotalNumberOfLines) {
oldLine2 = textView.Highlight.CloseBrace.Y;
}
textView.Highlight = FindMatchingBracketHighlight();
if (oldLine1 >= 0)
UpdateLine(oldLine1);
if (oldLine2 >= 0 && oldLine2 != oldLine1)
UpdateLine(oldLine2);
if (textView.Highlight != null) {
int newLine1 = textView.Highlight.OpenBrace.Y;
int newLine2 = textView.Highlight.CloseBrace.Y;
if (newLine1 != oldLine1 && newLine1 != oldLine2)
UpdateLine(newLine1);
if (newLine2 != oldLine1 && newLine2 != oldLine2 && newLine2 != newLine1)
UpdateLine(newLine2);
}
}
public Highlight FindMatchingBracketHighlight()
{
if (Caret.Offset == 0)
return null;
foreach (BracketHighlightingSheme bracketsheme in bracketshemes) {
Highlight highlight = bracketsheme.GetHighlight(Document, Caret.Offset - 1);
if (highlight != null) {
return highlight;
}
}
return null;
}
public void SetDesiredColumn()
{
Caret.DesiredColumn = TextView.GetDrawingXPos(Caret.Line, Caret.Column) + VirtualTop.X;
}
public void SetCaretToDesiredColumn()
{
FoldMarker dummy;
Caret.Position = textView.GetLogicalColumn(Caret.Line, Caret.DesiredColumn + VirtualTop.X, out dummy);
}
public void OptionsChanged()
{
UpdateMatchingBracket();
textView.OptionsChanged();
caret.RecreateCaret();
caret.UpdateCaretPosition();
Refresh();
}
AbstractMargin lastMouseInMargin;
protected override void OnMouseLeave(System.EventArgs e)
{
base.OnMouseLeave(e);
this.Cursor = Cursors.Default;
if (lastMouseInMargin != null) {
lastMouseInMargin.HandleMouseLeave(EventArgs.Empty);
lastMouseInMargin = null;
}
CloseToolTip();
}
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
// this corrects weird problems when text is selected,
// then a menu item is selected, then the text is
// clicked again - it correctly synchronises the
// click position
mousepos = new Point(e.X, e.Y);
base.OnMouseDown(e);
CloseToolTip();
foreach (AbstractMargin margin in leftMargins) {
if (margin.DrawingPosition.Contains(e.X, e.Y)) {
margin.HandleMouseDown(new Point(e.X, e.Y), e.Button);
}
}
}
/// <summary>
/// Shows the mouse cursor if it has been hidden.
/// </summary>
/// <param name="forceShow"><c>true</c> to always show the cursor or <c>false</c> to show it only if it has been moved since it was hidden.</param>
internal void ShowHiddenCursor(bool forceShow)
{
if (hiddenMouseCursor) {
if (mouseCursorHidePosition != Cursor.Position || forceShow) {
Cursor.Show();
hiddenMouseCursor = false;
}
}
}
// static because the mouse can only be in one text area and we don't want to have
// tooltips of text areas from inactive tabs floating around.
static DeclarationViewWindow toolTip;
static string oldToolTip;
void SetToolTip(string text, int lineNumber)
{
if (toolTip == null || toolTip.IsDisposed)
toolTip = new DeclarationViewWindow(this.FindForm());
if (oldToolTip == text)
return;
if (text == null) {
toolTip.Hide();
} else {
Point p = Control.MousePosition;
Point cp = PointToClient(p);
if (lineNumber >= 0) {
lineNumber = this.Document.GetVisibleLine(lineNumber);
p.Y = (p.Y - cp.Y) + (lineNumber * this.TextView.FontHeight) - this.virtualTop.Y;
}
p.Offset(3, 3);
toolTip.Owner = this.FindForm();
toolTip.Location = p;
toolTip.Description = text;
toolTip.HideOnClick = true;
toolTip.Show();
}
oldToolTip = text;
}
public event ToolTipRequestEventHandler ToolTipRequest;
protected virtual void OnToolTipRequest(ToolTipRequestEventArgs e)
{
if (ToolTipRequest != null) {
ToolTipRequest(this, e);
}
}
bool toolTipActive;
/// <summary>
/// Rectangle in text area that caused the current tool tip.
/// Prevents tooltip from re-showing when it was closed because of a click or keyboard
/// input and the mouse was not used.
/// </summary>
Rectangle toolTipRectangle;
void CloseToolTip()
{
if (toolTipActive) {
//Console.WriteLine("Closing tooltip");
toolTipActive = false;
SetToolTip(null, -1);
}
ResetMouseEventArgs();
}
protected override void OnMouseHover(EventArgs e)
{
base.OnMouseHover(e);
//Console.WriteLine("Hover raised at " + PointToClient(Control.MousePosition));
if (MouseButtons == MouseButtons.None) {
RequestToolTip(PointToClient(Control.MousePosition));
} else {
CloseToolTip();
}
}
protected void RequestToolTip(Point mousePos)
{
if (toolTipRectangle.Contains(mousePos)) {
if (!toolTipActive)
ResetMouseEventArgs();
return;
}
//Console.WriteLine("Request tooltip for " + mousePos);
toolTipRectangle = new Rectangle(mousePos.X - 4, mousePos.Y - 4, 8, 8);
TextLocation logicPos = textView.GetLogicalPosition(mousePos.X - textView.DrawingPosition.Left,
mousePos.Y - textView.DrawingPosition.Top);
bool inDocument = textView.DrawingPosition.Contains(mousePos)
&& logicPos.Y >= 0 && logicPos.Y < Document.TotalNumberOfLines;
ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(mousePos, logicPos, inDocument);
OnToolTipRequest(args);
if (args.ToolTipShown) {
//Console.WriteLine("Set tooltip to " + args.toolTipText);
toolTipActive = true;
SetToolTip(args.toolTipText, inDocument ? logicPos.Y + 1 : -1);
} else {
CloseToolTip();
}
}
// external interface to the attached event
internal void RaiseMouseMove(MouseEventArgs e)
{
OnMouseMove(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (!toolTipRectangle.Contains(e.Location)) {
toolTipRectangle = Rectangle.Empty;
if (toolTipActive)
RequestToolTip(e.Location);
}
foreach (AbstractMargin margin in leftMargins) {
if (margin.DrawingPosition.Contains(e.X, e.Y)) {
this.Cursor = margin.Cursor;
margin.HandleMouseMove(new Point(e.X, e.Y), e.Button);
if (lastMouseInMargin != margin) {
if (lastMouseInMargin != null) {
lastMouseInMargin.HandleMouseLeave(EventArgs.Empty);
}
lastMouseInMargin = margin;
}
return;
}
}
if (lastMouseInMargin != null) {
lastMouseInMargin.HandleMouseLeave(EventArgs.Empty);
lastMouseInMargin = null;
}
if (textView.DrawingPosition.Contains(e.X, e.Y)) {
TextLocation realmousepos = TextView.GetLogicalPosition(e.X - TextView.DrawingPosition.X, e.Y - TextView.DrawingPosition.Y);
if(SelectionManager.IsSelected(Document.PositionToOffset(realmousepos)) && MouseButtons == MouseButtons.None) {
// mouse is hovering over a selection, so show default mouse
this.Cursor = Cursors.Default;
} else {
// mouse is hovering over text area, not a selection, so show the textView cursor
this.Cursor = textView.Cursor;
}
return;
}
this.Cursor = Cursors.Default;
}
AbstractMargin updateMargin = null;
public void Refresh(AbstractMargin margin)
{
updateMargin = margin;
Invalidate(updateMargin.DrawingPosition);
Update();
updateMargin = null;
}
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs pevent)
{
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
int currentXPos = 0;
int currentYPos = 0;
bool adjustScrollBars = false;
Graphics g = e.Graphics;
Rectangle clipRectangle = e.ClipRectangle;
bool isFullRepaint = clipRectangle.X == 0 && clipRectangle.Y == 0
&& clipRectangle.Width == this.Width && clipRectangle.Height == this.Height;
g.TextRenderingHint = this.TextEditorProperties.TextRenderingHint;
if (updateMargin != null) {
updateMargin.Paint(g, updateMargin.DrawingPosition);
// clipRectangle.Intersect(updateMargin.DrawingPosition);
}
if (clipRectangle.Width <= 0 || clipRectangle.Height <= 0) {
return;
}
foreach (AbstractMargin margin in leftMargins) {
if (margin.IsVisible) {
Rectangle marginRectangle = new Rectangle(currentXPos , currentYPos, margin.Size.Width, Height - currentYPos);
if (marginRectangle != margin.DrawingPosition) {
// margin changed size
if (!isFullRepaint && !clipRectangle.Contains(marginRectangle)) {
Invalidate(); // do a full repaint
}
adjustScrollBars = true;
margin.DrawingPosition = marginRectangle;
}
currentXPos += margin.DrawingPosition.Width;
if (clipRectangle.IntersectsWith(marginRectangle)) {
marginRectangle.Intersect(clipRectangle);
if (!marginRectangle.IsEmpty) {
margin.Paint(g, marginRectangle);
}
}
}
}
Rectangle textViewArea = new Rectangle(currentXPos, currentYPos, Width - currentXPos, Height - currentYPos);
if (textViewArea != textView.DrawingPosition) {
adjustScrollBars = true;
textView.DrawingPosition = textViewArea;
// update caret position (but outside of WM_PAINT!)
BeginInvoke((MethodInvoker)caret.UpdateCaretPosition);
}
if (clipRectangle.IntersectsWith(textViewArea)) {
textViewArea.Intersect(clipRectangle);
if (!textViewArea.IsEmpty) {
textView.Paint(g, textViewArea);
}
}
if (adjustScrollBars) {
this.motherTextAreaControl.AdjustScrollBars();
}
// we cannot update the caret position here, it's not allowed to call the caret API inside WM_PAINT
//Caret.UpdateCaretPosition();
base.OnPaint(e);
}
void DocumentFoldingsChanged(object sender, EventArgs e)
{
Caret.UpdateCaretPosition();
Invalidate();
this.motherTextAreaControl.AdjustScrollBars();
}
#region keyboard handling methods
/// <summary>
/// This method is called on each Keypress
/// </summary>
/// <returns>
/// True, if the key is handled by this method and should NOT be
/// inserted in the textarea.
/// </returns>
protected internal virtual bool HandleKeyPress(char ch)
{
if (KeyEventHandler != null) {
return KeyEventHandler(ch);
}
return false;
}
// Fixes SD2-747: Form containing the text editor and a button with a shortcut
protected override bool IsInputChar(char charCode)
{
return true;
}
internal bool IsReadOnly(int offset)
{
if (Document.ReadOnly) {
return true;
}
if (TextEditorProperties.SupportReadOnlySegments) {
return Document.MarkerStrategy.GetMarkers(offset).Exists(m=>m.IsReadOnly);
} else {
return false;
}
}
internal bool IsReadOnly(int offset, int length)
{
if (Document.ReadOnly) {
return true;
}
if (TextEditorProperties.SupportReadOnlySegments) {
return Document.MarkerStrategy.GetMarkers(offset, length).Exists(m=>m.IsReadOnly);
} else {
return false;
}
}
public void SimulateKeyPress(char ch)
{
if (SelectionManager.HasSomethingSelected) {
if (SelectionManager.SelectionIsReadonly)
return;
} else if (IsReadOnly(Caret.Offset)) {
return;
}
if (ch < ' ') {
return;
}
if (!hiddenMouseCursor && TextEditorProperties.HideMouseCursor) {
if (this.ClientRectangle.Contains(PointToClient(Cursor.Position))) {
mouseCursorHidePosition = Cursor.Position;
hiddenMouseCursor = true;
Cursor.Hide();
}
}
CloseToolTip();
BeginUpdate();
Document.UndoStack.StartUndoGroup();
try {
// INSERT char
if (!HandleKeyPress(ch)) {
switch (Caret.CaretMode) {
case CaretMode.InsertMode:
InsertChar(ch);
break;
case CaretMode.OverwriteMode:
ReplaceChar(ch);
break;
default:
Debug.Assert(false, "Unknown caret mode " + Caret.CaretMode);
break;
}
}
int currentLineNr = Caret.Line;
Document.FormattingStrategy.FormatLine(this, currentLineNr, Document.PositionToOffset(Caret.Position), ch);
EndUpdate();
} finally {
Document.UndoStack.EndUndoGroup();
}
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
SimulateKeyPress(e.KeyChar);
e.Handled = true;
}
/// <summary>
/// This method executes a dialog key
/// </summary>
public bool ExecuteDialogKey(Keys keyData)
{
// try, if a dialog key processor was set to use this
if (DoProcessDialogKey != null && DoProcessDialogKey(keyData)) {
return true;
}
// if not (or the process was 'silent', use the standard edit actions
IEditAction action = motherTextEditorControl.GetEditAction(keyData);
AutoClearSelection = true;
if (action != null) {
BeginUpdate();
try {
lock (Document) {
action.Execute(this);
if (SelectionManager.HasSomethingSelected && AutoClearSelection /*&& caretchanged*/) {
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal) {
SelectionManager.ClearSelection();
}
}
}
} finally {
EndUpdate();
Caret.UpdateCaretPosition();
}
return true;
}
return false;
}
protected override bool ProcessDialogKey(Keys keyData)
{
return ExecuteDialogKey(keyData) || base.ProcessDialogKey(keyData);
}
#endregion
public void ScrollToCaret()
{
motherTextAreaControl.ScrollToCaret();
}
public void ScrollTo(int line)
{
motherTextAreaControl.ScrollTo(line);
}
public void BeginUpdate()
{
motherTextEditorControl.BeginUpdate();
}
public void EndUpdate()
{
motherTextEditorControl.EndUpdate();
}
public bool EnableCutOrPaste {
get {
if (motherTextAreaControl == null)
return false;
if (SelectionManager.HasSomethingSelected)
return !SelectionManager.SelectionIsReadonly;
else
return !IsReadOnly(Caret.Offset);
}
}
string GenerateWhitespaceString(int length)
{
return new string(' ', length);
}
/// <remarks>
/// Inserts a single character at the caret position
/// </remarks>
public void InsertChar(char ch)
{
bool updating = motherTextEditorControl.IsInUpdate;
if (!updating) {
BeginUpdate();
}
// filter out forgein whitespace chars and replace them with standard space (ASCII 32)
if (char.IsWhiteSpace(ch) && ch != '\t' && ch != '\n') {
ch = ' ';
}
Document.UndoStack.StartUndoGroup();
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal &&
SelectionManager.SelectionCollection.Count > 0) {
Caret.Position = SelectionManager.SelectionCollection[0].StartPosition;
SelectionManager.RemoveSelectedText();
}
LineSegment caretLine = Document.GetLineSegment(Caret.Line);
int offset = Caret.Offset;
// use desired column for generated whitespaces
int dc = Caret.Column;
if (caretLine.Length < dc && ch != '\n') {
Document.Insert(offset, GenerateWhitespaceString(dc - caretLine.Length) + ch);
} else {
Document.Insert(offset, ch.ToString());
}
Document.UndoStack.EndUndoGroup();
++Caret.Column;
if (!updating) {
EndUpdate();
UpdateLineToEnd(Caret.Line, Caret.Column);
}
// I prefer to set NOT the standard column, if you type something
// ++Caret.DesiredColumn;
}
/// <remarks>
/// Inserts a whole string at the caret position
/// </remarks>
public void InsertString(string str)
{
bool updating = motherTextEditorControl.IsInUpdate;
if (!updating) {
BeginUpdate();
}
try {
Document.UndoStack.StartUndoGroup();
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal &&
SelectionManager.SelectionCollection.Count > 0) {
Caret.Position = SelectionManager.SelectionCollection[0].StartPosition;
SelectionManager.RemoveSelectedText();
}
int oldOffset = Document.PositionToOffset(Caret.Position);
int oldLine = Caret.Line;
LineSegment caretLine = Document.GetLineSegment(Caret.Line);
if (caretLine.Length < Caret.Column) {
int whiteSpaceLength = Caret.Column - caretLine.Length;
Document.Insert(oldOffset, GenerateWhitespaceString(whiteSpaceLength) + str);
Caret.Position = Document.OffsetToPosition(oldOffset + str.Length + whiteSpaceLength);
} else {
Document.Insert(oldOffset, str);
Caret.Position = Document.OffsetToPosition(oldOffset + str.Length);
}
Document.UndoStack.EndUndoGroup();
if (oldLine != Caret.Line) {
UpdateToEnd(oldLine);
} else {
UpdateLineToEnd(Caret.Line, Caret.Column);
}
} finally {
if (!updating) {
EndUpdate();
}
}
}
/// <remarks>
/// Replaces a char at the caret position
/// </remarks>
public void ReplaceChar(char ch)
{
bool updating = motherTextEditorControl.IsInUpdate;
if (!updating) {
BeginUpdate();
}
if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && SelectionManager.SelectionCollection.Count > 0) {
Caret.Position = SelectionManager.SelectionCollection[0].StartPosition;
SelectionManager.RemoveSelectedText();
}
int lineNr = Caret.Line;
LineSegment line = Document.GetLineSegment(lineNr);
int offset = Document.PositionToOffset(Caret.Position);
if (offset < line.Offset + line.Length) {
Document.Replace(offset, 1, ch.ToString());
} else {
Document.Insert(offset, ch.ToString());
}
if (!updating) {
EndUpdate();
UpdateLineToEnd(lineNr, Caret.Column);
}
++Caret.Column;
// ++Caret.DesiredColumn;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing) {
if (!disposed) {
disposed = true;
if (caret != null) {
caret.PositionChanged -= new EventHandler(SearchMatchingBracket);
caret.Dispose();
}
if (selectionManager != null) {
selectionManager.Dispose();
}
Document.TextContentChanged -= new EventHandler(TextContentChanged);
Document.FoldingManager.FoldingsChanged -= new EventHandler(DocumentFoldingsChanged);
motherTextAreaControl = null;
motherTextEditorControl = null;
foreach (AbstractMargin margin in leftMargins) {
if (margin is IDisposable)
(margin as IDisposable).Dispose();
}
textView.Dispose();
}
}
}
#region UPDATE Commands
internal void UpdateLine(int line)
{
UpdateLines(0, line, line);
}
internal void UpdateLines(int lineBegin, int lineEnd)
{
UpdateLines(0, lineBegin, lineEnd);
}
internal void UpdateToEnd(int lineBegin)
{
// if (lineBegin > FirstPhysicalLine + textView.VisibleLineCount) {
// return;
// }
lineBegin = Document.GetVisibleLine(lineBegin);
int y = Math.Max( 0, (int)(lineBegin * textView.FontHeight));
y = Math.Max(0, y - this.virtualTop.Y);
Rectangle r = new Rectangle(0,
y,
Width,
Height - y);
Invalidate(r);
}
internal void UpdateLineToEnd(int lineNr, int xStart)
{
UpdateLines(xStart, lineNr, lineNr);
}
internal void UpdateLine(int line, int begin, int end)
{
UpdateLines(line, line);
}
int FirstPhysicalLine {
get {
return VirtualTop.Y / textView.FontHeight;
}
}
internal void UpdateLines(int xPos, int lineBegin, int lineEnd)
{
// if (lineEnd < FirstPhysicalLine || lineBegin > FirstPhysicalLine + textView.VisibleLineCount) {
// return;
// }
InvalidateLines((int)(xPos * this.TextView.WideSpaceWidth), lineBegin, lineEnd);
}
void InvalidateLines(int xPos, int lineBegin, int lineEnd)
{
lineBegin = Math.Max(Document.GetVisibleLine(lineBegin), FirstPhysicalLine);
lineEnd = Math.Min(Document.GetVisibleLine(lineEnd), FirstPhysicalLine + textView.VisibleLineCount);
int y = Math.Max( 0, (int)(lineBegin * textView.FontHeight));
int height = Math.Min(textView.DrawingPosition.Height, (int)((1 + lineEnd - lineBegin) * (textView.FontHeight + 1)));
Rectangle r = new Rectangle(0,
y - 1 - this.virtualTop.Y,
Width,
height + 3);
Invalidate(r);
}
#endregion
public event KeyEventHandler KeyEventHandler;
public event DialogKeyProcessor DoProcessDialogKey;
//internal void
}
}

View File

@@ -0,0 +1,268 @@
// <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.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor
{
public class TextAreaClipboardHandler
{
TextArea textArea;
public bool EnableCut {
get {
return textArea.EnableCutOrPaste; //textArea.SelectionManager.HasSomethingSelected;
}
}
public bool EnableCopy {
get {
return true; //textArea.SelectionManager.HasSomethingSelected;
}
}
public delegate bool ClipboardContainsTextDelegate();
/// <summary>
/// Is called when CachedClipboardContainsText should be updated.
/// If this property is null (the default value), the text editor uses
/// System.Windows.Forms.Clipboard.ContainsText.
/// </summary>
/// <remarks>
/// This property is useful if you want to prevent the default Clipboard.ContainsText
/// behaviour that waits for the clipboard to be available - the clipboard might
/// never become available if it is owned by a process that is paused by the debugger.
/// </remarks>
public static ClipboardContainsTextDelegate GetClipboardContainsText;
public bool EnablePaste {
get {
if (!textArea.EnableCutOrPaste)
return false;
ClipboardContainsTextDelegate d = GetClipboardContainsText;
if (d != null) {
return d();
} else {
try {
return Clipboard.ContainsText();
} catch (ExternalException) {
return false;
}
}
}
}
public bool EnableDelete {
get {
return textArea.SelectionManager.HasSomethingSelected && !textArea.SelectionManager.SelectionIsReadonly;
}
}
public bool EnableSelectAll {
get {
return true;
}
}
public TextAreaClipboardHandler(TextArea textArea)
{
this.textArea = textArea;
textArea.SelectionManager.SelectionChanged += new EventHandler(DocumentSelectionChanged);
}
void DocumentSelectionChanged(object sender, EventArgs e)
{
// ((DefaultWorkbench)WorkbenchSingleton.Workbench).UpdateToolbars();
}
const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy
bool CopyTextToClipboard(string stringToCopy, bool asLine)
{
if (stringToCopy.Length > 0) {
DataObject dataObject = new DataObject();
dataObject.SetData(DataFormats.UnicodeText, true, stringToCopy);
if (asLine) {
MemoryStream lineSelected = new MemoryStream(1);
lineSelected.WriteByte(1);
dataObject.SetData(LineSelectedType, false, lineSelected);
}
// Default has no highlighting, therefore we don't need RTF output
if (textArea.Document.HighlightingStrategy.Name != "Default") {
dataObject.SetData(DataFormats.Rtf, RtfWriter.GenerateRtf(textArea));
}
OnCopyText(new CopyTextEventArgs(stringToCopy));
SafeSetClipboard(dataObject);
return true;
} else {
return false;
}
}
// Code duplication: TextAreaClipboardHandler.cs also has SafeSetClipboard
[ThreadStatic] static int SafeSetClipboardDataVersion;
static void SafeSetClipboard(object dataObject)
{
// Work around ExternalException bug. (SD2-426)
// Best reproducable inside Virtual PC.
int version = unchecked(++SafeSetClipboardDataVersion);
try {
Clipboard.SetDataObject(dataObject, true);
} catch (ExternalException) {
Timer timer = new Timer();
timer.Interval = 100;
timer.Tick += delegate {
timer.Stop();
timer.Dispose();
if (SafeSetClipboardDataVersion == version) {
try {
Clipboard.SetDataObject(dataObject, true, 10, 50);
} catch (ExternalException) { }
}
};
timer.Start();
}
}
bool CopyTextToClipboard(string stringToCopy)
{
return CopyTextToClipboard(stringToCopy, false);
}
public void Cut(object sender, EventArgs e)
{
if (textArea.SelectionManager.HasSomethingSelected) {
if (CopyTextToClipboard(textArea.SelectionManager.SelectedText)) {
if (textArea.SelectionManager.SelectionIsReadonly)
return;
// Remove text
textArea.BeginUpdate();
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
textArea.SelectionManager.RemoveSelectedText();
textArea.EndUpdate();
}
} else if (textArea.Document.TextEditorProperties.CutCopyWholeLine) {
// No text was selected, select and cut the entire line
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr);
string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength);
textArea.SelectionManager.SetSelection(textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset), textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset + lineWhereCaretIs.TotalLength));
if (CopyTextToClipboard(caretLineText, true)) {
if (textArea.SelectionManager.SelectionIsReadonly)
return;
// remove line
textArea.BeginUpdate();
textArea.Caret.Position = textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset);
textArea.SelectionManager.RemoveSelectedText();
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr)));
textArea.EndUpdate();
}
}
}
public void Copy(object sender, EventArgs e)
{
if (!CopyTextToClipboard(textArea.SelectionManager.SelectedText) && textArea.Document.TextEditorProperties.CutCopyWholeLine) {
// No text was selected, select the entire line, copy it, and then deselect
int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr);
string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength);
CopyTextToClipboard(caretLineText, true);
}
}
public void Paste(object sender, EventArgs e)
{
if (!textArea.EnableCutOrPaste) {
return;
}
// Clipboard.GetDataObject may throw an exception...
for (int i = 0;; i++) {
try {
IDataObject data = Clipboard.GetDataObject();
if (data == null)
return;
bool fullLine = data.GetDataPresent(LineSelectedType);
if (data.GetDataPresent(DataFormats.UnicodeText)) {
string text = (string)data.GetData(DataFormats.UnicodeText);
// we got NullReferenceExceptions here, apparently the clipboard can contain null strings
if (!string.IsNullOrEmpty(text)) {
textArea.Document.UndoStack.StartUndoGroup();
try {
if (textArea.SelectionManager.HasSomethingSelected) {
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
textArea.SelectionManager.RemoveSelectedText();
}
if (fullLine) {
int col = textArea.Caret.Column;
textArea.Caret.Column = 0;
if (!textArea.IsReadOnly(textArea.Caret.Offset))
textArea.InsertString(text);
textArea.Caret.Column = col;
} else {
// textArea.EnableCutOrPaste already checked readonly for this case
textArea.InsertString(text);
}
} finally {
textArea.Document.UndoStack.EndUndoGroup();
}
}
}
return;
} catch (ExternalException) {
// GetDataObject does not provide RetryTimes parameter
if (i > 5) throw;
}
}
}
public void Delete(object sender, EventArgs e)
{
new ICSharpCode.TextEditor.Actions.Delete().Execute(textArea);
}
public void SelectAll(object sender, EventArgs e)
{
new ICSharpCode.TextEditor.Actions.SelectWholeDocument().Execute(textArea);
}
protected virtual void OnCopyText(CopyTextEventArgs e)
{
if (CopyText != null) {
CopyText(this, e);
}
}
public event CopyTextEventHandler CopyText;
}
public delegate void CopyTextEventHandler(object sender, CopyTextEventArgs e);
public class CopyTextEventArgs : EventArgs
{
string text;
public string Text {
get {
return text;
}
}
public CopyTextEventArgs(string text)
{
this.text = text;
}
}
}

View File

@@ -0,0 +1,463 @@
// <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.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class paints the textarea.
/// </summary>
[ToolboxItem(false)]
public class TextAreaControl : Panel
{
TextEditorControl motherTextEditorControl;
HRuler hRuler = null;
VScrollBar vScrollBar = new VScrollBar();
HScrollBar hScrollBar = new HScrollBar();
TextArea textArea;
bool doHandleMousewheel = true;
bool disposed;
public TextArea TextArea {
get {
return textArea;
}
}
public SelectionManager SelectionManager {
get {
return textArea.SelectionManager;
}
}
public Caret Caret {
get {
return textArea.Caret;
}
}
[Browsable(false)]
public IDocument Document {
get {
if (motherTextEditorControl != null)
return motherTextEditorControl.Document;
return null;
}
}
public ITextEditorProperties TextEditorProperties {
get {
if (motherTextEditorControl != null)
return motherTextEditorControl.TextEditorProperties;
return null;
}
}
public VScrollBar VScrollBar {
get {
return vScrollBar;
}
}
public HScrollBar HScrollBar {
get {
return hScrollBar;
}
}
public bool DoHandleMousewheel {
get {
return doHandleMousewheel;
}
set {
doHandleMousewheel = value;
}
}
public TextAreaControl(TextEditorControl motherTextEditorControl)
{
this.motherTextEditorControl = motherTextEditorControl;
this.textArea = new TextArea(motherTextEditorControl, this);
Controls.Add(textArea);
vScrollBar.ValueChanged += new EventHandler(VScrollBarValueChanged);
Controls.Add(this.vScrollBar);
hScrollBar.ValueChanged += new EventHandler(HScrollBarValueChanged);
Controls.Add(this.hScrollBar);
ResizeRedraw = true;
Document.TextContentChanged += DocumentTextContentChanged;
Document.DocumentChanged += AdjustScrollBarsOnDocumentChange;
Document.UpdateCommited += DocumentUpdateCommitted;
}
protected override void Dispose(bool disposing)
{
if (disposing) {
if (!disposed) {
disposed = true;
Document.TextContentChanged -= DocumentTextContentChanged;
Document.DocumentChanged -= AdjustScrollBarsOnDocumentChange;
Document.UpdateCommited -= DocumentUpdateCommitted;
motherTextEditorControl = null;
if (vScrollBar != null) {
vScrollBar.Dispose();
vScrollBar = null;
}
if (hScrollBar != null) {
hScrollBar.Dispose();
hScrollBar = null;
}
if (hRuler != null) {
hRuler.Dispose();
hRuler = null;
}
}
}
base.Dispose(disposing);
}
void DocumentTextContentChanged(object sender, EventArgs e)
{
// after the text content is changed abruptly, we need to validate the
// caret position - otherwise the caret position is invalid for a short amount
// of time, which can break client code that expects that the caret position is always valid
Caret.ValidateCaretPos();
}
protected override void OnResize(System.EventArgs e)
{
base.OnResize(e);
ResizeTextArea();
}
public void ResizeTextArea()
{
int y = 0;
int h = 0;
if (hRuler != null) {
hRuler.Bounds = new Rectangle(0,
0,
Width - SystemInformation.HorizontalScrollBarArrowWidth,
textArea.TextView.FontHeight);
y = hRuler.Bounds.Bottom;
h = hRuler.Bounds.Height;
}
textArea.Bounds = new Rectangle(0, y,
Width - SystemInformation.HorizontalScrollBarArrowWidth,
Height - SystemInformation.VerticalScrollBarArrowHeight - h);
SetScrollBarBounds();
}
public void SetScrollBarBounds()
{
vScrollBar.Bounds = new Rectangle(textArea.Bounds.Right, 0, SystemInformation.HorizontalScrollBarArrowWidth, Height - SystemInformation.VerticalScrollBarArrowHeight);
hScrollBar.Bounds = new Rectangle(0,
textArea.Bounds.Bottom,
Width - SystemInformation.HorizontalScrollBarArrowWidth,
SystemInformation.VerticalScrollBarArrowHeight);
}
bool adjustScrollBarsOnNextUpdate;
Point scrollToPosOnNextUpdate;
void AdjustScrollBarsOnDocumentChange(object sender, DocumentEventArgs e)
{
if (motherTextEditorControl.IsInUpdate == false) {
AdjustScrollBarsClearCache();
AdjustScrollBars();
} else {
adjustScrollBarsOnNextUpdate = true;
}
}
void DocumentUpdateCommitted(object sender, EventArgs e)
{
if (motherTextEditorControl.IsInUpdate == false) {
Caret.ValidateCaretPos();
// AdjustScrollBarsOnCommittedUpdate
if (!scrollToPosOnNextUpdate.IsEmpty) {
ScrollTo(scrollToPosOnNextUpdate.Y, scrollToPosOnNextUpdate.X);
}
if (adjustScrollBarsOnNextUpdate) {
AdjustScrollBarsClearCache();
AdjustScrollBars();
}
}
}
int[] lineLengthCache;
const int LineLengthCacheAdditionalSize = 100;
void AdjustScrollBarsClearCache()
{
if (lineLengthCache != null) {
if (lineLengthCache.Length < this.Document.TotalNumberOfLines + 2 * LineLengthCacheAdditionalSize) {
lineLengthCache = null;
} else {
Array.Clear(lineLengthCache, 0, lineLengthCache.Length);
}
}
}
public void AdjustScrollBars()
{
adjustScrollBarsOnNextUpdate = false;
vScrollBar.Minimum = 0;
// number of visible lines in document (folding!)
vScrollBar.Maximum = textArea.MaxVScrollValue;
int max = 0;
int firstLine = textArea.TextView.FirstVisibleLine;
int lastLine = this.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + textArea.TextView.VisibleLineCount);
if (lastLine >= this.Document.TotalNumberOfLines)
lastLine = this.Document.TotalNumberOfLines - 1;
if (lineLengthCache == null || lineLengthCache.Length <= lastLine) {
lineLengthCache = new int[lastLine + LineLengthCacheAdditionalSize];
}
for (int lineNumber = firstLine; lineNumber <= lastLine; lineNumber++) {
LineSegment lineSegment = this.Document.GetLineSegment(lineNumber);
if (Document.FoldingManager.IsLineVisible(lineNumber)) {
if (lineLengthCache[lineNumber] > 0) {
max = Math.Max(max, lineLengthCache[lineNumber]);
} else {
int visualLength = textArea.TextView.GetVisualColumnFast(lineSegment, lineSegment.Length);
lineLengthCache[lineNumber] = Math.Max(1, visualLength);
max = Math.Max(max, visualLength);
}
}
}
hScrollBar.Minimum = 0;
hScrollBar.Maximum = (Math.Max(max + 20, textArea.TextView.VisibleColumnCount - 1));
vScrollBar.LargeChange = Math.Max(0, textArea.TextView.DrawingPosition.Height);
vScrollBar.SmallChange = Math.Max(0, textArea.TextView.FontHeight);
hScrollBar.LargeChange = Math.Max(0, textArea.TextView.VisibleColumnCount - 1);
hScrollBar.SmallChange = Math.Max(0, (int)textArea.TextView.SpaceWidth);
}
public void OptionsChanged()
{
textArea.OptionsChanged();
if (textArea.TextEditorProperties.ShowHorizontalRuler) {
if (hRuler == null) {
hRuler = new HRuler(textArea);
Controls.Add(hRuler);
ResizeTextArea();
} else {
hRuler.Invalidate();
}
} else {
if (hRuler != null) {
Controls.Remove(hRuler);
hRuler.Dispose();
hRuler = null;
ResizeTextArea();
}
}
AdjustScrollBars();
}
void VScrollBarValueChanged(object sender, EventArgs e)
{
textArea.VirtualTop = new Point(textArea.VirtualTop.X, vScrollBar.Value);
textArea.Invalidate();
AdjustScrollBars();
}
void HScrollBarValueChanged(object sender, EventArgs e)
{
textArea.VirtualTop = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, textArea.VirtualTop.Y);
textArea.Invalidate();
}
Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();
public void HandleMouseWheel(MouseEventArgs e)
{
int scrollDistance = mouseWheelHandler.GetScrollAmount(e);
if (scrollDistance == 0)
return;
if ((Control.ModifierKeys & Keys.Control) != 0 && TextEditorProperties.MouseWheelTextZoom) {
if (scrollDistance > 0) {
motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
motherTextEditorControl.Font.Size + 1);
} else {
motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
Math.Max(6, motherTextEditorControl.Font.Size - 1));
}
} else {
if (TextEditorProperties.MouseWheelScrollDown)
scrollDistance = -scrollDistance;
int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
if (DoHandleMousewheel) {
HandleMouseWheel(e);
}
}
public void ScrollToCaret()
{
ScrollTo(textArea.Caret.Line, textArea.Caret.Column);
}
public void ScrollTo(int line, int column)
{
if (motherTextEditorControl.IsInUpdate) {
scrollToPosOnNextUpdate = new Point(column, line);
return;
} else {
scrollToPosOnNextUpdate = Point.Empty;
}
ScrollTo(line);
int curCharMin = (int)(this.hScrollBar.Value - this.hScrollBar.Minimum);
int curCharMax = curCharMin + textArea.TextView.VisibleColumnCount;
int pos = textArea.TextView.GetVisualColumn(line, column);
if (textArea.TextView.VisibleColumnCount < 0) {
hScrollBar.Value = 0;
} else {
if (pos < curCharMin) {
hScrollBar.Value = (int)(Math.Max(0, pos - scrollMarginHeight));
} else {
if (pos > curCharMax) {
hScrollBar.Value = (int)Math.Max(0, Math.Min(hScrollBar.Maximum, (pos - textArea.TextView.VisibleColumnCount + scrollMarginHeight)));
}
}
}
}
int scrollMarginHeight = 3;
/// <summary>
/// Ensure that <paramref name="line"/> is visible.
/// </summary>
public void ScrollTo(int line)
{
line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
line = Document.GetVisibleLine(line);
int curLineMin = textArea.TextView.FirstPhysicalLine;
if (textArea.TextView.LineHeightRemainder > 0) {
curLineMin ++;
}
if (line - scrollMarginHeight + 3 < curLineMin) {
this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
VScrollBarValueChanged(this, EventArgs.Empty);
} else {
int curLineMax = curLineMin + this.textArea.TextView.VisibleLineCount;
if (line + scrollMarginHeight - 1 > curLineMax) {
if (this.textArea.TextView.VisibleLineCount == 1) {
this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight - 1) * textArea.TextView.FontHeight)) ;
} else {
this.vScrollBar.Value = Math.Min(this.vScrollBar.Maximum,
(line - this.textArea.TextView.VisibleLineCount + scrollMarginHeight - 1)* textArea.TextView.FontHeight) ;
}
VScrollBarValueChanged(this, EventArgs.Empty);
}
}
}
/// <summary>
/// Scroll so that the specified line is centered.
/// </summary>
/// <param name="line">Line to center view on</param>
/// <param name="treshold">If this action would cause scrolling by less than or equal to
/// <paramref name="treshold"/> lines in any direction, don't scroll.
/// Use -1 to always center the view.</param>
public void CenterViewOn(int line, int treshold)
{
line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
// convert line to visible line:
line = Document.GetVisibleLine(line);
// subtract half the visible line count
line -= textArea.TextView.VisibleLineCount / 2;
int curLineMin = textArea.TextView.FirstPhysicalLine;
if (textArea.TextView.LineHeightRemainder > 0) {
curLineMin ++;
}
if (Math.Abs(curLineMin - line) > treshold) {
// scroll:
this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
VScrollBarValueChanged(this, EventArgs.Empty);
}
}
public void JumpTo(int line)
{
line = Math.Max(0, Math.Min(line, Document.TotalNumberOfLines - 1));
string text = Document.GetText(Document.GetLineSegment(line));
JumpTo(line, text.Length - text.TrimStart().Length);
}
public void JumpTo(int line, int column)
{
textArea.Focus();
textArea.SelectionManager.ClearSelection();
textArea.Caret.Position = new TextLocation(column, line);
textArea.SetDesiredColumn();
ScrollToCaret();
}
public event MouseEventHandler ShowContextMenu;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x007B) { // handle WM_CONTEXTMENU
if (ShowContextMenu != null) {
long lParam = m.LParam.ToInt64();
int x = unchecked((short)(lParam & 0xffff));
int y = unchecked((short)((lParam & 0xffff0000) >> 16));
if (x == -1 && y == -1) {
Point pos = Caret.ScreenPosition;
ShowContextMenu(this, new MouseEventArgs(MouseButtons.None, 0, pos.X, pos.Y + textArea.TextView.FontHeight, 0));
} else {
Point pos = PointToClient(new Point(x, y));
ShowContextMenu(this, new MouseEventArgs(MouseButtons.Right, 1, pos.X, pos.Y, 0));
}
}
}
base.WndProc(ref m);
}
protected override void OnEnter(EventArgs e)
{
// SD2-1072 - Make sure the caret line is valid if anyone
// has handlers for the Enter event.
Caret.ValidateCaretPos();
base.OnEnter(e);
}
}
}

View File

@@ -0,0 +1,144 @@
// <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.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
public class TextAreaDragDropHandler
{
public static Action<Exception> OnDragDropException = ex => MessageBox.Show(ex.ToString());
TextArea textArea;
public void Attach(TextArea textArea)
{
this.textArea = textArea;
textArea.AllowDrop = true;
textArea.DragEnter += MakeDragEventHandler(OnDragEnter);
textArea.DragDrop += MakeDragEventHandler(OnDragDrop);
textArea.DragOver += MakeDragEventHandler(OnDragOver);
}
/// <summary>
/// Create a drag'n'drop event handler.
/// Windows Forms swallows unhandled exceptions during drag'n'drop, so we report them here.
/// </summary>
static DragEventHandler MakeDragEventHandler(DragEventHandler h)
{
return (sender, e) => {
try {
h(sender, e);
} catch (Exception ex) {
OnDragDropException(ex);
}
};
}
static DragDropEffects GetDragDropEffect(DragEventArgs e)
{
if ((e.AllowedEffect & DragDropEffects.Move) > 0 &&
(e.AllowedEffect & DragDropEffects.Copy) > 0) {
return (e.KeyState & 8) > 0 ? DragDropEffects.Copy : DragDropEffects.Move;
} else if ((e.AllowedEffect & DragDropEffects.Move) > 0) {
return DragDropEffects.Move;
} else if ((e.AllowedEffect & DragDropEffects.Copy) > 0) {
return DragDropEffects.Copy;
}
return DragDropEffects.None;
}
protected void OnDragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(string))) {
e.Effect = GetDragDropEffect(e);
}
}
void InsertString(int offset, string str)
{
textArea.Document.Insert(offset, str);
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document,
textArea.Document.OffsetToPosition(offset),
textArea.Document.OffsetToPosition(offset + str.Length)));
textArea.Caret.Position = textArea.Document.OffsetToPosition(offset + str.Length);
textArea.Refresh();
}
protected void OnDragDrop(object sender, DragEventArgs e)
{
Point p = textArea.PointToClient(new Point(e.X, e.Y));
if (e.Data.GetDataPresent(typeof(string))) {
textArea.BeginUpdate();
textArea.Document.UndoStack.StartUndoGroup();
try {
int offset = textArea.Caret.Offset;
if (textArea.IsReadOnly(offset)) {
// prevent dragging text into readonly section
return;
}
if (e.Data.GetDataPresent(typeof(DefaultSelection))) {
ISelection sel = (ISelection)e.Data.GetData(typeof(DefaultSelection));
if (sel.ContainsPosition(textArea.Caret.Position)) {
return;
}
if (GetDragDropEffect(e) == DragDropEffects.Move) {
if (SelectionManager.SelectionIsReadOnly(textArea.Document, sel)) {
// prevent dragging text out of readonly section
return;
}
int len = sel.Length;
textArea.Document.Remove(sel.Offset, len);
if (sel.Offset < offset) {
offset -= len;
}
}
}
textArea.SelectionManager.ClearSelection();
InsertString(offset, (string)e.Data.GetData(typeof(string)));
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
} finally {
textArea.Document.UndoStack.EndUndoGroup();
textArea.EndUpdate();
}
}
}
protected void OnDragOver(object sender, DragEventArgs e)
{
if (!textArea.Focused) {
textArea.Focus();
}
Point p = textArea.PointToClient(new Point(e.X, e.Y));
if (textArea.TextView.DrawingPosition.Contains(p.X, p.Y)) {
TextLocation realmousepos= textArea.TextView.GetLogicalPosition(p.X - textArea.TextView.DrawingPosition.X,
p.Y - textArea.TextView.DrawingPosition.Y);
int lineNr = Math.Min(textArea.Document.TotalNumberOfLines - 1, Math.Max(0, realmousepos.Y));
textArea.Caret.Position = new TextLocation(realmousepos.X, lineNr);
textArea.SetDesiredColumn();
if (e.Data.GetDataPresent(typeof(string)) && !textArea.IsReadOnly(textArea.Caret.Offset)) {
e.Effect = GetDragDropEffect(e);
} else {
e.Effect = DragDropEffects.None;
}
} else {
e.Effect = DragDropEffects.None;
}
}
}
}

View File

@@ -0,0 +1,492 @@
// <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;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class handles all mouse stuff for a textArea.
/// </summary>
public class TextAreaMouseHandler
{
TextArea textArea;
bool doubleclick = false;
bool clickedOnSelectedText = false;
MouseButtons button;
static readonly Point nilPoint = new Point(-1, -1);
Point mousedownpos = nilPoint;
Point lastmousedownpos = nilPoint;
bool gotmousedown = false;
bool dodragdrop = false;
public TextAreaMouseHandler(TextArea ttextArea)
{
textArea = ttextArea;
}
public void Attach()
{
textArea.Click += new EventHandler(TextAreaClick);
textArea.MouseMove += new MouseEventHandler(TextAreaMouseMove);
textArea.MouseDown += new MouseEventHandler(OnMouseDown);
textArea.DoubleClick += new EventHandler(OnDoubleClick);
textArea.MouseLeave += new EventHandler(OnMouseLeave);
textArea.MouseUp += new MouseEventHandler(OnMouseUp);
textArea.LostFocus += new EventHandler(TextAreaLostFocus);
textArea.ToolTipRequest += new ToolTipRequestEventHandler(OnToolTipRequest);
}
void OnToolTipRequest(object sender, ToolTipRequestEventArgs e)
{
if (e.ToolTipShown)
return;
Point mousepos = e.MousePosition;
FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (marker != null && marker.IsFolded) {
StringBuilder sb = new StringBuilder(marker.InnerText);
// max 10 lines
int endLines = 0;
for (int i = 0; i < sb.Length; ++i) {
if (sb[i] == '\n') {
++endLines;
if (endLines >= 10) {
sb.Remove(i + 1, sb.Length - i - 1);
sb.Append(Environment.NewLine);
sb.Append("...");
break;
}
}
}
sb.Replace("\t", " ");
e.ShowToolTip(sb.ToString());
return;
}
List<TextMarker> markers = textArea.Document.MarkerStrategy.GetMarkers(e.LogicalPosition);
foreach (TextMarker tm in markers) {
if (tm.ToolTip != null) {
e.ShowToolTip(tm.ToolTip.Replace("\t", " "));
return;
}
}
}
void ShowHiddenCursorIfMovedOrLeft()
{
textArea.ShowHiddenCursor(!textArea.Focused ||
!textArea.ClientRectangle.Contains(textArea.PointToClient(Cursor.Position)));
}
void TextAreaLostFocus(object sender, EventArgs e)
{
// The call to ShowHiddenCursorIfMovedOrLeft is delayed
// until pending messages have been processed
// so that it can properly detect whether the TextArea
// has really lost focus.
// For example, the CodeCompletionWindow gets focus when it is shown,
// but immediately gives back focus to the TextArea.
textArea.BeginInvoke(new MethodInvoker(ShowHiddenCursorIfMovedOrLeft));
}
void OnMouseLeave(object sender, EventArgs e)
{
ShowHiddenCursorIfMovedOrLeft();
gotmousedown = false;
mousedownpos = nilPoint;
}
void OnMouseUp(object sender, MouseEventArgs e)
{
textArea.SelectionManager.selectFrom.where = WhereFrom.None;
gotmousedown = false;
mousedownpos = nilPoint;
}
void TextAreaClick(object sender, EventArgs e)
{
Point mousepos;
mousepos = textArea.mousepos;
if (dodragdrop)
{
return;
}
if (clickedOnSelectedText && textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y))
{
textArea.SelectionManager.ClearSelection();
TextLocation clickPosition = textArea.TextView.GetLogicalPosition(
mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
textArea.Caret.Position = clickPosition;
textArea.SetDesiredColumn();
}
}
void TextAreaMouseMove(object sender, MouseEventArgs e)
{
textArea.mousepos = e.Location;
// honour the starting selection strategy
switch (textArea.SelectionManager.selectFrom.where)
{
case WhereFrom.Gutter:
ExtendSelectionToMouse();
return;
case WhereFrom.TArea:
break;
}
textArea.ShowHiddenCursor(false);
if (dodragdrop) {
dodragdrop = false;
return;
}
doubleclick = false;
textArea.mousepos = new Point(e.X, e.Y);
if (clickedOnSelectedText) {
if (Math.Abs(mousedownpos.X - e.X) >= SystemInformation.DragSize.Width / 2 ||
Math.Abs(mousedownpos.Y - e.Y) >= SystemInformation.DragSize.Height / 2)
{
clickedOnSelectedText = false;
ISelection selection = textArea.SelectionManager.GetSelectionAt(textArea.Caret.Offset);
if (selection != null) {
string text = selection.SelectedText;
bool isReadOnly = SelectionManager.SelectionIsReadOnly(textArea.Document, selection);
if (text != null && text.Length > 0) {
DataObject dataObject = new DataObject ();
dataObject.SetData(DataFormats.UnicodeText, true, text);
dataObject.SetData(selection);
dodragdrop = true;
textArea.DoDragDrop(dataObject, isReadOnly ? DragDropEffects.All & ~DragDropEffects.Move : DragDropEffects.All);
}
}
}
return;
}
if (e.Button == MouseButtons.Left) {
if (gotmousedown && textArea.SelectionManager.selectFrom.where == WhereFrom.TArea)
{
ExtendSelectionToMouse();
}
}
}
void ExtendSelectionToMouse()
{
Point mousepos;
mousepos = textArea.mousepos;
TextLocation realmousepos = textArea.TextView.GetLogicalPosition(
Math.Max(0, mousepos.X - textArea.TextView.DrawingPosition.X),
mousepos.Y - textArea.TextView.DrawingPosition.Y);
int y = realmousepos.Y;
realmousepos = textArea.Caret.ValidatePosition(realmousepos);
TextLocation oldPos = textArea.Caret.Position;
if (oldPos == realmousepos && textArea.SelectionManager.selectFrom.where != WhereFrom.Gutter)
{
return;
}
// the selection is from the gutter
if (textArea.SelectionManager.selectFrom.where == WhereFrom.Gutter) {
if(realmousepos.Y < textArea.SelectionManager.SelectionStart.Y) {
// the selection has moved above the startpoint
textArea.Caret.Position = new TextLocation(0, realmousepos.Y);
} else {
// the selection has moved below the startpoint
textArea.Caret.Position = textArea.SelectionManager.NextValidPosition(realmousepos.Y);
}
} else {
textArea.Caret.Position = realmousepos;
}
// moves selection across whole words for double-click initiated selection
if (!minSelection.IsEmpty && textArea.SelectionManager.SelectionCollection.Count > 0 && textArea.SelectionManager.selectFrom.where == WhereFrom.TArea) {
// Extend selection when selection was started with double-click
ISelection selection = textArea.SelectionManager.SelectionCollection[0];
TextLocation min = textArea.SelectionManager.GreaterEqPos(minSelection, maxSelection) ? maxSelection : minSelection;
TextLocation max = textArea.SelectionManager.GreaterEqPos(minSelection, maxSelection) ? minSelection : maxSelection;
if (textArea.SelectionManager.GreaterEqPos(max, realmousepos) && textArea.SelectionManager.GreaterEqPos(realmousepos, min)) {
textArea.SelectionManager.SetSelection(min, max);
} else if (textArea.SelectionManager.GreaterEqPos(max, realmousepos)) {
int moff = textArea.Document.PositionToOffset(realmousepos);
min = textArea.Document.OffsetToPosition(FindWordStart(textArea.Document, moff));
textArea.SelectionManager.SetSelection(min, max);
} else {
int moff = textArea.Document.PositionToOffset(realmousepos);
max = textArea.Document.OffsetToPosition(FindWordEnd(textArea.Document, moff));
textArea.SelectionManager.SetSelection(min, max);
}
} else {
textArea.SelectionManager.ExtendSelection(oldPos, textArea.Caret.Position);
}
textArea.SetDesiredColumn();
}
void DoubleClickSelectionExtend()
{
Point mousepos;
mousepos = textArea.mousepos;
textArea.SelectionManager.ClearSelection();
if (textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y))
{
FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (marker != null && marker.IsFolded) {
marker.IsFolded = false;
textArea.MotherTextAreaControl.AdjustScrollBars();
}
if (textArea.Caret.Offset < textArea.Document.TextLength) {
switch (textArea.Document.GetCharAt(textArea.Caret.Offset)) {
case '"':
if (textArea.Caret.Offset < textArea.Document.TextLength) {
int next = FindNext(textArea.Document, textArea.Caret.Offset + 1, '"');
minSelection = textArea.Caret.Position;
if (next > textArea.Caret.Offset && next < textArea.Document.TextLength)
next += 1;
maxSelection = textArea.Document.OffsetToPosition(next);
}
break;
default:
minSelection = textArea.Document.OffsetToPosition(FindWordStart(textArea.Document, textArea.Caret.Offset));
maxSelection = textArea.Document.OffsetToPosition(FindWordEnd(textArea.Document, textArea.Caret.Offset));
break;
}
textArea.Caret.Position = maxSelection;
textArea.SelectionManager.ExtendSelection(minSelection, maxSelection);
}
if (textArea.SelectionManager.selectionCollection.Count > 0) {
ISelection selection = textArea.SelectionManager.selectionCollection[0];
selection.StartPosition = minSelection;
selection.EndPosition = maxSelection;
textArea.SelectionManager.SelectionStart = minSelection;
}
// after a double-click selection, the caret is placed correctly,
// but it is not positioned internally. The effect is when the cursor
// is moved up or down a line, the caret will take on the column first
// clicked on for the double-click
textArea.SetDesiredColumn();
// HACK WARNING !!!
// must refresh here, because when a error tooltip is showed and the underlined
// code is double clicked the textArea don't update corrctly, updateline doesn't
// work ... but the refresh does.
// Mike
textArea.Refresh();
}
}
void OnMouseDown(object sender, MouseEventArgs e)
{
Point mousepos;
textArea.mousepos = e.Location;
mousepos = e.Location;
if (dodragdrop)
{
return;
}
if (doubleclick) {
doubleclick = false;
return;
}
if (textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y)) {
gotmousedown = true;
textArea.SelectionManager.selectFrom.where = WhereFrom.TArea;
button = e.Button;
// double-click
if (button == MouseButtons.Left && e.Clicks == 2) {
int deltaX = Math.Abs(lastmousedownpos.X - e.X);
int deltaY = Math.Abs(lastmousedownpos.Y - e.Y);
if (deltaX <= SystemInformation.DoubleClickSize.Width &&
deltaY <= SystemInformation.DoubleClickSize.Height) {
DoubleClickSelectionExtend();
lastmousedownpos = new Point(e.X, e.Y);
if (textArea.SelectionManager.selectFrom.where == WhereFrom.Gutter) {
if (!minSelection.IsEmpty && !maxSelection.IsEmpty && textArea.SelectionManager.SelectionCollection.Count > 0) {
textArea.SelectionManager.SelectionCollection[0].StartPosition = minSelection;
textArea.SelectionManager.SelectionCollection[0].EndPosition = maxSelection;
textArea.SelectionManager.SelectionStart = minSelection;
minSelection = TextLocation.Empty;
maxSelection = TextLocation.Empty;
}
}
return;
}
}
minSelection = TextLocation.Empty;
maxSelection = TextLocation.Empty;
lastmousedownpos = mousedownpos = new Point(e.X, e.Y);
if (button == MouseButtons.Left) {
FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X,
mousepos.Y - textArea.TextView.DrawingPosition.Y);
if (marker != null && marker.IsFolded) {
if (textArea.SelectionManager.HasSomethingSelected) {
clickedOnSelectedText = true;
}
TextLocation startLocation = new TextLocation(marker.StartColumn, marker.StartLine);
TextLocation endLocation = new TextLocation(marker.EndColumn, marker.EndLine);
textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.TextView.Document, startLocation, endLocation));
textArea.Caret.Position = startLocation;
textArea.SetDesiredColumn();
textArea.Focus();
return;
}
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) {
ExtendSelectionToMouse();
} else {
TextLocation realmousepos = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
clickedOnSelectedText = false;
int offset = textArea.Document.PositionToOffset(realmousepos);
if (textArea.SelectionManager.HasSomethingSelected &&
textArea.SelectionManager.IsSelected(offset)) {
clickedOnSelectedText = true;
} else {
textArea.SelectionManager.ClearSelection();
if (mousepos.Y > 0 && mousepos.Y < textArea.TextView.DrawingPosition.Height) {
TextLocation pos = new TextLocation();
pos.Y = Math.Min(textArea.Document.TotalNumberOfLines - 1, realmousepos.Y);
pos.X = realmousepos.X;
textArea.Caret.Position = pos;
textArea.SetDesiredColumn();
}
}
}
} else if (button == MouseButtons.Right) {
// Rightclick sets the cursor to the click position unless
// the previous selection was clicked
TextLocation realmousepos = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
int offset = textArea.Document.PositionToOffset(realmousepos);
if (!textArea.SelectionManager.HasSomethingSelected ||
!textArea.SelectionManager.IsSelected(offset))
{
textArea.SelectionManager.ClearSelection();
if (mousepos.Y > 0 && mousepos.Y < textArea.TextView.DrawingPosition.Height) {
TextLocation pos = new TextLocation();
pos.Y = Math.Min(textArea.Document.TotalNumberOfLines - 1, realmousepos.Y);
pos.X = realmousepos.X;
textArea.Caret.Position = pos;
textArea.SetDesiredColumn();
}
}
}
}
textArea.Focus();
}
int FindNext(IDocument document, int offset, char ch)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
int endPos = line.Offset + line.Length;
while (offset < endPos && document.GetCharAt(offset) != ch) {
++offset;
}
return offset;
}
bool IsSelectableChar(char ch)
{
return char.IsLetterOrDigit(ch) || ch=='_';
}
int FindWordStart(IDocument document, int offset)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
if (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset - 1)) && char.IsWhiteSpace(document.GetCharAt(offset))) {
while (offset > line.Offset && char.IsWhiteSpace(document.GetCharAt(offset - 1))) {
--offset;
}
} else if (IsSelectableChar(document.GetCharAt(offset)) || (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset)) && IsSelectableChar(document.GetCharAt(offset - 1)))) {
while (offset > line.Offset && IsSelectableChar(document.GetCharAt(offset - 1))) {
--offset;
}
} else {
if (offset > 0 && !char.IsWhiteSpace(document.GetCharAt(offset - 1)) && !IsSelectableChar(document.GetCharAt(offset - 1)) ) {
return Math.Max(0, offset - 1);
}
}
return offset;
}
int FindWordEnd(IDocument document, int offset)
{
LineSegment line = document.GetLineSegmentForOffset(offset);
if (line.Length == 0)
return offset;
int endPos = line.Offset + line.Length;
offset = Math.Min(offset, endPos - 1);
if (IsSelectableChar(document.GetCharAt(offset))) {
while (offset < endPos && IsSelectableChar(document.GetCharAt(offset))) {
++offset;
}
} else if (char.IsWhiteSpace(document.GetCharAt(offset))) {
if (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset - 1))) {
while (offset < endPos && char.IsWhiteSpace(document.GetCharAt(offset))) {
++offset;
}
}
} else {
return Math.Max(0, offset + 1);
}
return offset;
}
TextLocation minSelection = TextLocation.Empty;
TextLocation maxSelection = TextLocation.Empty;
void OnDoubleClick(object sender, System.EventArgs e)
{
if (dodragdrop) {
return;
}
textArea.SelectionManager.selectFrom.where = WhereFrom.TArea;
doubleclick = true;
}
}
}

View File

@@ -0,0 +1,85 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This enum describes all implemented request types
/// </summary>
public enum TextAreaUpdateType {
WholeTextArea,
SingleLine,
SinglePosition,
PositionToLineEnd,
PositionToEnd,
LinesBetween
}
/// <summary>
/// This class is used to request an update of the textarea
/// </summary>
public class TextAreaUpdate
{
TextLocation position;
TextAreaUpdateType type;
public TextAreaUpdateType TextAreaUpdateType {
get {
return type;
}
}
public TextLocation Position {
get {
return position;
}
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type)
{
this.type = type;
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type, TextLocation position)
{
this.type = type;
this.position = position;
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type, int startLine, int endLine)
{
this.type = type;
this.position = new TextLocation(startLine, endLine);
}
/// <summary>
/// Creates a new instance of <see cref="TextAreaUpdate"/>
/// </summary>
public TextAreaUpdate(TextAreaUpdateType type, int singleLine)
{
this.type = type;
this.position = new TextLocation(0, singleLine);
}
public override string ToString()
{
return string.Format("[TextAreaUpdate: Type={0}, Position={1}]", type, position);
}
}
}

View File

@@ -0,0 +1,396 @@
// <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.ComponentModel;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class is used for a basic text area control
/// </summary>
[ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")]
[ToolboxItem(true)]
public class TextEditorControl : TextEditorControlBase
{
protected Panel textAreaPanel = new Panel();
TextAreaControl primaryTextArea;
Splitter textAreaSplitter = null;
TextAreaControl secondaryTextArea = null;
PrintDocument printDocument = null;
[Browsable(false)]
public PrintDocument PrintDocument {
get {
if (printDocument == null) {
printDocument = new PrintDocument();
printDocument.BeginPrint += new PrintEventHandler(this.BeginPrint);
printDocument.PrintPage += new PrintPageEventHandler(this.PrintPage);
}
return printDocument;
}
}
TextAreaControl activeTextAreaControl;
public override TextAreaControl ActiveTextAreaControl {
get {
return activeTextAreaControl;
}
}
protected void SetActiveTextAreaControl(TextAreaControl value)
{
if (activeTextAreaControl != value) {
activeTextAreaControl = value;
if (ActiveTextAreaControlChanged != null) {
ActiveTextAreaControlChanged(this, EventArgs.Empty);
}
}
}
public event EventHandler ActiveTextAreaControlChanged;
public TextEditorControl()
{
SetStyle(ControlStyles.ContainerControl, true);
textAreaPanel.Dock = DockStyle.Fill;
Document = (new DocumentFactory()).CreateDocument();
Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy();
primaryTextArea = new TextAreaControl(this);
activeTextAreaControl = primaryTextArea;
primaryTextArea.TextArea.GotFocus += delegate {
SetActiveTextAreaControl(primaryTextArea);
};
primaryTextArea.Dock = DockStyle.Fill;
textAreaPanel.Controls.Add(primaryTextArea);
InitializeTextAreaControl(primaryTextArea);
Controls.Add(textAreaPanel);
ResizeRedraw = true;
Document.UpdateCommited += new EventHandler(CommitUpdateRequested);
OptionsChanged();
}
protected virtual void InitializeTextAreaControl(TextAreaControl newControl)
{
}
public override void OptionsChanged()
{
primaryTextArea.OptionsChanged();
if (secondaryTextArea != null) {
secondaryTextArea.OptionsChanged();
}
}
public void Split()
{
if (secondaryTextArea == null) {
secondaryTextArea = new TextAreaControl(this);
secondaryTextArea.Dock = DockStyle.Bottom;
secondaryTextArea.Height = Height / 2;
secondaryTextArea.TextArea.GotFocus += delegate {
SetActiveTextAreaControl(secondaryTextArea);
};
textAreaSplitter = new Splitter();
textAreaSplitter.BorderStyle = BorderStyle.FixedSingle ;
textAreaSplitter.Height = 8;
textAreaSplitter.Dock = DockStyle.Bottom;
textAreaPanel.Controls.Add(textAreaSplitter);
textAreaPanel.Controls.Add(secondaryTextArea);
InitializeTextAreaControl(secondaryTextArea);
secondaryTextArea.OptionsChanged();
} else {
SetActiveTextAreaControl(primaryTextArea);
textAreaPanel.Controls.Remove(secondaryTextArea);
textAreaPanel.Controls.Remove(textAreaSplitter);
secondaryTextArea.Dispose();
textAreaSplitter.Dispose();
secondaryTextArea = null;
textAreaSplitter = null;
}
}
[Browsable(false)]
public bool EnableUndo {
get {
return Document.UndoStack.CanUndo;
}
}
[Browsable(false)]
public bool EnableRedo {
get {
return Document.UndoStack.CanRedo;
}
}
public void Undo()
{
if (Document.ReadOnly) {
return;
}
if (Document.UndoStack.CanUndo) {
BeginUpdate();
Document.UndoStack.Undo();
Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
this.primaryTextArea.TextArea.UpdateMatchingBracket();
if (secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateMatchingBracket();
}
EndUpdate();
}
}
public void Redo()
{
if (Document.ReadOnly) {
return;
}
if (Document.UndoStack.CanRedo) {
BeginUpdate();
Document.UndoStack.Redo();
Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
this.primaryTextArea.TextArea.UpdateMatchingBracket();
if (secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateMatchingBracket();
}
EndUpdate();
}
}
public virtual void SetHighlighting(string name)
{
Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(name);
}
protected override void Dispose(bool disposing)
{
if (disposing) {
if (printDocument != null) {
printDocument.BeginPrint -= new PrintEventHandler(this.BeginPrint);
printDocument.PrintPage -= new PrintPageEventHandler(this.PrintPage);
printDocument = null;
}
Document.UndoStack.ClearAll();
Document.UpdateCommited -= new EventHandler(CommitUpdateRequested);
if (textAreaPanel != null) {
if (secondaryTextArea != null) {
secondaryTextArea.Dispose();
textAreaSplitter.Dispose();
secondaryTextArea = null;
textAreaSplitter = null;
}
if (primaryTextArea != null) {
primaryTextArea.Dispose();
}
textAreaPanel.Dispose();
textAreaPanel = null;
}
}
base.Dispose(disposing);
}
#region Update Methods
public override void EndUpdate()
{
base.EndUpdate();
Document.CommitUpdate();
if (!IsInUpdate) {
ActiveTextAreaControl.Caret.OnEndUpdate();
}
}
void CommitUpdateRequested(object sender, EventArgs e)
{
if (IsInUpdate) {
return;
}
foreach (TextAreaUpdate update in Document.UpdateQueue) {
switch (update.TextAreaUpdateType) {
case TextAreaUpdateType.PositionToEnd:
this.primaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateToEnd(update.Position.Y);
}
break;
case TextAreaUpdateType.PositionToLineEnd:
case TextAreaUpdateType.SingleLine:
this.primaryTextArea.TextArea.UpdateLine(update.Position.Y);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y);
}
break;
case TextAreaUpdateType.SinglePosition:
this.primaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X);
}
break;
case TextAreaUpdateType.LinesBetween:
this.primaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y);
}
break;
case TextAreaUpdateType.WholeTextArea:
this.primaryTextArea.TextArea.Invalidate();
if (this.secondaryTextArea != null) {
this.secondaryTextArea.TextArea.Invalidate();
}
break;
}
}
Document.UpdateQueue.Clear();
// this.primaryTextArea.TextArea.Update();
// if (this.secondaryTextArea != null) {
// this.secondaryTextArea.TextArea.Update();
// }
}
#endregion
#region Printing routines
int curLineNr = 0;
float curTabIndent = 0;
StringFormat printingStringFormat;
void BeginPrint(object sender, PrintEventArgs ev)
{
curLineNr = 0;
printingStringFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();
// 100 should be enough for everyone ...err ?
float[] tabStops = new float[100];
for (int i = 0; i < tabStops.Length; ++i) {
tabStops[i] = TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth;
}
printingStringFormat.SetTabStops(0, tabStops);
}
void Advance(ref float x, ref float y, float maxWidth, float size, float fontHeight)
{
if (x + size < maxWidth) {
x += size;
} else {
x = curTabIndent;
y += fontHeight;
}
}
// btw. I hate source code duplication ... but this time I don't care !!!!
float MeasurePrintingHeight(Graphics g, LineSegment line, float maxWidth)
{
float xPos = 0;
float yPos = 0;
float fontHeight = Font.GetHeight(g);
// bool gotNonWhitespace = false;
curTabIndent = 0;
FontContainer fontContainer = TextEditorProperties.FontContainer;
foreach (TextWord word in line.Words) {
switch (word.Type) {
case TextWordType.Space:
Advance(ref xPos, ref yPos, maxWidth, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Tab:
Advance(ref xPos, ref yPos, maxWidth, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Word:
// if (!gotNonWhitespace) {
// gotNonWhitespace = true;
// curTabIndent += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
// }
SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(maxWidth, fontHeight * 100), printingStringFormat);
Advance(ref xPos, ref yPos, maxWidth, drawingSize.Width, fontHeight);
break;
}
}
return yPos + fontHeight;
}
void DrawLine(Graphics g, LineSegment line, float yPos, RectangleF margin)
{
float xPos = 0;
float fontHeight = Font.GetHeight(g);
// bool gotNonWhitespace = false;
curTabIndent = 0 ;
FontContainer fontContainer = TextEditorProperties.FontContainer;
foreach (TextWord word in line.Words) {
switch (word.Type) {
case TextWordType.Space:
Advance(ref xPos, ref yPos, margin.Width, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Tab:
Advance(ref xPos, ref yPos, margin.Width, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight);
// if (!gotNonWhitespace) {
// curTabIndent = xPos;
// }
break;
case TextWordType.Word:
// if (!gotNonWhitespace) {
// gotNonWhitespace = true;
// curTabIndent += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' ');
// }
g.DrawString(word.Word, word.GetFont(fontContainer), BrushRegistry.GetBrush(word.Color), xPos + margin.X, yPos);
SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(margin.Width, fontHeight * 100), printingStringFormat);
Advance(ref xPos, ref yPos, margin.Width, drawingSize.Width, fontHeight);
break;
}
}
}
void PrintPage(object sender, PrintPageEventArgs ev)
{
Graphics g = ev.Graphics;
float yPos = ev.MarginBounds.Top;
while (curLineNr < Document.TotalNumberOfLines) {
LineSegment curLine = Document.GetLineSegment(curLineNr);
if (curLine.Words != null) {
float drawingHeight = MeasurePrintingHeight(g, curLine, ev.MarginBounds.Width);
if (drawingHeight + yPos > ev.MarginBounds.Bottom) {
break;
}
DrawLine(g, curLine, yPos, ev.MarginBounds);
yPos += drawingHeight;
}
++curLineNr;
}
// If more lines exist, print another page.
ev.HasMorePages = curLineNr < Document.TotalNumberOfLines;
}
#endregion
}
}

View File

@@ -0,0 +1,759 @@
// <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.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Text;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor
{
/// <summary>
/// This class is used for a basic text area control
/// </summary>
[ToolboxItem(false)]
public abstract class TextEditorControlBase : UserControl
{
string currentFileName = null;
int updateLevel = 0;
protected IDocument document; // Stef Heyenrath : Changed to protected
/// <summary>
/// This hashtable contains all editor keys, where
/// the key is the key combination and the value the
/// action.
/// </summary>
protected Dictionary<Keys, IEditAction> editactions = new Dictionary<Keys, IEditAction>();
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ITextEditorProperties TextEditorProperties {
get {
return document.TextEditorProperties;
}
set {
document.TextEditorProperties = value;
OptionsChanged();
}
}
Encoding encoding;
/// <value>
/// Current file's character encoding
/// </value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Encoding Encoding {
get {
if (encoding == null)
return TextEditorProperties.Encoding;
return encoding;
}
set {
encoding = value;
}
}
/// <value>
/// The current file name
/// </value>
[Browsable(false)]
[ReadOnly(true)]
public string FileName {
get {
return currentFileName;
}
set {
if (currentFileName != value) {
currentFileName = value;
OnFileNameChanged(EventArgs.Empty);
}
}
}
/// <value>
/// The current document
/// </value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IDocument Document {
get {
return document;
}
set {
if (value == null)
throw new ArgumentNullException("value");
if (document != null) {
document.DocumentChanged -= OnDocumentChanged;
}
document = value;
document.UndoStack.TextEditorControl = this;
document.DocumentChanged += OnDocumentChanged;
}
}
protected void OnDocumentChanged(object sender, EventArgs e) // Stef Heyenrath : changed to protected
{
OnTextChanged(e);
}
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))]
public override string Text {
get {
return Document.TextContent;
}
set {
Document.TextContent = value;
}
}
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true)]
public new event EventHandler TextChanged
{
add { base.TextChanged += value; }
remove { base.TextChanged -= value; }
}
static Font ParseFont(string font)
{
string[] descr = font.Split(new char[]{',', '='});
return new Font(descr[1], float.Parse(descr[3]));
}
/// <value>
/// If set to true the contents can't be altered.
/// </value>
[Browsable(false)]
public bool IsReadOnly {
get {
return Document.ReadOnly;
}
set {
Document.ReadOnly = value;
}
}
/// <value>
/// true, if the textarea is updating it's status, while
/// it updates it status no redraw operation occurs.
/// </value>
[Browsable(false)]
public bool IsInUpdate {
get {
return updateLevel > 0;
}
}
/// <value>
/// supposedly this is the way to do it according to .NET docs,
/// as opposed to setting the size in the constructor
/// </value>
protected override Size DefaultSize {
get {
return new Size(100, 100);
}
}
#region Document Properties
/// <value>
/// If true spaces are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true spaces are shown in the textarea")]
public bool ShowSpaces {
get {
return document.TextEditorProperties.ShowSpaces;
}
set {
document.TextEditorProperties.ShowSpaces = value;
OptionsChanged();
}
}
/// <value>
/// Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing).
/// </value>
[Category("Appearance")]
[DefaultValue(TextRenderingHint.SystemDefault)]
[Description("Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing).")]
public TextRenderingHint TextRenderingHint {
get {
return document.TextEditorProperties.TextRenderingHint;
}
set {
document.TextEditorProperties.TextRenderingHint = value;
OptionsChanged();
}
}
/// <value>
/// If true tabs are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true tabs are shown in the textarea")]
public bool ShowTabs {
get {
return document.TextEditorProperties.ShowTabs;
}
set {
document.TextEditorProperties.ShowTabs = value;
OptionsChanged();
}
}
/// <value>
/// If true EOL markers are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true EOL markers are shown in the textarea")]
public bool ShowEOLMarkers {
get {
return document.TextEditorProperties.ShowEOLMarker;
}
set {
document.TextEditorProperties.ShowEOLMarker = value;
OptionsChanged();
}
}
/// <value>
/// If true the horizontal ruler is shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true the horizontal ruler is shown in the textarea")]
public bool ShowHRuler {
get {
return document.TextEditorProperties.ShowHorizontalRuler;
}
set {
document.TextEditorProperties.ShowHorizontalRuler = value;
OptionsChanged();
}
}
/// <value>
/// If true the vertical ruler is shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true the vertical ruler is shown in the textarea")]
public bool ShowVRuler {
get {
return document.TextEditorProperties.ShowVerticalRuler;
}
set {
document.TextEditorProperties.ShowVerticalRuler = value;
OptionsChanged();
}
}
/// <value>
/// The row in which the vertical ruler is displayed
/// </value>
[Category("Appearance")]
[DefaultValue(80)]
[Description("The row in which the vertical ruler is displayed")]
public int VRulerRow {
get {
return document.TextEditorProperties.VerticalRulerRow;
}
set {
document.TextEditorProperties.VerticalRulerRow = value;
OptionsChanged();
}
}
/// <value>
/// If true line numbers are shown in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true line numbers are shown in the textarea")]
public bool ShowLineNumbers {
get {
return document.TextEditorProperties.ShowLineNumbers;
}
set {
document.TextEditorProperties.ShowLineNumbers = value;
OptionsChanged();
}
}
/// <value>
/// If true invalid lines are marked in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true invalid lines are marked in the textarea")]
public bool ShowInvalidLines {
get {
return document.TextEditorProperties.ShowInvalidLines;
}
set {
document.TextEditorProperties.ShowInvalidLines = value;
OptionsChanged();
}
}
/// <value>
/// If true folding is enabled in the textarea
/// </value>
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true folding is enabled in the textarea")]
public bool EnableFolding {
get {
return document.TextEditorProperties.EnableFolding;
}
set {
document.TextEditorProperties.EnableFolding = value;
OptionsChanged();
}
}
[Category("Appearance")]
[DefaultValue(true)]
[Description("If true matching brackets are highlighted")]
public bool ShowMatchingBracket {
get {
return document.TextEditorProperties.ShowMatchingBracket;
}
set {
document.TextEditorProperties.ShowMatchingBracket = value;
OptionsChanged();
}
}
[Category("Appearance")]
[DefaultValue(false)]
[Description("If true the icon bar is displayed")]
public bool IsIconBarVisible {
get {
return document.TextEditorProperties.IsIconBarVisible;
}
set {
document.TextEditorProperties.IsIconBarVisible = value;
OptionsChanged();
}
}
/// <value>
/// The width in spaces of a tab character
/// </value>
[Category("Appearance")]
[DefaultValue(4)]
[Description("The width in spaces of a tab character")]
public int TabIndent {
get {
return document.TextEditorProperties.TabIndent;
}
set {
document.TextEditorProperties.TabIndent = value;
OptionsChanged();
}
}
/// <value>
/// The line viewer style
/// </value>
[Category("Appearance")]
[DefaultValue(LineViewerStyle.None)]
[Description("The line viewer style")]
public LineViewerStyle LineViewerStyle {
get {
return document.TextEditorProperties.LineViewerStyle;
}
set {
document.TextEditorProperties.LineViewerStyle = value;
OptionsChanged();
}
}
/// <value>
/// The indent style
/// </value>
[Category("Behavior")]
[DefaultValue(IndentStyle.Smart)]
[Description("The indent style")]
public IndentStyle IndentStyle {
get {
return document.TextEditorProperties.IndentStyle;
}
set {
document.TextEditorProperties.IndentStyle = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(false)]
[Description("Converts tabs to spaces while typing")]
public bool ConvertTabsToSpaces {
get {
return document.TextEditorProperties.ConvertTabsToSpaces;
}
set {
document.TextEditorProperties.ConvertTabsToSpaces = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(false)]
[Description("Hide the mouse cursor while typing")]
public bool HideMouseCursor {
get {
return document.TextEditorProperties.HideMouseCursor;
}
set {
document.TextEditorProperties.HideMouseCursor = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(false)]
[Description("Allows the caret to be placed beyond the end of line")]
public bool AllowCaretBeyondEOL {
get {
return document.TextEditorProperties.AllowCaretBeyondEOL;
}
set {
document.TextEditorProperties.AllowCaretBeyondEOL = value;
OptionsChanged();
}
}
/// <value>
/// if true spaces are converted to tabs
/// </value>
[Category("Behavior")]
[DefaultValue(BracketMatchingStyle.After)]
[Description("Specifies if the bracket matching should match the bracket before or after the caret.")]
public BracketMatchingStyle BracketMatchingStyle {
get {
return document.TextEditorProperties.BracketMatchingStyle;
}
set {
document.TextEditorProperties.BracketMatchingStyle = value;
OptionsChanged();
}
}
/// <value>
/// The base font of the text area. No bold or italic fonts
/// can be used because bold/italic is reserved for highlighting
/// purposes.
/// </value>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(typeof(Font), null)] // Stef_H
[Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")]
public override Font Font {
get {
return document.TextEditorProperties.Font;
}
set {
document.TextEditorProperties.Font = value;
OptionsChanged();
}
}
#endregion
public abstract TextAreaControl ActiveTextAreaControl {
get;
}
protected TextEditorControlBase()
{
GenerateDefaultActions();
HighlightingManager.Manager.ReloadSyntaxHighlighting += new EventHandler(OnReloadHighlighting);
}
protected virtual void OnReloadHighlighting(object sender, EventArgs e)
{
if (Document.HighlightingStrategy != null) {
try {
Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(Document.HighlightingStrategy.Name);
} catch (HighlightingDefinitionInvalidException ex) {
MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
OptionsChanged();
}
}
public bool IsEditAction(Keys keyData)
{
return editactions.ContainsKey(keyData);
}
internal IEditAction GetEditAction(Keys keyData)
{
if (!IsEditAction(keyData)) {
return null;
}
return (IEditAction)editactions[keyData];
}
void GenerateDefaultActions()
{
editactions[Keys.Left] = new CaretLeft();
editactions[Keys.Left | Keys.Shift] = new ShiftCaretLeft();
editactions[Keys.Left | Keys.Control] = new WordLeft();
editactions[Keys.Left | Keys.Control | Keys.Shift] = new ShiftWordLeft();
editactions[Keys.Right] = new CaretRight();
editactions[Keys.Right | Keys.Shift] = new ShiftCaretRight();
editactions[Keys.Right | Keys.Control] = new WordRight();
editactions[Keys.Right | Keys.Control | Keys.Shift] = new ShiftWordRight();
editactions[Keys.Up] = new CaretUp();
editactions[Keys.Up | Keys.Shift] = new ShiftCaretUp();
editactions[Keys.Up | Keys.Control] = new ScrollLineUp();
editactions[Keys.Down] = new CaretDown();
editactions[Keys.Down | Keys.Shift] = new ShiftCaretDown();
editactions[Keys.Down | Keys.Control] = new ScrollLineDown();
editactions[Keys.Insert] = new ToggleEditMode();
editactions[Keys.Insert | Keys.Control] = new Copy();
editactions[Keys.Insert | Keys.Shift] = new Paste();
editactions[Keys.Delete] = new Delete();
editactions[Keys.Delete | Keys.Shift] = new Cut();
editactions[Keys.Home] = new Home();
editactions[Keys.Home | Keys.Shift] = new ShiftHome();
editactions[Keys.Home | Keys.Control] = new MoveToStart();
editactions[Keys.Home | Keys.Control | Keys.Shift] = new ShiftMoveToStart();
editactions[Keys.End] = new End();
editactions[Keys.End | Keys.Shift] = new ShiftEnd();
editactions[Keys.End | Keys.Control] = new MoveToEnd();
editactions[Keys.End | Keys.Control | Keys.Shift] = new ShiftMoveToEnd();
editactions[Keys.PageUp] = new MovePageUp();
editactions[Keys.PageUp | Keys.Shift] = new ShiftMovePageUp();
editactions[Keys.PageDown] = new MovePageDown();
editactions[Keys.PageDown | Keys.Shift] = new ShiftMovePageDown();
editactions[Keys.Return] = new Return();
editactions[Keys.Tab] = new Tab();
editactions[Keys.Tab | Keys.Shift] = new ShiftTab();
editactions[Keys.Back] = new Backspace();
editactions[Keys.Back | Keys.Shift] = new Backspace();
editactions[Keys.X | Keys.Control] = new Cut();
editactions[Keys.C | Keys.Control] = new Copy();
editactions[Keys.V | Keys.Control] = new Paste();
editactions[Keys.A | Keys.Control] = new SelectWholeDocument();
editactions[Keys.Escape] = new ClearAllSelections();
editactions[Keys.Divide | Keys.Control] = new ToggleComment();
editactions[Keys.OemQuestion | Keys.Control] = new ToggleComment();
editactions[Keys.Back | Keys.Alt] = new Actions.Undo();
editactions[Keys.Z | Keys.Control] = new Actions.Undo();
editactions[Keys.Y | Keys.Control] = new Redo();
editactions[Keys.Delete | Keys.Control] = new DeleteWord();
editactions[Keys.Back | Keys.Control] = new WordBackspace();
editactions[Keys.D | Keys.Control] = new DeleteLine();
editactions[Keys.D | Keys.Shift | Keys.Control] = new DeleteToLineEnd();
editactions[Keys.B | Keys.Control] = new GotoMatchingBrace();
}
/// <remarks>
/// Call this method before a long update operation this
/// 'locks' the text area so that no screen update occurs.
/// </remarks>
public virtual void BeginUpdate()
{
++updateLevel;
}
/// <remarks>
/// Call this method to 'unlock' the text area. After this call
/// screen update can occur. But no automatical refresh occurs you
/// have to commit the updates in the queue.
/// </remarks>
public virtual void EndUpdate()
{
Debug.Assert(updateLevel > 0);
updateLevel = Math.Max(0, updateLevel - 1);
}
public void LoadFile(string fileName)
{
LoadFile(fileName, true, true);
}
/// <remarks>
/// Loads a file given by fileName
/// </remarks>
/// <param name="fileName">The name of the file to open</param>
/// <param name="autoLoadHighlighting">Automatically load the highlighting for the file</param>
/// <param name="autodetectEncoding">Automatically detect file encoding and set Encoding property to the detected encoding.</param>
public void LoadFile(string fileName, bool autoLoadHighlighting, bool autodetectEncoding)
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
LoadFile(fileName, fs, autoLoadHighlighting, autodetectEncoding);
}
}
/// <remarks>
/// Loads a file from the specified stream.
/// </remarks>
/// <param name="fileName">The name of the file to open. Used to find the correct highlighting strategy
/// if autoLoadHighlighting is active, and sets the filename property to this value.</param>
/// <param name="stream">The stream to actually load the file content from.</param>
/// <param name="autoLoadHighlighting">Automatically load the highlighting for the file</param>
/// <param name="autodetectEncoding">Automatically detect file encoding and set Encoding property to the detected encoding.</param>
public void LoadFile(string fileName, Stream stream, bool autoLoadHighlighting, bool autodetectEncoding)
{
if (stream == null)
throw new ArgumentNullException("stream");
BeginUpdate();
document.TextContent = string.Empty;
document.UndoStack.ClearAll();
document.BookmarkManager.Clear();
if (autoLoadHighlighting) {
try {
document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategyForFile(fileName);
} catch (HighlightingDefinitionInvalidException ex) {
MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
if (autodetectEncoding) {
Encoding encoding = this.Encoding;
Document.TextContent = Util.FileReader.ReadFileContent(stream, ref encoding);
this.Encoding = encoding;
} else {
using (StreamReader reader = new StreamReader(fileName, this.Encoding)) {
Document.TextContent = reader.ReadToEnd();
}
}
this.FileName = fileName;
Document.UpdateQueue.Clear();
EndUpdate();
OptionsChanged();
Refresh();
}
/// <summary>
/// Gets if the document can be saved with the current encoding without losing data.
/// </summary>
public bool CanSaveWithCurrentEncoding()
{
if (encoding == null || Util.FileReader.IsUnicode(encoding))
return true;
// not a unicode codepage
string text = document.TextContent;
return encoding.GetString(encoding.GetBytes(text)) == text;
}
/// <remarks>
/// Saves the text editor content into the file.
/// </remarks>
public void SaveFile(string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) {
SaveFile(fs);
}
this.FileName = fileName;
}
/// <remarks>
/// Saves the text editor content into the specified stream.
/// Does not close the stream.
/// </remarks>
public void SaveFile(Stream stream)
{
StreamWriter streamWriter = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8);
// save line per line to apply the LineTerminator to all lines
// (otherwise we might save files with mixed-up line endings)
foreach (LineSegment line in Document.LineSegmentCollection) {
streamWriter.Write(Document.GetText(line.Offset, line.Length));
if (line.DelimiterLength > 0) {
char charAfterLine = Document.GetCharAt(line.Offset + line.Length);
if (charAfterLine != '\n' && charAfterLine != '\r')
throw new InvalidOperationException("The document cannot be saved because it is corrupted.");
// only save line terminator if the line has one
streamWriter.Write(document.TextEditorProperties.LineTerminator);
}
}
streamWriter.Flush();
}
public abstract void OptionsChanged();
// Localization ISSUES
// used in insight window
public virtual string GetRangeDescription(int selectedItem, int itemCount)
{
StringBuilder sb=new StringBuilder(selectedItem.ToString());
sb.Append(" from ");
sb.Append(itemCount.ToString());
return sb.ToString();
}
/// <remarks>
/// Overwritten refresh method that does nothing if the control is in
/// an update cycle.
/// </remarks>
public override void Refresh()
{
if (IsInUpdate) {
return;
}
base.Refresh();
}
protected override void Dispose(bool disposing)
{
if (disposing) {
HighlightingManager.Manager.ReloadSyntaxHighlighting -= new EventHandler(OnReloadHighlighting);
document.HighlightingStrategy = null;
document.UndoStack.TextEditorControl = null;
}
base.Dispose(disposing);
}
protected virtual void OnFileNameChanged(EventArgs e)
{
if (FileNameChanged != null) {
FileNameChanged(this, e);
}
}
public event EventHandler FileNameChanged;
}
}

View File

@@ -0,0 +1,541 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Actions;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Properties;
using ICSharpCode.TextEditor.Src.Actions;
using ICSharpCode.TextEditor.Src.Document.FoldingStrategy;
using ICSharpCode.TextEditor.Src.Document.HighlightingStrategy.SyntaxModes;
using ICSharpCode.TextEditor.UserControls;
// ReSharper disable once CheckNamespace
namespace ICSharpCode.TextEditor
{
[ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")]
[ToolboxItem(true)]
public class TextEditorControlEx : TextEditorControl
{
private bool _contextMenuEnabled;
private bool _contextMenuShowDefaultIcons;
private bool _contextMenuShowShortCutKeys;
private readonly FindAndReplaceForm _findForm = new FindAndReplaceForm();
public TextEditorControlEx()
{
editactions[Keys.Control | Keys.F] = new EditFindAction(_findForm, this);
editactions[Keys.Control | Keys.H] = new EditReplaceAction(_findForm, this);
editactions[Keys.F3] = new FindAgainAction(_findForm, this);
editactions[Keys.F3 | Keys.Shift] = new FindAgainReverseAction(_findForm, this);
editactions[Keys.Control | Keys.G] = new GoToLineNumberAction();
// Add additional Syntax highlighting providers
HighlightingManager.Manager.AddSyntaxModeFileProvider(new ResourceSyntaxModeProviderEx());
TextChanged += TextChangedEventHandler;
}
protected override void OnLoad(EventArgs e)
{
if (ContextMenuEnabled)
{
AssignContextMenu(CreateNewContextMenu(ContextMenuShowDefaultIcons, ContextMenuShowShortCutKeys));
}
base.OnLoad(e);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
TextChanged -= TextChangedEventHandler;
}
private void TextChangedEventHandler(object sender, EventArgs e)
{
var editor = sender as TextEditorControlEx;
if (editor != null)
{
bool vScrollBarIsNeeded = editor.Document.TotalNumberOfLines > ActiveTextAreaControl.TextArea.TextView.VisibleLineCount;
if (ActiveTextAreaControl.VScrollBar.Visible && HideVScrollBarIfPossible && !vScrollBarIsNeeded)
{
ActiveTextAreaControl.ShowScrollBars(Orientation.Vertical, false);
}
}
}
#region Extended properties
public string SelectedText
{
get
{
return ActiveTextAreaControl.SelectionManager.SelectedText;
}
}
public string[] Lines
{
get
{
return base.Text.Split(new[] { "\r\n" }, StringSplitOptions.None);
}
}
#endregion
#region Designer Properties
/// <value>
/// The current document
/// </value>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new IDocument Document
{
get
{
return document;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (document != null)
{
document.DocumentChanged -= OnDocumentChanged;
document.DocumentChanged -= OnDocumentChangedDoUpdateContextMenu;
}
document = value;
document.UndoStack.TextEditorControl = this;
document.DocumentChanged += OnDocumentChanged;
document.DocumentChanged += OnDocumentChangedDoUpdateContextMenu;
}
}
/// <value>
/// The base font of the text area. No bold or italic fonts
/// can be used because bold/italic is reserved for highlighting
/// purposes.
/// </value>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(typeof(Font), null)]
[Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")]
public override Font Font
{
get
{
return Document.TextEditorProperties.Font;
}
set
{
Document.TextEditorProperties.Font = value;
OptionsChanged();
}
}
[Category("Appearance")]
[DefaultValue(false)]
[Description("Hide the vertical ScrollBar if it's not needed. ")]
public bool HideVScrollBarIfPossible { get; set; }
private string _foldingStrategy;
[Category("Appearance")]
[Description("Set the Folding Strategy. Supported : XML and CSharp.")]
public string FoldingStrategy
{
get
{
return _foldingStrategy;
}
set
{
SetFoldingStrategy(value);
OptionsChanged();
}
}
private string _syntaxHighlighting;
[Category("Appearance")]
[Description("Sets the Syntax Highlighting.")]
public string SyntaxHighlighting
{
get
{
return _syntaxHighlighting;
}
set
{
_syntaxHighlighting = value;
SetHighlighting(_syntaxHighlighting);
OptionsChanged();
}
}
[Category("Behavior")]
[DefaultValue(false)]
[Description("If true document is readonly.")]
[Browsable(true)]
public new bool IsReadOnly
{
get
{
return Document.ReadOnly;
}
set
{
Document.ReadOnly = value;
OptionsChanged();
}
}
[DefaultValue(false)]
[Category("Appearance")]
[Description("Show default Icons in ContextMenu")]
[Browsable(true)]
public bool ContextMenuShowDefaultIcons
{
get
{
return _contextMenuShowDefaultIcons & _contextMenuEnabled;
}
set
{
_contextMenuShowDefaultIcons = _contextMenuEnabled & value;
}
}
[DefaultValue(false)]
[Category("Appearance")]
[Description("Show shortcut keys in ContextMenu")]
[Browsable(true)]
public bool ContextMenuShowShortCutKeys
{
get
{
return _contextMenuShowShortCutKeys & _contextMenuEnabled;
}
set
{
_contextMenuShowShortCutKeys = _contextMenuEnabled & value;
}
}
[DefaultValue(false)]
[Category("Appearance")]
[Description("Enable a ContextMenu")]
[Browsable(true)]
public bool ContextMenuEnabled
{
get
{
return _contextMenuEnabled;
}
set
{
_contextMenuEnabled = value;
}
}
#endregion
/// <summary>
/// Sets the text and refreshes the control.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="updateFoldings">if set to <c>true</c> [update foldings].</param>
public void SetTextAndRefresh(string text, bool updateFoldings = false)
{
ResetText();
Text = text;
if (updateFoldings && Document.TextEditorProperties.EnableFolding)
{
Document.FoldingManager.UpdateFoldings(null, null);
}
Refresh();
}
/// <summary>
/// Sets the folding strategy. Currently only XML is supported.
/// </summary>
/// <param name="foldingStrategy">The foldingStrategy.</param>
public void SetFoldingStrategy(string foldingStrategy)
{
if (foldingStrategy == null)
{
throw new ArgumentNullException("foldingStrategy");
}
if (!Document.TextEditorProperties.EnableFolding)
{
return;
}
switch (foldingStrategy)
{
case "XML":
_foldingStrategy = foldingStrategy;
Document.FoldingManager.FoldingStrategy = new XmlFoldingStrategy();
break;
case "C#":
_foldingStrategy = foldingStrategy;
Document.FoldingManager.FoldingStrategy = new CSharpFoldingStrategy();
break;
case "JSON":
_foldingStrategy = foldingStrategy;
Document.FoldingManager.FoldingStrategy = new JSONFoldingStrategy();
break;
default:
Document.FoldingManager.FoldingStrategy = null;
_foldingStrategy = null;
break;
}
Document.FoldingManager.UpdateFoldings(null, null);
}
/// <summary>
/// Gets the folding errors. Currently only XML is supported.
/// </summary>
/// <returns>List of errors, else empty list</returns>
public List<string> GetFoldingErrors()
{
if (_foldingStrategy == "XML")
{
var foldingStrategy = Document.FoldingManager.FoldingStrategy as IFoldingStrategyEx;
if (foldingStrategy != null)
{
return foldingStrategy.GetFoldingErrors();
}
}
return new List<string>();
}
#region ContextMenu Commands implementations
private bool CanUndo()
{
return Document.UndoStack.CanUndo;
}
private bool CanRedo()
{
return Document.UndoStack.CanRedo;
}
private bool CanCopy()
{
return ActiveTextAreaControl.SelectionManager.HasSomethingSelected;
}
private bool CanCut()
{
return ActiveTextAreaControl.SelectionManager.HasSomethingSelected;
}
private bool CanDelete()
{
return ActiveTextAreaControl.SelectionManager.HasSomethingSelected;
}
private bool CanPaste()
{
return ActiveTextAreaControl.TextArea.ClipboardHandler.EnablePaste;
}
private bool CanSelectAll()
{
if (Document.TextContent == null)
return false;
return !Document.TextContent.Trim().Equals(string.Empty);
}
private bool CanFind()
{
if (Document.TextContent == null)
return false;
return Document.TextContent.Trim().Any();
}
private void DoCut()
{
new Cut().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoDelete()
{
new Delete().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoCopy()
{
new Copy().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoPaste()
{
new Paste().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
private void DoSelectAll()
{
new SelectWholeDocument().Execute(ActiveTextAreaControl.TextArea);
ActiveTextAreaControl.Focus();
}
public void DoToggleFoldings()
{
new ToggleAllFoldings().Execute(ActiveTextAreaControl.TextArea);
}
private void DoFind()
{
new EditFindAction(_findForm, this).Execute(ActiveTextAreaControl.TextArea);
}
#endregion
#region ContextMenu Initialization
private ContextMenuStrip CreateNewContextMenu(bool showImages, bool showKeys)
{
var mnu = new ContextMenuStripEx();
mnu.AddToolStripMenuItem("&Undo",
showImages ? Resources.sc_undo : null,
(sender, e) => Undo(),
showKeys ? Keys.Control | Keys.Z : Keys.None,
CanUndo
);
mnu.AddToolStripMenuItem("&Redo",
showImages ? Resources.sc_redo : null,
(sender, e) => Redo(),
showKeys ? Keys.Control | Keys.Y : Keys.None,
CanRedo
);
mnu.AddToolStripSeparator();
mnu.AddToolStripMenuItem("&Cut",
showImages ? Resources.cut : null,
(sender, e) => DoCut(),
showKeys ? Keys.Control | Keys.X : Keys.None,
CanCut
);
mnu.AddToolStripMenuItem("Cop&y",
showImages ? Resources.sc_copy : null,
(sender, e) => DoCopy(),
showKeys ? Keys.Control | Keys.C : Keys.None,
CanCopy
);
mnu.AddToolStripMenuItem("&Paste",
showImages ? Resources.sc_paste : null,
(sender, e) => DoPaste(),
showKeys ? Keys.Control | Keys.V : Keys.None,
CanPaste
);
mnu.AddToolStripSeparator();
mnu.AddToolStripMenuItem("&Delete",
showImages ? Resources.sc_cancel : null,
(sender, e) => DoDelete(),
showKeys ? Keys.Delete : Keys.None,
CanDelete
);
mnu.AddToolStripMenuItem("&Select All",
showImages ? Resources.sc_selectall : null,
(sender, e) => DoSelectAll(),
showKeys ? Keys.Control | Keys.A : Keys.None,
CanSelectAll
);
mnu.AddToolStripMenuItem("&Find",
showImages ? Resources.sc_searchdialog : null,
(sender, e) => DoFind(),
showKeys ? Keys.Control | Keys.F : Keys.None,
CanFind
);
return mnu;
}
private void AssignContextMenu(ContextMenuStrip mnu)
{
if (ActiveTextAreaControl.ContextMenuStrip != null)
{
ActiveTextAreaControl.ContextMenuStrip.Dispose();
ActiveTextAreaControl.ContextMenuStrip = null;
}
ActiveTextAreaControl.ContextMenuStrip = mnu;
}
#endregion
#region ContextMenu Methods
public void SelectText(int start, int length)
{
var textLength = Document.TextLength;
if (textLength < (start + length))
{
length = (textLength - 1) - start;
}
ActiveTextAreaControl.Caret.Position = Document.OffsetToPosition(start + length);
ActiveTextAreaControl.SelectionManager.ClearSelection();
ActiveTextAreaControl.SelectionManager.SetSelection(new DefaultSelection(Document, Document.OffsetToPosition(start), Document.OffsetToPosition(start + length)));
Refresh();
}
private void OnDocumentChangedDoUpdateContextMenu(object sender, DocumentEventArgs e)
{
bool isVisible = (Document.TotalNumberOfLines > ActiveTextAreaControl.TextArea.TextView.VisibleLineCount);
ActiveTextAreaControl.VScrollBar.Visible = isVisible;
}
#endregion
}
public static class TextAreaControlExtensions
{
/// <summary>
/// Extension method to show a scrollbar.
/// </summary>
/// <param name="textAreaControl">The text area control.</param>
/// <param name="orientation">The orientation.</param>
/// <param name="isVisible">if set to <c>true</c> [is visible].</param>
public static void ShowScrollBars(this TextAreaControl textAreaControl, Orientation orientation, bool isVisible)
{
if (orientation == Orientation.Vertical)
{
textAreaControl.VScrollBar.Visible = isVisible;
}
else
{
textAreaControl.HScrollBar.Visible = isVisible;
}
}
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More