first commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
203
ICSharpCode.TextEditor/Project/Src/Actions/CaretActions.cs
Normal file
203
ICSharpCode.TextEditor/Project/Src/Actions/CaretActions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
ICSharpCode.TextEditor/Project/Src/Actions/FoldActions.cs
Normal file
68
ICSharpCode.TextEditor/Project/Src/Actions/FoldActions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
219
ICSharpCode.TextEditor/Project/Src/Actions/FormatActions.cs
Normal file
219
ICSharpCode.TextEditor/Project/Src/Actions/FormatActions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
ICSharpCode.TextEditor/Project/Src/Actions/HomeEndActions.cs
Normal file
114
ICSharpCode.TextEditor/Project/Src/Actions/HomeEndActions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
ICSharpCode.TextEditor/Project/Src/Actions/IEditAction.cs
Normal file
58
ICSharpCode.TextEditor/Project/Src/Actions/IEditAction.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
902
ICSharpCode.TextEditor/Project/Src/Actions/MiscActions.cs
Normal file
902
ICSharpCode.TextEditor/Project/Src/Actions/MiscActions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
176
ICSharpCode.TextEditor/Project/Src/Actions/SelectionActions.cs
Normal file
176
ICSharpCode.TextEditor/Project/Src/Actions/SelectionActions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used to describe a span inside a text sequence
|
||||
/// </summary>
|
||||
public class AbstractSegment : ISegment
|
||||
{
|
||||
[CLSCompliant(false)]
|
||||
protected int offset = -1;
|
||||
[CLSCompliant(false)]
|
||||
protected int length = -1;
|
||||
|
||||
#region ICSharpCode.TextEditor.Document.ISegment interface implementation
|
||||
public virtual int Offset {
|
||||
get {
|
||||
return offset;
|
||||
}
|
||||
set {
|
||||
offset = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int Length {
|
||||
get {
|
||||
return length;
|
||||
}
|
||||
set {
|
||||
length = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[AbstractSegment: Offset = {0}, Length = {1}]",
|
||||
Offset,
|
||||
Length);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using SWF = System.Windows.Forms;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of Bookmark.
|
||||
/// </summary>
|
||||
public class Bookmark
|
||||
{
|
||||
IDocument document;
|
||||
TextAnchor anchor;
|
||||
TextLocation location;
|
||||
bool isEnabled = true;
|
||||
|
||||
public IDocument Document {
|
||||
get {
|
||||
return document;
|
||||
}
|
||||
set {
|
||||
if (document != value) {
|
||||
if (anchor != null) {
|
||||
location = anchor.Location;
|
||||
anchor = null;
|
||||
}
|
||||
document = value;
|
||||
CreateAnchor();
|
||||
OnDocumentChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateAnchor()
|
||||
{
|
||||
if (document != null) {
|
||||
LineSegment line = document.GetLineSegment(Math.Max(0, Math.Min(location.Line, document.TotalNumberOfLines-1)));
|
||||
anchor = line.CreateAnchor(Math.Max(0, Math.Min(location.Column, line.Length)));
|
||||
// after insertion: keep bookmarks after the initial whitespace (see DefaultFormattingStrategy.SmartReplaceLine)
|
||||
anchor.MovementType = AnchorMovementType.AfterInsertion;
|
||||
anchor.Deleted += AnchorDeleted;
|
||||
}
|
||||
}
|
||||
|
||||
void AnchorDeleted(object sender, EventArgs e)
|
||||
{
|
||||
document.BookmarkManager.RemoveMark(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TextAnchor used for this bookmark.
|
||||
/// Is null if the bookmark is not connected to a document.
|
||||
/// </summary>
|
||||
public TextAnchor Anchor {
|
||||
get { return anchor; }
|
||||
}
|
||||
|
||||
public TextLocation Location {
|
||||
get {
|
||||
if (anchor != null)
|
||||
return anchor.Location;
|
||||
else
|
||||
return location;
|
||||
}
|
||||
set {
|
||||
location = value;
|
||||
CreateAnchor();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler DocumentChanged;
|
||||
|
||||
protected virtual void OnDocumentChanged(EventArgs e)
|
||||
{
|
||||
if (DocumentChanged != null) {
|
||||
DocumentChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled {
|
||||
get {
|
||||
return isEnabled;
|
||||
}
|
||||
set {
|
||||
if (isEnabled != value) {
|
||||
isEnabled = value;
|
||||
if (document != null) {
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, LineNumber));
|
||||
document.CommitUpdate();
|
||||
}
|
||||
OnIsEnabledChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler IsEnabledChanged;
|
||||
|
||||
protected virtual void OnIsEnabledChanged(EventArgs e)
|
||||
{
|
||||
if (IsEnabledChanged != null) {
|
||||
IsEnabledChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public int LineNumber {
|
||||
get {
|
||||
if (anchor != null)
|
||||
return anchor.LineNumber;
|
||||
else
|
||||
return location.Line;
|
||||
}
|
||||
}
|
||||
|
||||
public int ColumnNumber {
|
||||
get {
|
||||
if (anchor != null)
|
||||
return anchor.ColumnNumber;
|
||||
else
|
||||
return location.Column;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the bookmark can be toggled off using the 'set/unset bookmark' command.
|
||||
/// </summary>
|
||||
public virtual bool CanToggle {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Bookmark(IDocument document, TextLocation location) : this(document, location, true)
|
||||
{
|
||||
}
|
||||
|
||||
public Bookmark(IDocument document, TextLocation location, bool isEnabled)
|
||||
{
|
||||
this.document = document;
|
||||
this.isEnabled = isEnabled;
|
||||
this.Location = location;
|
||||
}
|
||||
|
||||
public virtual bool Click(SWF.Control parent, SWF.MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == SWF.MouseButtons.Left && CanToggle) {
|
||||
document.BookmarkManager.RemoveMark(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Draw(IconBarMargin margin, Graphics g, Point p)
|
||||
{
|
||||
margin.DrawBookmark(g, p.Y, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public delegate void BookmarkEventHandler(object sender, BookmarkEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Description of BookmarkEventHandler.
|
||||
/// </summary>
|
||||
public class BookmarkEventArgs : EventArgs
|
||||
{
|
||||
Bookmark bookmark;
|
||||
|
||||
public Bookmark Bookmark {
|
||||
get {
|
||||
return bookmark;
|
||||
}
|
||||
}
|
||||
|
||||
public BookmarkEventArgs(Bookmark bookmark)
|
||||
{
|
||||
this.bookmark = bookmark;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using ICSharpCode.TextEditor.Util;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public interface IBookmarkFactory
|
||||
{
|
||||
Bookmark CreateBookmark(IDocument document, TextLocation location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class handles the bookmarks for a buffer
|
||||
/// </summary>
|
||||
public class BookmarkManager
|
||||
{
|
||||
IDocument document;
|
||||
#if DEBUG
|
||||
IList<Bookmark> bookmark = new CheckedList<Bookmark>();
|
||||
#else
|
||||
List<Bookmark> bookmark = new List<Bookmark>();
|
||||
#endif
|
||||
|
||||
/// <value>
|
||||
/// Contains all bookmarks
|
||||
/// </value>
|
||||
public ReadOnlyCollection<Bookmark> Marks {
|
||||
get {
|
||||
return new ReadOnlyCollection<Bookmark>(bookmark);
|
||||
}
|
||||
}
|
||||
|
||||
public IDocument Document {
|
||||
get {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="BookmarkManager"/>
|
||||
/// </summary>
|
||||
internal BookmarkManager(IDocument document, LineManager lineTracker)
|
||||
{
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the bookmark factory used to create bookmarks for "ToggleMarkAt".
|
||||
/// </summary>
|
||||
public IBookmarkFactory Factory { get; set;}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mark at the line <code>location.Line</code> if it is not set, if the
|
||||
/// line is already marked the mark is cleared.
|
||||
/// </summary>
|
||||
public void ToggleMarkAt(TextLocation location)
|
||||
{
|
||||
Bookmark newMark;
|
||||
if (Factory != null) {
|
||||
newMark = Factory.CreateBookmark(document, location);
|
||||
} else {
|
||||
newMark = new Bookmark(document, location);
|
||||
}
|
||||
|
||||
Type newMarkType = newMark.GetType();
|
||||
|
||||
for (int i = 0; i < bookmark.Count; ++i) {
|
||||
Bookmark mark = bookmark[i];
|
||||
|
||||
if (mark.LineNumber == location.Line && mark.CanToggle && mark.GetType() == newMarkType) {
|
||||
bookmark.RemoveAt(i);
|
||||
OnRemoved(new BookmarkEventArgs(mark));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bookmark.Add(newMark);
|
||||
OnAdded(new BookmarkEventArgs(newMark));
|
||||
}
|
||||
|
||||
public void AddMark(Bookmark mark)
|
||||
{
|
||||
bookmark.Add(mark);
|
||||
OnAdded(new BookmarkEventArgs(mark));
|
||||
}
|
||||
|
||||
public void RemoveMark(Bookmark mark)
|
||||
{
|
||||
bookmark.Remove(mark);
|
||||
OnRemoved(new BookmarkEventArgs(mark));
|
||||
}
|
||||
|
||||
public void RemoveMarks(Predicate<Bookmark> predicate)
|
||||
{
|
||||
for (int i = 0; i < bookmark.Count; ++i) {
|
||||
Bookmark bm = bookmark[i];
|
||||
if (predicate(bm)) {
|
||||
bookmark.RemoveAt(i--);
|
||||
OnRemoved(new BookmarkEventArgs(bm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// true, if a mark at mark exists, otherwise false
|
||||
/// </returns>
|
||||
public bool IsMarked(int lineNr)
|
||||
{
|
||||
for (int i = 0; i < bookmark.Count; ++i) {
|
||||
if (bookmark[i].LineNumber == lineNr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Clears all bookmark
|
||||
/// </remarks>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (Bookmark mark in bookmark) {
|
||||
OnRemoved(new BookmarkEventArgs(mark));
|
||||
}
|
||||
bookmark.Clear();
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The lowest mark, if no marks exists it returns -1
|
||||
/// </value>
|
||||
public Bookmark GetFirstMark(Predicate<Bookmark> predicate)
|
||||
{
|
||||
if (bookmark.Count < 1) {
|
||||
return null;
|
||||
}
|
||||
Bookmark first = null;
|
||||
for (int i = 0; i < bookmark.Count; ++i) {
|
||||
if (predicate(bookmark[i]) && bookmark[i].IsEnabled && (first == null || bookmark[i].LineNumber < first.LineNumber)) {
|
||||
first = bookmark[i];
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The highest mark, if no marks exists it returns -1
|
||||
/// </value>
|
||||
public Bookmark GetLastMark(Predicate<Bookmark> predicate)
|
||||
{
|
||||
if (bookmark.Count < 1) {
|
||||
return null;
|
||||
}
|
||||
Bookmark last = null;
|
||||
for (int i = 0; i < bookmark.Count; ++i) {
|
||||
if (predicate(bookmark[i]) && bookmark[i].IsEnabled && (last == null || bookmark[i].LineNumber > last.LineNumber)) {
|
||||
last = bookmark[i];
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
bool AcceptAnyMarkPredicate(Bookmark mark)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public Bookmark GetNextMark(int curLineNr)
|
||||
{
|
||||
return GetNextMark(curLineNr, AcceptAnyMarkPredicate);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// returns first mark higher than <code>lineNr</code>
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// returns the next mark > cur, if it not exists it returns FirstMark()
|
||||
/// </returns>
|
||||
public Bookmark GetNextMark(int curLineNr, Predicate<Bookmark> predicate)
|
||||
{
|
||||
if (bookmark.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bookmark next = GetFirstMark(predicate);
|
||||
foreach (Bookmark mark in bookmark) {
|
||||
if (predicate(mark) && mark.IsEnabled && mark.LineNumber > curLineNr) {
|
||||
if (mark.LineNumber < next.LineNumber || next.LineNumber <= curLineNr) {
|
||||
next = mark;
|
||||
}
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
public Bookmark GetPrevMark(int curLineNr)
|
||||
{
|
||||
return GetPrevMark(curLineNr, AcceptAnyMarkPredicate);
|
||||
}
|
||||
/// <remarks>
|
||||
/// returns first mark lower than <code>lineNr</code>
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// returns the next mark lower than cur, if it not exists it returns LastMark()
|
||||
/// </returns>
|
||||
public Bookmark GetPrevMark(int curLineNr, Predicate<Bookmark> predicate)
|
||||
{
|
||||
if (bookmark.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bookmark prev = GetLastMark(predicate);
|
||||
|
||||
foreach (Bookmark mark in bookmark) {
|
||||
if (predicate(mark) && mark.IsEnabled && mark.LineNumber < curLineNr) {
|
||||
if (mark.LineNumber > prev.LineNumber || prev.LineNumber >= curLineNr) {
|
||||
prev = mark;
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
protected virtual void OnRemoved(BookmarkEventArgs e)
|
||||
{
|
||||
if (Removed != null) {
|
||||
Removed(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnAdded(BookmarkEventArgs e)
|
||||
{
|
||||
if (Added != null) {
|
||||
Added(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event BookmarkEventHandler Removed;
|
||||
public event BookmarkEventHandler Added;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used for storing the state of a bookmark manager
|
||||
/// </summary>
|
||||
public class BookmarkManagerMemento
|
||||
{
|
||||
List<int> bookmarks = new List<int>();
|
||||
|
||||
/// <value>
|
||||
/// Contains all bookmarks as int values
|
||||
/// </value>
|
||||
public List<int> Bookmarks {
|
||||
get {
|
||||
return bookmarks;
|
||||
}
|
||||
set {
|
||||
bookmarks = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates all bookmarks if they're in range of the document.
|
||||
/// (removing all bookmarks < 0 and bookmarks > max. line number
|
||||
/// </summary>
|
||||
public void CheckMemento(IDocument document)
|
||||
{
|
||||
for (int i = 0; i < bookmarks.Count; ++i) {
|
||||
int mark = (int)bookmarks[i];
|
||||
if (mark < 0 || mark >= document.TotalNumberOfLines) {
|
||||
bookmarks.RemoveAt(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="BookmarkManagerMemento"/>
|
||||
/// </summary>
|
||||
public BookmarkManagerMemento()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="BookmarkManagerMemento"/>
|
||||
/// </summary>
|
||||
public BookmarkManagerMemento(XmlElement element)
|
||||
{
|
||||
foreach (XmlElement el in element.ChildNodes) {
|
||||
bookmarks.Add(int.Parse(el.Attributes["line"].InnerText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="BookmarkManagerMemento"/>
|
||||
/// </summary>
|
||||
public BookmarkManagerMemento(List<int> bookmarks)
|
||||
{
|
||||
this.bookmarks = bookmarks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a xml element to a <see cref="BookmarkManagerMemento"/> object
|
||||
/// </summary>
|
||||
public object FromXmlElement(XmlElement element)
|
||||
{
|
||||
return new BookmarkManagerMemento(element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this <see cref="BookmarkManagerMemento"/> to a xml element
|
||||
/// </summary>
|
||||
public XmlElement ToXmlElement(XmlDocument doc)
|
||||
{
|
||||
XmlElement bookmarknode = doc.CreateElement("Bookmarks");
|
||||
|
||||
foreach (int line in bookmarks) {
|
||||
XmlElement markNode = doc.CreateElement("Mark");
|
||||
|
||||
XmlAttribute lineAttr = doc.CreateAttribute("line");
|
||||
lineAttr.InnerText = line.ToString();
|
||||
markNode.Attributes.Append(lineAttr);
|
||||
|
||||
bookmarknode.AppendChild(markNode);
|
||||
}
|
||||
|
||||
return bookmarknode;
|
||||
}
|
||||
}
|
||||
}
|
||||
457
ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs
Normal file
457
ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs
Normal file
@@ -0,0 +1,457 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
|
||||
using ICSharpCode.TextEditor.Undo;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the caret marker
|
||||
/// </summary>
|
||||
public enum LineViewerStyle {
|
||||
/// <summary>
|
||||
/// No line viewer will be displayed
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The row in which the caret is will be marked
|
||||
/// </summary>
|
||||
FullRow
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the indent style
|
||||
/// </summary>
|
||||
public enum IndentStyle {
|
||||
/// <summary>
|
||||
/// No indentation occurs
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The indentation from the line above will be
|
||||
/// taken to indent the curent line
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Inteligent, context sensitive indentation will occur
|
||||
/// </summary>
|
||||
Smart
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the bracket highlighting style
|
||||
/// </summary>
|
||||
public enum BracketHighlightingStyle {
|
||||
|
||||
/// <summary>
|
||||
/// Brackets won't be highlighted
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Brackets will be highlighted if the caret is on the bracket
|
||||
/// </summary>
|
||||
OnBracket,
|
||||
|
||||
/// <summary>
|
||||
/// Brackets will be highlighted if the caret is after the bracket
|
||||
/// </summary>
|
||||
AfterBracket
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the selection mode of the text area
|
||||
/// </summary>
|
||||
public enum DocumentSelectionMode {
|
||||
/// <summary>
|
||||
/// The 'normal' selection mode.
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// Selections will be added to the current selection or new
|
||||
/// ones will be created (multi-select mode)
|
||||
/// </summary>
|
||||
Additive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default <see cref="IDocument"/> implementation.
|
||||
/// </summary>
|
||||
internal sealed class DefaultDocument : IDocument
|
||||
{
|
||||
bool readOnly = false;
|
||||
|
||||
LineManager lineTrackingStrategy;
|
||||
BookmarkManager bookmarkManager;
|
||||
ITextBufferStrategy textBufferStrategy;
|
||||
IFormattingStrategy formattingStrategy;
|
||||
FoldingManager foldingManager;
|
||||
UndoStack undoStack = new UndoStack();
|
||||
ITextEditorProperties textEditorProperties = new DefaultTextEditorProperties();
|
||||
MarkerStrategy markerStrategy;
|
||||
|
||||
public LineManager LineManager {
|
||||
get { return lineTrackingStrategy; }
|
||||
set { lineTrackingStrategy = value; }
|
||||
}
|
||||
|
||||
public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged {
|
||||
add { lineTrackingStrategy.LineLengthChanged += value; }
|
||||
remove { lineTrackingStrategy.LineLengthChanged -= value; }
|
||||
}
|
||||
public event EventHandler<LineCountChangeEventArgs> LineCountChanged {
|
||||
add { lineTrackingStrategy.LineCountChanged += value; }
|
||||
remove { lineTrackingStrategy.LineCountChanged -= value; }
|
||||
}
|
||||
public event EventHandler<LineEventArgs> LineDeleted {
|
||||
add { lineTrackingStrategy.LineDeleted += value; }
|
||||
remove { lineTrackingStrategy.LineDeleted -= value; }
|
||||
}
|
||||
|
||||
public MarkerStrategy MarkerStrategy {
|
||||
get { return markerStrategy; }
|
||||
set { markerStrategy = value; }
|
||||
}
|
||||
|
||||
public ITextEditorProperties TextEditorProperties {
|
||||
get {
|
||||
return textEditorProperties;
|
||||
}
|
||||
set {
|
||||
textEditorProperties = value;
|
||||
}
|
||||
}
|
||||
|
||||
public UndoStack UndoStack {
|
||||
get {
|
||||
return undoStack;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<LineSegment> LineSegmentCollection {
|
||||
get {
|
||||
return lineTrackingStrategy.LineSegmentCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadOnly {
|
||||
get {
|
||||
return readOnly;
|
||||
}
|
||||
set {
|
||||
readOnly = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ITextBufferStrategy TextBufferStrategy {
|
||||
get {
|
||||
return textBufferStrategy;
|
||||
}
|
||||
set {
|
||||
textBufferStrategy = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IFormattingStrategy FormattingStrategy {
|
||||
get {
|
||||
return formattingStrategy;
|
||||
}
|
||||
set {
|
||||
formattingStrategy = value;
|
||||
}
|
||||
}
|
||||
|
||||
public FoldingManager FoldingManager {
|
||||
get {
|
||||
return foldingManager;
|
||||
}
|
||||
set {
|
||||
foldingManager = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IHighlightingStrategy HighlightingStrategy {
|
||||
get {
|
||||
return lineTrackingStrategy.HighlightingStrategy;
|
||||
}
|
||||
set {
|
||||
lineTrackingStrategy.HighlightingStrategy = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int TextLength {
|
||||
get {
|
||||
return textBufferStrategy.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public BookmarkManager BookmarkManager {
|
||||
get {
|
||||
return bookmarkManager;
|
||||
}
|
||||
set {
|
||||
bookmarkManager = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string TextContent {
|
||||
get {
|
||||
return GetText(0, textBufferStrategy.Length);
|
||||
}
|
||||
set {
|
||||
Debug.Assert(textBufferStrategy != null);
|
||||
Debug.Assert(lineTrackingStrategy != null);
|
||||
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, 0, 0, value));
|
||||
textBufferStrategy.SetContent(value);
|
||||
lineTrackingStrategy.SetContent(value);
|
||||
undoStack.ClearAll();
|
||||
|
||||
OnDocumentChanged(new DocumentEventArgs(this, 0, 0, value));
|
||||
OnTextContentChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int offset, string text)
|
||||
{
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, -1, text));
|
||||
|
||||
textBufferStrategy.Insert(offset, text);
|
||||
lineTrackingStrategy.Insert(offset, text);
|
||||
|
||||
undoStack.Push(new UndoableInsert(this, offset, text));
|
||||
|
||||
OnDocumentChanged(new DocumentEventArgs(this, offset, -1, text));
|
||||
}
|
||||
|
||||
public void Remove(int offset, int length)
|
||||
{
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length));
|
||||
undoStack.Push(new UndoableDelete(this, offset, GetText(offset, length)));
|
||||
|
||||
textBufferStrategy.Remove(offset, length);
|
||||
lineTrackingStrategy.Remove(offset, length);
|
||||
|
||||
OnDocumentChanged(new DocumentEventArgs(this, offset, length));
|
||||
}
|
||||
|
||||
public void Replace(int offset, int length, string text)
|
||||
{
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length, text));
|
||||
undoStack.Push(new UndoableReplace(this, offset, GetText(offset, length), text));
|
||||
|
||||
textBufferStrategy.Replace(offset, length, text);
|
||||
lineTrackingStrategy.Replace(offset, length, text);
|
||||
|
||||
OnDocumentChanged(new DocumentEventArgs(this, offset, length, text));
|
||||
}
|
||||
|
||||
public char GetCharAt(int offset)
|
||||
{
|
||||
return textBufferStrategy.GetCharAt(offset);
|
||||
}
|
||||
|
||||
public string GetText(int offset, int length)
|
||||
{
|
||||
#if DEBUG
|
||||
if (length < 0) throw new ArgumentOutOfRangeException("length", length, "length < 0");
|
||||
#endif
|
||||
return textBufferStrategy.GetText(offset, length);
|
||||
}
|
||||
public string GetText(ISegment segment)
|
||||
{
|
||||
return GetText(segment.Offset, segment.Length);
|
||||
}
|
||||
|
||||
public int TotalNumberOfLines {
|
||||
get {
|
||||
return lineTrackingStrategy.TotalNumberOfLines;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetLineNumberForOffset(int offset)
|
||||
{
|
||||
return lineTrackingStrategy.GetLineNumberForOffset(offset);
|
||||
}
|
||||
|
||||
public LineSegment GetLineSegmentForOffset(int offset)
|
||||
{
|
||||
return lineTrackingStrategy.GetLineSegmentForOffset(offset);
|
||||
}
|
||||
|
||||
public LineSegment GetLineSegment(int line)
|
||||
{
|
||||
return lineTrackingStrategy.GetLineSegment(line);
|
||||
}
|
||||
|
||||
public int GetFirstLogicalLine(int lineNumber)
|
||||
{
|
||||
return lineTrackingStrategy.GetFirstLogicalLine(lineNumber);
|
||||
}
|
||||
|
||||
public int GetLastLogicalLine(int lineNumber)
|
||||
{
|
||||
return lineTrackingStrategy.GetLastLogicalLine(lineNumber);
|
||||
}
|
||||
|
||||
public int GetVisibleLine(int lineNumber)
|
||||
{
|
||||
return lineTrackingStrategy.GetVisibleLine(lineNumber);
|
||||
}
|
||||
|
||||
// public int GetVisibleColumn(int logicalLine, int logicalColumn)
|
||||
// {
|
||||
// return lineTrackingStrategy.GetVisibleColumn(logicalLine, logicalColumn);
|
||||
// }
|
||||
//
|
||||
public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
|
||||
{
|
||||
return lineTrackingStrategy.GetNextVisibleLineAbove(lineNumber, lineCount);
|
||||
}
|
||||
|
||||
public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
|
||||
{
|
||||
return lineTrackingStrategy.GetNextVisibleLineBelow(lineNumber, lineCount);
|
||||
}
|
||||
|
||||
public TextLocation OffsetToPosition(int offset)
|
||||
{
|
||||
int lineNr = GetLineNumberForOffset(offset);
|
||||
LineSegment line = GetLineSegment(lineNr);
|
||||
return new TextLocation(offset - line.Offset, lineNr);
|
||||
}
|
||||
|
||||
public int PositionToOffset(TextLocation p)
|
||||
{
|
||||
if (p.Y >= this.TotalNumberOfLines) {
|
||||
return 0;
|
||||
}
|
||||
LineSegment line = GetLineSegment(p.Y);
|
||||
return Math.Min(this.TextLength, line.Offset + Math.Min(line.Length, p.X));
|
||||
}
|
||||
|
||||
public void UpdateSegmentListOnDocumentChange<T>(List<T> list, DocumentEventArgs e) where T : ISegment
|
||||
{
|
||||
int removedCharacters = e.Length > 0 ? e.Length : 0;
|
||||
int insertedCharacters = e.Text != null ? e.Text.Length : 0;
|
||||
for (int i = 0; i < list.Count; ++i) {
|
||||
ISegment s = list[i];
|
||||
int segmentStart = s.Offset;
|
||||
int segmentEnd = s.Offset + s.Length;
|
||||
|
||||
if (e.Offset <= segmentStart) {
|
||||
segmentStart -= removedCharacters;
|
||||
if (segmentStart < e.Offset)
|
||||
segmentStart = e.Offset;
|
||||
}
|
||||
if (e.Offset < segmentEnd) {
|
||||
segmentEnd -= removedCharacters;
|
||||
if (segmentEnd < e.Offset)
|
||||
segmentEnd = e.Offset;
|
||||
}
|
||||
|
||||
Debug.Assert(segmentStart <= segmentEnd);
|
||||
|
||||
if (segmentStart == segmentEnd) {
|
||||
list.RemoveAt(i);
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.Offset <= segmentStart)
|
||||
segmentStart += insertedCharacters;
|
||||
if (e.Offset < segmentEnd)
|
||||
segmentEnd += insertedCharacters;
|
||||
|
||||
Debug.Assert(segmentStart < segmentEnd);
|
||||
|
||||
s.Offset = segmentStart;
|
||||
s.Length = segmentEnd - segmentStart;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDocumentAboutToBeChanged(DocumentEventArgs e)
|
||||
{
|
||||
if (DocumentAboutToBeChanged != null) {
|
||||
DocumentAboutToBeChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDocumentChanged(DocumentEventArgs e)
|
||||
{
|
||||
if (DocumentChanged != null) {
|
||||
DocumentChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event DocumentEventHandler DocumentAboutToBeChanged;
|
||||
public event DocumentEventHandler DocumentChanged;
|
||||
|
||||
// UPDATE STUFF
|
||||
List<TextAreaUpdate> updateQueue = new List<TextAreaUpdate>();
|
||||
|
||||
public List<TextAreaUpdate> UpdateQueue {
|
||||
get {
|
||||
return updateQueue;
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestUpdate(TextAreaUpdate update)
|
||||
{
|
||||
if (updateQueue.Count == 1 && updateQueue[0].TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) {
|
||||
// if we're going to update the whole text area, we don't need to store detail updates
|
||||
return;
|
||||
}
|
||||
if (update.TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) {
|
||||
// if we're going to update the whole text area, we don't need to store detail updates
|
||||
updateQueue.Clear();
|
||||
}
|
||||
updateQueue.Add(update);
|
||||
}
|
||||
|
||||
public void CommitUpdate()
|
||||
{
|
||||
if (UpdateCommited != null) {
|
||||
UpdateCommited(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
void OnTextContentChanged(EventArgs e)
|
||||
{
|
||||
if (TextContentChanged != null) {
|
||||
TextContentChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler UpdateCommited;
|
||||
public event EventHandler TextContentChanged;
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void ValidatePosition(IDocument document, TextLocation position)
|
||||
{
|
||||
document.GetLineSegment(position.Line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="none" email=""/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public enum BracketMatchingStyle {
|
||||
Before,
|
||||
After
|
||||
}
|
||||
|
||||
public class DefaultTextEditorProperties : ITextEditorProperties
|
||||
{
|
||||
int tabIndent = 4;
|
||||
int indentationSize = 4;
|
||||
IndentStyle indentStyle = IndentStyle.Smart;
|
||||
DocumentSelectionMode documentSelectionMode = DocumentSelectionMode.Normal;
|
||||
Encoding encoding = System.Text.Encoding.UTF8;
|
||||
BracketMatchingStyle bracketMatchingStyle = BracketMatchingStyle.After;
|
||||
FontContainer fontContainer;
|
||||
static Font DefaultFont;
|
||||
|
||||
public DefaultTextEditorProperties()
|
||||
{
|
||||
if (DefaultFont == null) {
|
||||
DefaultFont = new Font("Courier New", 10);
|
||||
}
|
||||
this.fontContainer = new FontContainer(DefaultFont);
|
||||
}
|
||||
|
||||
bool allowCaretBeyondEOL = false;
|
||||
|
||||
bool caretLine = false;
|
||||
|
||||
bool showMatchingBracket = true;
|
||||
bool showLineNumbers = true;
|
||||
|
||||
bool showSpaces = false;
|
||||
bool showTabs = false;
|
||||
bool showEOLMarker = false;
|
||||
|
||||
bool showInvalidLines = false;
|
||||
|
||||
bool isIconBarVisible = false;
|
||||
bool enableFolding = true;
|
||||
bool showHorizontalRuler = false;
|
||||
bool showVerticalRuler = true;
|
||||
bool convertTabsToSpaces = false;
|
||||
System.Drawing.Text.TextRenderingHint textRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault;
|
||||
bool mouseWheelScrollDown = true;
|
||||
bool mouseWheelTextZoom = true;
|
||||
|
||||
bool hideMouseCursor = false;
|
||||
bool cutCopyWholeLine = true;
|
||||
|
||||
int verticalRulerRow = 80;
|
||||
LineViewerStyle lineViewerStyle = LineViewerStyle.None;
|
||||
string lineTerminator = "\r\n";
|
||||
bool autoInsertCurlyBracket = true;
|
||||
bool supportReadOnlySegments = false;
|
||||
|
||||
public int TabIndent {
|
||||
get {
|
||||
return tabIndent;
|
||||
}
|
||||
set {
|
||||
tabIndent = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int IndentationSize {
|
||||
get { return indentationSize; }
|
||||
set { indentationSize = value; }
|
||||
}
|
||||
|
||||
public IndentStyle IndentStyle {
|
||||
get {
|
||||
return indentStyle;
|
||||
}
|
||||
set {
|
||||
indentStyle = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CaretLine {
|
||||
get
|
||||
{
|
||||
return caretLine;
|
||||
}
|
||||
set
|
||||
{
|
||||
caretLine = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DocumentSelectionMode DocumentSelectionMode {
|
||||
get {
|
||||
return documentSelectionMode;
|
||||
}
|
||||
set {
|
||||
documentSelectionMode = value;
|
||||
}
|
||||
}
|
||||
public bool AllowCaretBeyondEOL {
|
||||
get {
|
||||
return allowCaretBeyondEOL;
|
||||
}
|
||||
set {
|
||||
allowCaretBeyondEOL = value;
|
||||
}
|
||||
}
|
||||
public bool ShowMatchingBracket {
|
||||
get {
|
||||
return showMatchingBracket;
|
||||
}
|
||||
set {
|
||||
showMatchingBracket = value;
|
||||
}
|
||||
}
|
||||
public bool ShowLineNumbers {
|
||||
get {
|
||||
return showLineNumbers;
|
||||
}
|
||||
set {
|
||||
showLineNumbers = value;
|
||||
}
|
||||
}
|
||||
public bool ShowSpaces {
|
||||
get {
|
||||
return showSpaces;
|
||||
}
|
||||
set {
|
||||
showSpaces = value;
|
||||
}
|
||||
}
|
||||
public bool ShowTabs {
|
||||
get {
|
||||
return showTabs;
|
||||
}
|
||||
set {
|
||||
showTabs = value;
|
||||
}
|
||||
}
|
||||
public bool ShowEOLMarker {
|
||||
get {
|
||||
return showEOLMarker;
|
||||
}
|
||||
set {
|
||||
showEOLMarker = value;
|
||||
}
|
||||
}
|
||||
public bool ShowInvalidLines {
|
||||
get {
|
||||
return showInvalidLines;
|
||||
}
|
||||
set {
|
||||
showInvalidLines = value;
|
||||
}
|
||||
}
|
||||
public bool IsIconBarVisible {
|
||||
get {
|
||||
return isIconBarVisible;
|
||||
}
|
||||
set {
|
||||
isIconBarVisible = value;
|
||||
}
|
||||
}
|
||||
public bool EnableFolding {
|
||||
get {
|
||||
return enableFolding;
|
||||
}
|
||||
set {
|
||||
enableFolding = value;
|
||||
}
|
||||
}
|
||||
public bool ShowHorizontalRuler {
|
||||
get {
|
||||
return showHorizontalRuler;
|
||||
}
|
||||
set {
|
||||
showHorizontalRuler = value;
|
||||
}
|
||||
}
|
||||
public bool ShowVerticalRuler {
|
||||
get {
|
||||
return showVerticalRuler;
|
||||
}
|
||||
set {
|
||||
showVerticalRuler = value;
|
||||
}
|
||||
}
|
||||
public bool ConvertTabsToSpaces {
|
||||
get {
|
||||
return convertTabsToSpaces;
|
||||
}
|
||||
set {
|
||||
convertTabsToSpaces = value;
|
||||
}
|
||||
}
|
||||
public System.Drawing.Text.TextRenderingHint TextRenderingHint {
|
||||
get { return textRenderingHint; }
|
||||
set { textRenderingHint = value; }
|
||||
}
|
||||
|
||||
public bool MouseWheelScrollDown {
|
||||
get {
|
||||
return mouseWheelScrollDown;
|
||||
}
|
||||
set {
|
||||
mouseWheelScrollDown = value;
|
||||
}
|
||||
}
|
||||
public bool MouseWheelTextZoom {
|
||||
get {
|
||||
return mouseWheelTextZoom;
|
||||
}
|
||||
set {
|
||||
mouseWheelTextZoom = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HideMouseCursor {
|
||||
get {
|
||||
return hideMouseCursor;
|
||||
}
|
||||
set {
|
||||
hideMouseCursor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CutCopyWholeLine {
|
||||
get {
|
||||
return cutCopyWholeLine;
|
||||
}
|
||||
set {
|
||||
cutCopyWholeLine = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Encoding Encoding {
|
||||
get {
|
||||
return encoding;
|
||||
}
|
||||
set {
|
||||
encoding = value;
|
||||
}
|
||||
}
|
||||
public int VerticalRulerRow {
|
||||
get {
|
||||
return verticalRulerRow;
|
||||
}
|
||||
set {
|
||||
verticalRulerRow = value;
|
||||
}
|
||||
}
|
||||
public LineViewerStyle LineViewerStyle {
|
||||
get {
|
||||
return lineViewerStyle;
|
||||
}
|
||||
set {
|
||||
lineViewerStyle = value;
|
||||
}
|
||||
}
|
||||
public string LineTerminator {
|
||||
get {
|
||||
return lineTerminator;
|
||||
}
|
||||
set {
|
||||
lineTerminator = value;
|
||||
}
|
||||
}
|
||||
public bool AutoInsertCurlyBracket {
|
||||
get {
|
||||
return autoInsertCurlyBracket;
|
||||
}
|
||||
set {
|
||||
autoInsertCurlyBracket = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Font Font {
|
||||
get {
|
||||
return fontContainer.DefaultFont;
|
||||
}
|
||||
set {
|
||||
fontContainer.DefaultFont = value;
|
||||
}
|
||||
}
|
||||
|
||||
public FontContainer FontContainer {
|
||||
get {
|
||||
return fontContainer;
|
||||
}
|
||||
}
|
||||
|
||||
public BracketMatchingStyle BracketMatchingStyle {
|
||||
get {
|
||||
return bracketMatchingStyle;
|
||||
}
|
||||
set {
|
||||
bracketMatchingStyle = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportReadOnlySegments {
|
||||
get {
|
||||
return supportReadOnlySegments;
|
||||
}
|
||||
set {
|
||||
supportReadOnlySegments = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs
Normal file
103
ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This delegate is used for document events.
|
||||
/// </summary>
|
||||
public delegate void DocumentEventHandler(object sender, DocumentEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// This class contains more information on a document event
|
||||
/// </summary>
|
||||
public class DocumentEventArgs : EventArgs
|
||||
{
|
||||
IDocument document;
|
||||
int offset;
|
||||
int length;
|
||||
string text;
|
||||
|
||||
/// <returns>
|
||||
/// always a valid Document which is related to the Event.
|
||||
/// </returns>
|
||||
public IDocument Document {
|
||||
get {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// -1 if no offset was specified for this event
|
||||
/// </returns>
|
||||
public int Offset {
|
||||
get {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// null if no text was specified for this event
|
||||
/// </returns>
|
||||
public string Text {
|
||||
get {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// -1 if no length was specified for this event
|
||||
/// </returns>
|
||||
public int Length {
|
||||
get {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance off <see cref="DocumentEventArgs"/>
|
||||
/// </summary>
|
||||
public DocumentEventArgs(IDocument document) : this(document, -1, -1, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance off <see cref="DocumentEventArgs"/>
|
||||
/// </summary>
|
||||
public DocumentEventArgs(IDocument document, int offset) : this(document, offset, -1, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance off <see cref="DocumentEventArgs"/>
|
||||
/// </summary>
|
||||
public DocumentEventArgs(IDocument document, int offset, int length) : this(document, offset, length, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance off <see cref="DocumentEventArgs"/>
|
||||
/// </summary>
|
||||
public DocumentEventArgs(IDocument document, int offset, int length, string text)
|
||||
{
|
||||
this.document = document;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.text = text;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[DocumentEventArgs: Document = {0}, Offset = {1}, Text = {2}, Length = {3}]",
|
||||
Document,
|
||||
Offset,
|
||||
Text,
|
||||
Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface represents a container which holds a text sequence and
|
||||
/// all necessary information about it. It is used as the base for a text editor.
|
||||
/// </summary>
|
||||
public class DocumentFactory
|
||||
{
|
||||
/// <remarks>
|
||||
/// Creates a new <see cref="IDocument"/> object. Only create
|
||||
/// <see cref="IDocument"/> with this method.
|
||||
/// </remarks>
|
||||
public IDocument CreateDocument()
|
||||
{
|
||||
DefaultDocument doc = new DefaultDocument();
|
||||
doc.TextBufferStrategy = new GapTextBufferStrategy();
|
||||
doc.FormattingStrategy = new DefaultFormattingStrategy();
|
||||
doc.LineManager = new LineManager(doc, null);
|
||||
doc.FoldingManager = new FoldingManager(doc, doc.LineManager);
|
||||
doc.FoldingManager.FoldingStrategy = null; //new ParserFoldingStrategy();
|
||||
doc.MarkerStrategy = new MarkerStrategy(doc);
|
||||
doc.BookmarkManager = new BookmarkManager(doc, doc.LineManager);
|
||||
return doc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new document and loads the given file
|
||||
/// </summary>
|
||||
public IDocument CreateFromTextBuffer(ITextBufferStrategy textBuffer)
|
||||
{
|
||||
DefaultDocument doc = (DefaultDocument)CreateDocument();
|
||||
doc.TextContent = textBuffer.GetText(0, textBuffer.Length);
|
||||
doc.TextBufferStrategy = textBuffer;
|
||||
return doc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new document and loads the given file
|
||||
/// </summary>
|
||||
public IDocument CreateFromFile(string fileName)
|
||||
{
|
||||
IDocument document = CreateDocument();
|
||||
document.TextContent = Util.FileReader.ReadFileContent(fileName, Encoding.Default);
|
||||
return document;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// This file is part of CodingEditor.
|
||||
// Note: This project is derived from Peter Project
|
||||
// (hosted on sourceforge and codeplex)
|
||||
//
|
||||
// Copyright (c) 2008-2009, CE Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
using System.Collections.Generic;
|
||||
using ICSharpCode.TextEditor.Document;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
|
||||
{
|
||||
public class CSharpFoldingStrategy : IFoldingStrategy
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Generates the foldings for our document.
|
||||
/// </summary>
|
||||
/// <param name="document">The current document.</param>
|
||||
/// <param name="fileName">The filename of the document.</param>
|
||||
/// <param name="parseInformation">Extra parse information, not used in this sample.</param>
|
||||
/// <returns>A list of FoldMarkers.</returns>
|
||||
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
|
||||
{
|
||||
var list = new List<FoldMarker>();
|
||||
var startLines = new Stack<int>();
|
||||
|
||||
// Create foldmarkers for the whole document, enumerate through every line.
|
||||
for (int i = 0; i < document.TotalNumberOfLines; i++)
|
||||
{
|
||||
var seg = document.GetLineSegment(i);
|
||||
int offs, end = document.TextLength;
|
||||
char c;
|
||||
for (offs = seg.Offset; offs < end && ((c = document.GetCharAt(offs)) == ' ' || c == '\t'); offs++)
|
||||
{
|
||||
}
|
||||
if (offs == end)
|
||||
break;
|
||||
int spaceCount = offs - seg.Offset;
|
||||
|
||||
// now offs points to the first non-whitespace char on the line
|
||||
if (document.GetCharAt(offs) == '#')
|
||||
{
|
||||
string text = document.GetText(offs, seg.Length - spaceCount);
|
||||
if (text.StartsWith("#region"))
|
||||
startLines.Push(i);
|
||||
if (text.StartsWith("#endregion") && startLines.Count > 0)
|
||||
{
|
||||
// Add a new FoldMarker to the list.
|
||||
int start = startLines.Pop();
|
||||
list.Add(new FoldMarker(document, start,
|
||||
document.GetLineSegment(start).Length,
|
||||
i, spaceCount + "#endregion".Length, FoldType.Region, "{...}"));
|
||||
}
|
||||
}
|
||||
|
||||
// { }
|
||||
if (document.GetCharAt(offs) == '{')
|
||||
{
|
||||
int offsetOfClosingBracket = document.FormattingStrategy.SearchBracketForward(document, offs + 1, '{', '}');
|
||||
if (offsetOfClosingBracket > 0)
|
||||
{
|
||||
int length = offsetOfClosingBracket - offs + 1;
|
||||
list.Add(new FoldMarker(document, offs, length, "{...}", false));
|
||||
}
|
||||
}
|
||||
|
||||
if (document.GetCharAt(offs) == '/')
|
||||
{
|
||||
string text = document.GetText(offs, seg.Length - spaceCount);
|
||||
if (text.StartsWith("/// <summary>"))
|
||||
startLines.Push(i);
|
||||
if ((text.StartsWith("/// <param") || text.StartsWith("/// <returns>") || text.StartsWith("/// </summary>"))
|
||||
&& startLines.Count > 0)
|
||||
{
|
||||
// Add a new FoldMarker to the list.
|
||||
int start = startLines.Pop();
|
||||
list.Add(new FoldMarker(document, start,
|
||||
document.GetLineSegment(start).Length,
|
||||
i, spaceCount + "/// </summary>".Length, FoldType.TypeBody, "/// <summary>..."));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public enum FoldType {
|
||||
Unspecified,
|
||||
MemberBody,
|
||||
Region,
|
||||
TypeBody
|
||||
}
|
||||
|
||||
public class FoldMarker : AbstractSegment, IComparable
|
||||
{
|
||||
bool isFolded = false;
|
||||
string foldText = "...";
|
||||
FoldType foldType = FoldType.Unspecified;
|
||||
IDocument document = null;
|
||||
int startLine = -1, startColumn, endLine = -1, endColumn;
|
||||
|
||||
static void GetPointForOffset(IDocument document, int offset, out int line, out int column)
|
||||
{
|
||||
if (offset > document.TextLength) {
|
||||
line = document.TotalNumberOfLines + 1;
|
||||
column = 1;
|
||||
} else if (offset < 0) {
|
||||
line = -1;
|
||||
column = -1;
|
||||
} else {
|
||||
line = document.GetLineNumberForOffset(offset);
|
||||
column = offset - document.GetLineSegment(line).Offset;
|
||||
}
|
||||
}
|
||||
|
||||
public FoldType FoldType {
|
||||
get { return foldType; }
|
||||
set { foldType = value; }
|
||||
}
|
||||
|
||||
public int StartLine {
|
||||
get {
|
||||
if (startLine < 0) {
|
||||
GetPointForOffset(document, offset, out startLine, out startColumn);
|
||||
}
|
||||
return startLine;
|
||||
}
|
||||
}
|
||||
|
||||
public int StartColumn {
|
||||
get {
|
||||
if (startLine < 0) {
|
||||
GetPointForOffset(document, offset, out startLine, out startColumn);
|
||||
}
|
||||
return startColumn;
|
||||
}
|
||||
}
|
||||
|
||||
public int EndLine {
|
||||
get {
|
||||
if (endLine < 0) {
|
||||
GetPointForOffset(document, offset + length, out endLine, out endColumn);
|
||||
}
|
||||
return endLine;
|
||||
}
|
||||
}
|
||||
|
||||
public int EndColumn {
|
||||
get {
|
||||
if (endLine < 0) {
|
||||
GetPointForOffset(document, offset + length, out endLine, out endColumn);
|
||||
}
|
||||
return endColumn;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Offset {
|
||||
get { return base.Offset; }
|
||||
set {
|
||||
base.Offset = value;
|
||||
startLine = -1; endLine = -1;
|
||||
}
|
||||
}
|
||||
public override int Length {
|
||||
get { return base.Length; }
|
||||
set {
|
||||
base.Length = value;
|
||||
endLine = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFolded {
|
||||
get {
|
||||
return isFolded;
|
||||
}
|
||||
set {
|
||||
isFolded = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string FoldText {
|
||||
get {
|
||||
return foldText;
|
||||
}
|
||||
}
|
||||
|
||||
public string InnerText {
|
||||
get {
|
||||
return document.GetText(offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
public FoldMarker(IDocument document, int offset, int length, string foldText, bool isFolded)
|
||||
{
|
||||
this.document = document;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.foldText = foldText;
|
||||
this.isFolded = isFolded;
|
||||
}
|
||||
|
||||
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn) : this(document, startLine, startColumn, endLine, endColumn, FoldType.Unspecified)
|
||||
{
|
||||
}
|
||||
|
||||
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType) : this(document, startLine, startColumn, endLine, endColumn, foldType, "...")
|
||||
{
|
||||
}
|
||||
|
||||
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText) : this(document, startLine, startColumn, endLine, endColumn, foldType, foldText, false)
|
||||
{
|
||||
}
|
||||
|
||||
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText, bool isFolded)
|
||||
{
|
||||
this.document = document;
|
||||
|
||||
startLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(startLine, 0));
|
||||
ISegment startLineSegment = document.GetLineSegment(startLine);
|
||||
|
||||
endLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(endLine, 0));
|
||||
ISegment endLineSegment = document.GetLineSegment(endLine);
|
||||
|
||||
// Prevent the region from completely disappearing
|
||||
if (string.IsNullOrEmpty(foldText)) {
|
||||
foldText = "...";
|
||||
}
|
||||
|
||||
this.FoldType = foldType;
|
||||
this.foldText = foldText;
|
||||
this.offset = startLineSegment.Offset + Math.Min(startColumn, startLineSegment.Length);
|
||||
this.length = (endLineSegment.Offset + Math.Min(endColumn, endLineSegment.Length)) - this.offset;
|
||||
this.isFolded = isFolded;
|
||||
}
|
||||
|
||||
public int CompareTo(object o)
|
||||
{
|
||||
if (!(o is FoldMarker)) {
|
||||
throw new ArgumentException();
|
||||
}
|
||||
FoldMarker f = (FoldMarker)o;
|
||||
if (offset != f.offset) {
|
||||
return offset.CompareTo(f.offset);
|
||||
}
|
||||
|
||||
return length.CompareTo(f.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class FoldingManager
|
||||
{
|
||||
List<FoldMarker> foldMarker = new List<FoldMarker>();
|
||||
List<FoldMarker> foldMarkerByEnd = new List<FoldMarker>();
|
||||
IDocument document;
|
||||
|
||||
public IList<FoldMarker> FoldMarker => foldMarker.AsReadOnly();
|
||||
|
||||
public IFoldingStrategy? FoldingStrategy { get; set; }
|
||||
|
||||
internal FoldingManager(IDocument document, LineManager lineTracker)
|
||||
{
|
||||
this.document = document;
|
||||
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
|
||||
|
||||
// lineTracker.LineCountChanged += new LineManagerEventHandler(LineManagerLineCountChanged);
|
||||
// lineTracker.LineLengthChanged += new LineLengthEventHandler(LineManagerLineLengthChanged);
|
||||
// foldMarker.Add(new FoldMarker(0, 5, 3, 5));
|
||||
//
|
||||
// foldMarker.Add(new FoldMarker(5, 5, 10, 3));
|
||||
// foldMarker.Add(new FoldMarker(6, 0, 8, 2));
|
||||
//
|
||||
// FoldMarker fm1 = new FoldMarker(10, 4, 10, 7);
|
||||
// FoldMarker fm2 = new FoldMarker(10, 10, 10, 14);
|
||||
//
|
||||
// fm1.IsFolded = true;
|
||||
// fm2.IsFolded = true;
|
||||
//
|
||||
// foldMarker.Add(fm1);
|
||||
// foldMarker.Add(fm2);
|
||||
// foldMarker.Sort();
|
||||
}
|
||||
|
||||
void DocumentChanged(object sender, DocumentEventArgs e)
|
||||
{
|
||||
int oldCount = foldMarker.Count;
|
||||
document.UpdateSegmentListOnDocumentChange(foldMarker, e);
|
||||
if (oldCount != foldMarker.Count)
|
||||
{
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
||||
}
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldingsFromPosition(int line, int column)
|
||||
{
|
||||
List<FoldMarker> foldings = new List<FoldMarker>();
|
||||
if (foldMarker != null)
|
||||
{
|
||||
for (int i = 0; i < foldMarker.Count; ++i)
|
||||
{
|
||||
FoldMarker fm = foldMarker[i];
|
||||
if ((fm.StartLine == line && column > fm.StartColumn && !(fm.EndLine == line && column >= fm.EndColumn)) ||
|
||||
(fm.EndLine == line && column < fm.EndColumn && !(fm.StartLine == line && column <= fm.StartColumn)) ||
|
||||
(line > fm.StartLine && line < fm.EndLine))
|
||||
{
|
||||
foldings.Add(fm);
|
||||
}
|
||||
}
|
||||
}
|
||||
return foldings;
|
||||
}
|
||||
|
||||
class StartComparer : IComparer<FoldMarker>
|
||||
{
|
||||
public readonly static StartComparer Instance = new StartComparer();
|
||||
|
||||
public int Compare(FoldMarker x, FoldMarker y)
|
||||
{
|
||||
if (x.StartLine < y.StartLine)
|
||||
return -1;
|
||||
else if (x.StartLine == y.StartLine)
|
||||
return x.StartColumn.CompareTo(y.StartColumn);
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
class EndComparer : IComparer<FoldMarker>
|
||||
{
|
||||
public readonly static EndComparer Instance = new EndComparer();
|
||||
|
||||
public int Compare(FoldMarker x, FoldMarker y)
|
||||
{
|
||||
if (x.EndLine < y.EndLine)
|
||||
return -1;
|
||||
else if (x.EndLine == y.EndLine)
|
||||
return x.EndColumn.CompareTo(y.EndColumn);
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
List<FoldMarker> GetFoldingsByStartAfterColumn(int lineNumber, int column, bool forceFolded)
|
||||
{
|
||||
List<FoldMarker> foldings = new List<FoldMarker>();
|
||||
|
||||
if (foldMarker != null)
|
||||
{
|
||||
int index = foldMarker.BinarySearch(
|
||||
new FoldMarker(document, lineNumber, column, lineNumber, column),
|
||||
StartComparer.Instance);
|
||||
if (index < 0) index = ~index;
|
||||
|
||||
for (; index < foldMarker.Count; index++)
|
||||
{
|
||||
FoldMarker fm = foldMarker[index];
|
||||
if (fm.StartLine > lineNumber)
|
||||
break;
|
||||
if (fm.StartColumn <= column)
|
||||
continue;
|
||||
if (!forceFolded || fm.IsFolded)
|
||||
foldings.Add(fm);
|
||||
}
|
||||
}
|
||||
return foldings;
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldingsWithStart(int lineNumber)
|
||||
{
|
||||
return GetFoldingsByStartAfterColumn(lineNumber, -1, false);
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldedFoldingsWithStart(int lineNumber)
|
||||
{
|
||||
return GetFoldingsByStartAfterColumn(lineNumber, -1, true);
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldedFoldingsWithStartAfterColumn(int lineNumber, int column)
|
||||
{
|
||||
return GetFoldingsByStartAfterColumn(lineNumber, column, true);
|
||||
}
|
||||
|
||||
List<FoldMarker> GetFoldingsByEndAfterColumn(int lineNumber, int column, bool forceFolded)
|
||||
{
|
||||
List<FoldMarker> foldings = new List<FoldMarker>();
|
||||
|
||||
if (foldMarker != null)
|
||||
{
|
||||
int index = foldMarkerByEnd.BinarySearch(
|
||||
new FoldMarker(document, lineNumber, column, lineNumber, column),
|
||||
EndComparer.Instance);
|
||||
if (index < 0) index = ~index;
|
||||
|
||||
for (; index < foldMarkerByEnd.Count; index++)
|
||||
{
|
||||
FoldMarker fm = foldMarkerByEnd[index];
|
||||
if (fm.EndLine > lineNumber)
|
||||
break;
|
||||
if (fm.EndColumn <= column)
|
||||
continue;
|
||||
if (!forceFolded || fm.IsFolded)
|
||||
foldings.Add(fm);
|
||||
}
|
||||
}
|
||||
return foldings;
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldingsWithEnd(int lineNumber)
|
||||
{
|
||||
return GetFoldingsByEndAfterColumn(lineNumber, -1, false);
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldedFoldingsWithEnd(int lineNumber)
|
||||
{
|
||||
return GetFoldingsByEndAfterColumn(lineNumber, -1, true);
|
||||
}
|
||||
|
||||
public bool IsFoldStart(int lineNumber)
|
||||
{
|
||||
return GetFoldingsWithStart(lineNumber).Count > 0;
|
||||
}
|
||||
|
||||
public bool IsFoldEnd(int lineNumber)
|
||||
{
|
||||
return GetFoldingsWithEnd(lineNumber).Count > 0;
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetFoldingsContainsLineNumber(int lineNumber)
|
||||
{
|
||||
List<FoldMarker> foldings = new List<FoldMarker>();
|
||||
if (foldMarker != null)
|
||||
{
|
||||
foreach (FoldMarker fm in foldMarker)
|
||||
{
|
||||
if (fm.StartLine < lineNumber && lineNumber < fm.EndLine)
|
||||
{
|
||||
foldings.Add(fm);
|
||||
}
|
||||
}
|
||||
}
|
||||
return foldings;
|
||||
}
|
||||
|
||||
public bool IsBetweenFolding(int lineNumber)
|
||||
{
|
||||
return GetFoldingsContainsLineNumber(lineNumber).Count > 0;
|
||||
}
|
||||
|
||||
public bool IsLineVisible(int lineNumber)
|
||||
{
|
||||
foreach (FoldMarker fm in GetFoldingsContainsLineNumber(lineNumber))
|
||||
{
|
||||
if (fm.IsFolded)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<FoldMarker> GetTopLevelFoldedFoldings()
|
||||
{
|
||||
List<FoldMarker> foldings = new List<FoldMarker>();
|
||||
if (foldMarker != null)
|
||||
{
|
||||
Point end = new Point(0, 0);
|
||||
foreach (FoldMarker fm in foldMarker)
|
||||
{
|
||||
if (fm.IsFolded && (fm.StartLine > end.Y || fm.StartLine == end.Y && fm.StartColumn >= end.X))
|
||||
{
|
||||
foldings.Add(fm);
|
||||
end = new Point(fm.EndColumn, fm.EndLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
return foldings;
|
||||
}
|
||||
|
||||
public void UpdateFoldings(string fileName, object parseInfo)
|
||||
{
|
||||
UpdateFoldings(FoldingStrategy?.GenerateFoldMarkers(document, fileName, parseInfo));
|
||||
}
|
||||
|
||||
public void UpdateFoldings(List<FoldMarker> newFoldings)
|
||||
{
|
||||
int oldFoldingsCount = foldMarker.Count;
|
||||
lock (this)
|
||||
{
|
||||
if (newFoldings != null && newFoldings.Count != 0)
|
||||
{
|
||||
newFoldings.Sort();
|
||||
if (foldMarker.Count == newFoldings.Count)
|
||||
{
|
||||
for (int i = 0; i < foldMarker.Count; ++i)
|
||||
{
|
||||
newFoldings[i].IsFolded = foldMarker[i].IsFolded;
|
||||
}
|
||||
foldMarker = newFoldings;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0, j = 0; i < foldMarker.Count && j < newFoldings.Count;)
|
||||
{
|
||||
int n = newFoldings[j].CompareTo(foldMarker[i]);
|
||||
if (n > 0)
|
||||
{
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (n == 0)
|
||||
{
|
||||
newFoldings[j].IsFolded = foldMarker[i].IsFolded;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newFoldings != null)
|
||||
{
|
||||
foldMarker = newFoldings;
|
||||
foldMarkerByEnd = new List<FoldMarker>(newFoldings);
|
||||
foldMarkerByEnd.Sort(EndComparer.Instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
foldMarker.Clear();
|
||||
foldMarkerByEnd.Clear();
|
||||
}
|
||||
}
|
||||
if (oldFoldingsCount != foldMarker.Count)
|
||||
{
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
||||
document.CommitUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public string SerializeToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (FoldMarker marker in this.foldMarker)
|
||||
{
|
||||
sb.Append(marker.Offset); sb.Append("\n");
|
||||
sb.Append(marker.Length); sb.Append("\n");
|
||||
sb.Append(marker.FoldText); sb.Append("\n");
|
||||
sb.Append(marker.IsFolded); sb.Append("\n");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void DeserializeFromString(string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] lines = str.Split('\n');
|
||||
for (int i = 0; i < lines.Length && lines[i].Length > 0; i += 4)
|
||||
{
|
||||
int offset = int.Parse(lines[i]);
|
||||
int length = int.Parse(lines[i + 1]);
|
||||
string text = lines[i + 2];
|
||||
bool isFolded = bool.Parse(lines[i + 3]);
|
||||
bool found = false;
|
||||
foreach (FoldMarker marker in foldMarker)
|
||||
{
|
||||
if (marker.Offset == offset && marker.Length == length)
|
||||
{
|
||||
marker.IsFolded = isFolded;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
foldMarker.Add(new FoldMarker(document, offset, length, text, isFolded));
|
||||
}
|
||||
}
|
||||
if (lines.Length > 0)
|
||||
{
|
||||
NotifyFoldingsChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Empty catch
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyFoldingsChanged(EventArgs e)
|
||||
{
|
||||
if (FoldingsChanged != null)
|
||||
{
|
||||
FoldingsChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler FoldingsChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used for the folding capabilities
|
||||
/// of the textarea.
|
||||
/// </summary>
|
||||
public interface IFoldingStrategy
|
||||
{
|
||||
/// <remarks>
|
||||
/// Calculates the fold level of a specific line.
|
||||
/// </remarks>
|
||||
List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using ICSharpCode.TextEditor.Document;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
|
||||
{
|
||||
public interface IFoldingStrategyEx : IFoldingStrategy
|
||||
{
|
||||
List<string> GetFoldingErrors();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple folding strategy which calculates the folding level
|
||||
/// using the indent level of the line.
|
||||
/// </summary>
|
||||
public class IndentFoldingStrategy : IFoldingStrategy
|
||||
{
|
||||
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
|
||||
{
|
||||
List<FoldMarker> l = new List<FoldMarker>();
|
||||
Stack<int> offsetStack = new Stack<int>();
|
||||
Stack<string> textStack = new Stack<string>();
|
||||
//int level = 0;
|
||||
//foreach (LineSegment segment in document.LineSegmentCollection) {
|
||||
//
|
||||
//}
|
||||
return l;
|
||||
}
|
||||
|
||||
int GetLevel(IDocument document, int offset)
|
||||
{
|
||||
int level = 0;
|
||||
int spaces = 0;
|
||||
for (int i = offset; i < document.TextLength; ++i) {
|
||||
char c = document.GetCharAt(i);
|
||||
if (c == '\t' || (c == ' ' && ++spaces == 4)) {
|
||||
spaces = 0;
|
||||
++level;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return level;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// This file is part of CodingEditor.
|
||||
// Note: This project is derived from Peter Project
|
||||
// (hosted on sourceforge and codeplex)
|
||||
//
|
||||
// Copyright (c) 2008-2009, CE Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ICSharpCode.TextEditor.Document;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
|
||||
{
|
||||
public class JSONFoldingStrategy : IFoldingStrategy
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Generates the foldings for our document.
|
||||
/// </summary>
|
||||
/// <param name="document">The current document.</param>
|
||||
/// <param name="fileName">The filename of the document.</param>
|
||||
/// <param name="parseInformation">Extra parse information, not used in this sample.</param>
|
||||
/// <returns>A list of FoldMarkers.</returns>
|
||||
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
|
||||
{
|
||||
var list = new List<FoldMarker>();
|
||||
var startLines = new Stack<int>();
|
||||
|
||||
// Create foldmarkers for the whole document, enumerate through every line.
|
||||
for (int i = 0; i < document.TotalNumberOfLines; i++)
|
||||
{
|
||||
var seg = document.GetLineSegment(i);
|
||||
int offs, end = seg.Length + seg.Offset;
|
||||
char c;
|
||||
for (
|
||||
offs = seg.Offset;
|
||||
offs < end;
|
||||
offs++)
|
||||
{
|
||||
|
||||
c = document.GetCharAt(offs);
|
||||
if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString()))
|
||||
{
|
||||
if (c == '{')
|
||||
{
|
||||
int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '{', '}');
|
||||
if (offsetOfClosingBracket > 0)
|
||||
{
|
||||
int length = offsetOfClosingBracket - offs + 1;
|
||||
list.Add(new FoldMarker(document, offs, length, "{...}", false));
|
||||
}
|
||||
}
|
||||
if (c == '[')
|
||||
{
|
||||
int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '[', ']');
|
||||
if (offsetOfClosingBracket > 0)
|
||||
{
|
||||
int length = offsetOfClosingBracket - offs + 1;
|
||||
list.Add(new FoldMarker(document, offs, length, "[...]", false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
private int SearchBracketForward(IDocument document, int currLine, int currOffset, char openBracket, char closingBracket)
|
||||
{
|
||||
// Create foldmarkers for the whole document, enumerate through every line.
|
||||
|
||||
int brackets = 1, spaceCount = 0;
|
||||
for (int i = currLine; i < document.TotalNumberOfLines; i++)
|
||||
{
|
||||
var seg = document.GetLineSegment(i);
|
||||
int offs, end = seg.Length + seg.Offset;
|
||||
char c;
|
||||
for (
|
||||
offs = i == currLine ? currOffset + 1 : seg.Offset;
|
||||
offs < end;
|
||||
offs++)
|
||||
{
|
||||
|
||||
c = document.GetCharAt(offs);
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') spaceCount++;
|
||||
if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString()))
|
||||
{
|
||||
if (c == openBracket)
|
||||
{
|
||||
++brackets;
|
||||
}
|
||||
else if (c == closingBracket)
|
||||
{
|
||||
--brackets;
|
||||
if (brackets == 0)
|
||||
{
|
||||
if (offs - spaceCount - 1 == currOffset)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return offs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copied from http://codingeditor.googlecode.com/svn/trunk/libs/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/
|
||||
#region Header
|
||||
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Matthew Ward" email="mrward@users.sourceforge.net"/>
|
||||
// <version>$Revision: 1971 $</version>
|
||||
// </file>
|
||||
|
||||
#endregion Header
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using ICSharpCode.TextEditor.Document;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds information about the start of a fold in an xml string.
|
||||
/// </summary>
|
||||
class XmlFoldStart
|
||||
{
|
||||
#region Fields
|
||||
|
||||
readonly int _col;
|
||||
string _foldText = string.Empty;
|
||||
readonly int _line;
|
||||
readonly string _name = string.Empty;
|
||||
readonly string _prefix = string.Empty;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Constructors
|
||||
|
||||
public XmlFoldStart(string prefix, string name, int line, int col)
|
||||
{
|
||||
_line = line;
|
||||
_col = col;
|
||||
_prefix = prefix;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The column where the fold should start. Columns start from 0.
|
||||
/// </summary>
|
||||
public int Column
|
||||
{
|
||||
get
|
||||
{
|
||||
return _col;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text to be displayed when the item is folded.
|
||||
/// </summary>
|
||||
public string FoldText
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foldText;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_foldText = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The line where the fold should start. Lines start from 0.
|
||||
/// </summary>
|
||||
public int Line
|
||||
{
|
||||
get
|
||||
{
|
||||
return _line;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the xml item with its prefix if it has one.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _prefix.Length > 0 ? string.Concat(_prefix, ":", _name) : _name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines folds for an xml string in the editor.
|
||||
/// </summary>
|
||||
public class XmlFoldingStrategy : IFoldingStrategyEx
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating whether attributes should be displayed on folded elements.
|
||||
/// </summary>
|
||||
public bool ShowAttributesWhenFolded = false;
|
||||
|
||||
private List<string> _foldingErrors = new List<string>();
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Methods
|
||||
|
||||
public List<string> GetFoldingErrors()
|
||||
{
|
||||
return _foldingErrors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds folds to the text editor around each start-end element pair.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the xml is not well formed then no folds are created.</para>
|
||||
/// <para>Note that the xml text reader lines and positions start
|
||||
/// from 1 and the SharpDevelop text editor line information starts from 0.</para>
|
||||
/// </remarks>
|
||||
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
|
||||
{
|
||||
_foldingErrors = new List<string>();
|
||||
//showAttributesWhenFolded = XmlEditorAddInOptions.ShowAttributesWhenFolded;
|
||||
|
||||
var foldMarkers = new List<FoldMarker>();
|
||||
var stack = new Stack();
|
||||
|
||||
try
|
||||
{
|
||||
string xml = document.TextContent;
|
||||
var reader = new XmlTextReader(new StringReader(xml));
|
||||
while (reader.Read())
|
||||
{
|
||||
switch (reader.NodeType)
|
||||
{
|
||||
case XmlNodeType.Element:
|
||||
if (!reader.IsEmptyElement)
|
||||
{
|
||||
XmlFoldStart newFoldStart = CreateElementFoldStart(reader);
|
||||
stack.Push(newFoldStart);
|
||||
}
|
||||
break;
|
||||
|
||||
case XmlNodeType.EndElement:
|
||||
var foldStart = (XmlFoldStart)stack.Pop();
|
||||
CreateElementFold(document, foldMarkers, reader, foldStart);
|
||||
break;
|
||||
|
||||
case XmlNodeType.Comment:
|
||||
CreateCommentFold(document, foldMarkers, reader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_foldingErrors.Add(ex.Message);
|
||||
|
||||
// If the xml is not well formed keep the foldings that already exist in the document.
|
||||
return new List<FoldMarker>(document.FoldingManager.FoldMarker);
|
||||
}
|
||||
|
||||
return foldMarkers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Xml encode the attribute string since the string returned from
|
||||
/// the XmlTextReader is the plain unencoded string and .NET
|
||||
/// does not provide us with an xml encode method.
|
||||
/// </summary>
|
||||
static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
|
||||
{
|
||||
var encodedValue = new StringBuilder(attributeValue);
|
||||
|
||||
encodedValue.Replace("&", "&");
|
||||
encodedValue.Replace("<", "<");
|
||||
encodedValue.Replace(">", ">");
|
||||
|
||||
if (quoteChar == '"')
|
||||
{
|
||||
encodedValue.Replace("\"", """);
|
||||
}
|
||||
else
|
||||
{
|
||||
encodedValue.Replace("'", "'");
|
||||
}
|
||||
|
||||
return encodedValue.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a comment fold if the comment spans more than one line.
|
||||
/// </summary>
|
||||
/// <remarks>The text displayed when the comment is folded is the first
|
||||
/// line of the comment.</remarks>
|
||||
void CreateCommentFold(IDocument document, List<FoldMarker> foldMarkers, XmlTextReader reader)
|
||||
{
|
||||
if (reader.Value != null)
|
||||
{
|
||||
string comment = reader.Value.Replace("\r\n", "\n");
|
||||
string[] lines = comment.Split('\n');
|
||||
if (lines.Length > 1)
|
||||
{
|
||||
|
||||
// Take off 5 chars to get the actual comment start (takes
|
||||
// into account the <!-- chars.
|
||||
int startCol = reader.LinePosition - 5;
|
||||
int startLine = reader.LineNumber - 1;
|
||||
|
||||
// Add 3 to the end col value to take into account the '-->'
|
||||
int endCol = lines[lines.Length - 1].Length + startCol + 3;
|
||||
int endLine = startLine + lines.Length - 1;
|
||||
string foldText = string.Concat("<!--", lines[0], "-->");
|
||||
var foldMarker = new FoldMarker(document, startLine, startCol, endLine, endCol, FoldType.TypeBody, foldText);
|
||||
foldMarkers.Add(foldMarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an element fold if the start and end tag are on
|
||||
/// different lines.
|
||||
/// </summary>
|
||||
void CreateElementFold(IDocument document, List<FoldMarker> foldMarkers, XmlTextReader reader, XmlFoldStart foldStart)
|
||||
{
|
||||
int endLine = reader.LineNumber - 1;
|
||||
if (endLine > foldStart.Line)
|
||||
{
|
||||
int endCol = reader.LinePosition + foldStart.Name.Length;
|
||||
var foldMarker = new FoldMarker(document, foldStart.Line, foldStart.Column, endLine, endCol, FoldType.TypeBody, foldStart.FoldText);
|
||||
foldMarkers.Add(foldMarker);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an XmlFoldStart for the start tag of an element.
|
||||
/// </summary>
|
||||
XmlFoldStart CreateElementFoldStart(XmlTextReader reader)
|
||||
{
|
||||
// Take off 2 from the line position returned
|
||||
// from the xml since it points to the start
|
||||
// of the element name and not the beginning
|
||||
// tag.
|
||||
var newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2);
|
||||
|
||||
if (ShowAttributesWhenFolded && reader.HasAttributes)
|
||||
{
|
||||
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, " ", GetAttributeFoldText(reader), ">");
|
||||
}
|
||||
else
|
||||
{
|
||||
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, ">");
|
||||
}
|
||||
|
||||
return newFoldStart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element's attributes as a string on one line that will
|
||||
/// be displayed when the element is folded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently this puts all attributes from an element on the same
|
||||
/// line of the start tag. It does not cater for elements where attributes
|
||||
/// are not on the same line as the start tag.
|
||||
/// </remarks>
|
||||
string GetAttributeFoldText(XmlTextReader reader)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < reader.AttributeCount; ++i)
|
||||
{
|
||||
reader.MoveToAttribute(i);
|
||||
|
||||
text.Append(reader.Name);
|
||||
text.Append("=");
|
||||
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
|
||||
text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
|
||||
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
// Append a space if this is not the
|
||||
// last attribute.
|
||||
if (i < reader.AttributeCount - 1)
|
||||
{
|
||||
text.Append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles the auto and smart indenting in the textbuffer while
|
||||
/// you type.
|
||||
/// </summary>
|
||||
public class DefaultFormattingStrategy : IFormattingStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance off <see cref="DefaultFormattingStrategy"/>
|
||||
/// </summary>
|
||||
public DefaultFormattingStrategy()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the whitespaces which are before a non white space character in the line line
|
||||
/// as a string.
|
||||
/// </summary>
|
||||
protected string GetIndentation(TextArea textArea, int lineNumber)
|
||||
{
|
||||
if (lineNumber < 0 || lineNumber > textArea.Document.TotalNumberOfLines) {
|
||||
throw new ArgumentOutOfRangeException("lineNumber");
|
||||
}
|
||||
|
||||
string lineText = TextUtilities.GetLineAsString(textArea.Document, lineNumber);
|
||||
StringBuilder whitespaces = new StringBuilder();
|
||||
|
||||
foreach (char ch in lineText) {
|
||||
if (char.IsWhiteSpace(ch)) {
|
||||
whitespaces.Append(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return whitespaces.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could be overwritten to define more complex indenting.
|
||||
/// </summary>
|
||||
protected virtual int AutoIndentLine(TextArea textArea, int lineNumber)
|
||||
{
|
||||
string indentation = lineNumber != 0 ? GetIndentation(textArea, lineNumber - 1) : "";
|
||||
if(indentation.Length > 0) {
|
||||
string newLineText = indentation + TextUtilities.GetLineAsString(textArea.Document, lineNumber).Trim();
|
||||
LineSegment oldLine = textArea.Document.GetLineSegment(lineNumber);
|
||||
SmartReplaceLine(textArea.Document, oldLine, newLineText);
|
||||
}
|
||||
return indentation.Length;
|
||||
}
|
||||
|
||||
static readonly char[] whitespaceChars = {' ', '\t'};
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the text in a line.
|
||||
/// If only whitespace at the beginning and end of the line was changed, this method
|
||||
/// only adjusts the whitespace and doesn't replace the other text.
|
||||
/// </summary>
|
||||
public static void SmartReplaceLine(IDocument document, LineSegment line, string newLineText)
|
||||
{
|
||||
if (document == null)
|
||||
throw new ArgumentNullException("document");
|
||||
if (line == null)
|
||||
throw new ArgumentNullException("line");
|
||||
if (newLineText == null)
|
||||
throw new ArgumentNullException("newLineText");
|
||||
string newLineTextTrim = newLineText.Trim(whitespaceChars);
|
||||
string oldLineText = document.GetText(line);
|
||||
if (oldLineText == newLineText)
|
||||
return;
|
||||
int pos = oldLineText.IndexOf(newLineTextTrim);
|
||||
if (newLineTextTrim.Length > 0 && pos >= 0) {
|
||||
document.UndoStack.StartUndoGroup();
|
||||
try {
|
||||
// find whitespace at beginning
|
||||
int startWhitespaceLength = 0;
|
||||
while (startWhitespaceLength < newLineText.Length) {
|
||||
char c = newLineText[startWhitespaceLength];
|
||||
if (c != ' ' && c != '\t')
|
||||
break;
|
||||
startWhitespaceLength++;
|
||||
}
|
||||
// find whitespace at end
|
||||
int endWhitespaceLength = newLineText.Length - newLineTextTrim.Length - startWhitespaceLength;
|
||||
|
||||
// replace whitespace sections
|
||||
int lineOffset = line.Offset;
|
||||
document.Replace(lineOffset + pos + newLineTextTrim.Length, line.Length - pos - newLineTextTrim.Length, newLineText.Substring(newLineText.Length - endWhitespaceLength));
|
||||
document.Replace(lineOffset, pos, newLineText.Substring(0, startWhitespaceLength));
|
||||
} finally {
|
||||
document.UndoStack.EndUndoGroup();
|
||||
}
|
||||
} else {
|
||||
document.Replace(line.Offset, line.Length, newLineText);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Could be overwritten to define more complex indenting.
|
||||
/// </summary>
|
||||
protected virtual int SmartIndentLine(TextArea textArea, int line)
|
||||
{
|
||||
return AutoIndentLine(textArea, line); // smart = autoindent in normal texts
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function formats a specific line after <code>ch</code> is pressed.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the caret delta position the caret will be moved this number
|
||||
/// of bytes (e.g. the number of bytes inserted before the caret, or
|
||||
/// removed, if this number is negative)
|
||||
/// </returns>
|
||||
public virtual void FormatLine(TextArea textArea, int line, int cursorOffset, char ch)
|
||||
{
|
||||
if (ch == '\n') {
|
||||
textArea.Caret.Column = IndentLine(textArea, line);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function sets the indentation level in a specific line
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the number of inserted characters.
|
||||
/// </returns>
|
||||
public int IndentLine(TextArea textArea, int line)
|
||||
{
|
||||
textArea.Document.UndoStack.StartUndoGroup();
|
||||
int result;
|
||||
switch (textArea.Document.TextEditorProperties.IndentStyle) {
|
||||
case IndentStyle.None:
|
||||
result = 0;
|
||||
break;
|
||||
case IndentStyle.Auto:
|
||||
result = AutoIndentLine(textArea, line);
|
||||
break;
|
||||
case IndentStyle.Smart:
|
||||
result = SmartIndentLine(textArea, line);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported value for IndentStyle: " + textArea.Document.TextEditorProperties.IndentStyle);
|
||||
}
|
||||
textArea.Document.UndoStack.EndUndoGroup();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function sets the indentlevel in a range of lines.
|
||||
/// </summary>
|
||||
public virtual void IndentLines(TextArea textArea, int begin, int end)
|
||||
{
|
||||
textArea.Document.UndoStack.StartUndoGroup();
|
||||
for (int i = begin; i <= end; ++i) {
|
||||
IndentLine(textArea, i);
|
||||
}
|
||||
textArea.Document.UndoStack.EndUndoGroup();
|
||||
}
|
||||
|
||||
public virtual int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket)
|
||||
{
|
||||
int brackets = -1;
|
||||
// first try "quick find" - find the matching bracket if there is no string/comment in the way
|
||||
for (int i = offset; i >= 0; --i) {
|
||||
char ch = document.GetCharAt(i);
|
||||
if (ch == openBracket) {
|
||||
++brackets;
|
||||
if (brackets == 0) return i;
|
||||
} else if (ch == closingBracket) {
|
||||
--brackets;
|
||||
} else if (ch == '"') {
|
||||
break;
|
||||
} else if (ch == '\'') {
|
||||
break;
|
||||
} else if (ch == '/' && i > 0) {
|
||||
if (document.GetCharAt(i - 1) == '/') break;
|
||||
if (document.GetCharAt(i - 1) == '*') break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public virtual int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket)
|
||||
{
|
||||
int brackets = 1;
|
||||
// try "quick find" - find the matching bracket if there is no string/comment in the way
|
||||
for (int i = offset; i < document.TextLength; ++i) {
|
||||
char ch = document.GetCharAt(i);
|
||||
if (ch == openBracket) {
|
||||
++brackets;
|
||||
} else if (ch == closingBracket) {
|
||||
--brackets;
|
||||
if (brackets == 0) return i;
|
||||
} else if (ch == '"') {
|
||||
break;
|
||||
} else if (ch == '\'') {
|
||||
break;
|
||||
} else if (ch == '/' && i > 0) {
|
||||
if (document.GetCharAt(i - 1) == '/') break;
|
||||
} else if (ch == '*' && i > 0) {
|
||||
if (document.GetCharAt(i - 1) == '/') break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface handles the auto and smart indenting and formating
|
||||
/// in the document while you type. Language bindings could overwrite this
|
||||
/// interface and define their own indentation/formating.
|
||||
/// </summary>
|
||||
public interface IFormattingStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// This function formats a specific line after <code>ch</code> is pressed.
|
||||
/// </summary>
|
||||
void FormatLine(TextArea textArea, int line, int caretOffset, char charTyped);
|
||||
|
||||
/// <summary>
|
||||
/// This function sets the indentation level in a specific line
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The target caret position (length of new indentation).
|
||||
/// </returns>
|
||||
int IndentLine(TextArea textArea, int line);
|
||||
|
||||
/// <summary>
|
||||
/// This function sets the indentlevel in a range of lines.
|
||||
/// </summary>
|
||||
void IndentLines(TextArea textArea, int begin, int end);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the offset of the opening bracket in the block defined by offset skipping
|
||||
/// brackets in strings and comments.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to search in.</param>
|
||||
/// <param name="offset">The offset of an position in the block or the offset of the closing bracket.</param>
|
||||
/// <param name="openBracket">The character for the opening bracket.</param>
|
||||
/// <param name="closingBracket">The character for the closing bracket.</param>
|
||||
/// <returns>Returns the offset of the opening bracket or -1 if no matching bracket was found.</returns>
|
||||
int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the offset of the closing bracket in the block defined by offset skipping
|
||||
/// brackets in strings and comments.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to search in.</param>
|
||||
/// <param name="offset">The offset of an position in the block or the offset of the opening bracket.</param>
|
||||
/// <param name="openBracket">The character for the opening bracket.</param>
|
||||
/// <param name="closingBracket">The character for the closing bracket.</param>
|
||||
/// <returns>Returns the offset of the closing bracket or -1 if no matching bracket was found.</returns>
|
||||
int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,917 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class DefaultHighlightingStrategy : IHighlightingStrategyUsingRuleSets
|
||||
{
|
||||
string name;
|
||||
List<HighlightRuleSet> rules = new List<HighlightRuleSet>();
|
||||
|
||||
Dictionary<string, HighlightColor> environmentColors = new Dictionary<string, HighlightColor>();
|
||||
Dictionary<string, string> properties = new Dictionary<string, string>();
|
||||
string[] extensions;
|
||||
|
||||
HighlightColor digitColor;
|
||||
HighlightRuleSet defaultRuleSet = null;
|
||||
|
||||
public HighlightColor DigitColor {
|
||||
get {
|
||||
return digitColor;
|
||||
}
|
||||
set {
|
||||
digitColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<string, HighlightColor>> EnvironmentColors {
|
||||
get {
|
||||
return environmentColors;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ImportSettingsFrom(DefaultHighlightingStrategy source)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
properties = source.properties;
|
||||
extensions = source.extensions;
|
||||
digitColor = source.digitColor;
|
||||
defaultRuleSet = source.defaultRuleSet;
|
||||
name = source.name;
|
||||
rules = source.rules;
|
||||
environmentColors = source.environmentColors;
|
||||
defaultTextColor = source.defaultTextColor;
|
||||
}
|
||||
|
||||
public DefaultHighlightingStrategy() : this("Default")
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultHighlightingStrategy(string name)
|
||||
{
|
||||
this.name = name;
|
||||
|
||||
digitColor = new HighlightColor(SystemColors.WindowText, false, false);
|
||||
defaultTextColor = new HighlightColor(SystemColors.WindowText, false, false);
|
||||
|
||||
// set small 'default color environment'
|
||||
environmentColors["Default"] = new HighlightBackground("WindowText", "Window", false, false);
|
||||
environmentColors["Selection"] = new HighlightColor("HighlightText", "Highlight", false, false);
|
||||
environmentColors["VRuler"] = new HighlightColor("ControlLight", "Window", false, false);
|
||||
environmentColors["InvalidLines"] = new HighlightColor(Color.Red, false, false);
|
||||
environmentColors["CaretMarker"] = new HighlightColor(Color.Yellow, false, false);
|
||||
environmentColors["CaretLine"] = new HighlightBackground("ControlLight", "Window", false, false);
|
||||
environmentColors["LineNumbers"] = new HighlightBackground("ControlDark", "Window", false, false);
|
||||
|
||||
environmentColors["FoldLine"] = new HighlightColor("ControlDark", false, false);
|
||||
environmentColors["FoldMarker"] = new HighlightColor("WindowText", "Window", false, false);
|
||||
environmentColors["SelectedFoldLine"] = new HighlightColor("WindowText", false, false);
|
||||
environmentColors["EOLMarkers"] = new HighlightColor("ControlLight", "Window", false, false);
|
||||
environmentColors["SpaceMarkers"] = new HighlightColor("ControlLight", "Window", false, false);
|
||||
environmentColors["TabMarkers"] = new HighlightColor("ControlLight", "Window", false, false);
|
||||
|
||||
}
|
||||
|
||||
public Dictionary<string, string> Properties {
|
||||
get {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] Extensions
|
||||
{
|
||||
set {
|
||||
extensions = value;
|
||||
}
|
||||
get {
|
||||
return extensions;
|
||||
}
|
||||
}
|
||||
|
||||
public List<HighlightRuleSet> Rules {
|
||||
get {
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightRuleSet FindHighlightRuleSet(string name)
|
||||
{
|
||||
foreach(HighlightRuleSet ruleSet in rules) {
|
||||
if (ruleSet.Name == name) {
|
||||
return ruleSet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AddRuleSet(HighlightRuleSet aRuleSet)
|
||||
{
|
||||
HighlightRuleSet existing = FindHighlightRuleSet(aRuleSet.Name);
|
||||
if (existing != null) {
|
||||
existing.MergeFrom(aRuleSet);
|
||||
} else {
|
||||
rules.Add(aRuleSet);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResolveReferences()
|
||||
{
|
||||
// Resolve references from Span definitions to RuleSets
|
||||
ResolveRuleSetReferences();
|
||||
// Resolve references from RuleSet defintitions to Highlighters defined in an external mode file
|
||||
ResolveExternalReferences();
|
||||
}
|
||||
|
||||
void ResolveRuleSetReferences()
|
||||
{
|
||||
foreach (HighlightRuleSet ruleSet in Rules) {
|
||||
if (ruleSet.Name == null) {
|
||||
defaultRuleSet = ruleSet;
|
||||
}
|
||||
|
||||
foreach (Span aSpan in ruleSet.Spans) {
|
||||
if (aSpan.Rule != null) {
|
||||
bool found = false;
|
||||
foreach (HighlightRuleSet refSet in Rules) {
|
||||
if (refSet.Name == aSpan.Rule) {
|
||||
found = true;
|
||||
aSpan.RuleSet = refSet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
aSpan.RuleSet = null;
|
||||
throw new HighlightingDefinitionInvalidException("The RuleSet " + aSpan.Rule + " could not be found in mode definition " + this.Name);
|
||||
}
|
||||
} else {
|
||||
aSpan.RuleSet = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultRuleSet == null) {
|
||||
throw new HighlightingDefinitionInvalidException("No default RuleSet is defined for mode definition " + this.Name);
|
||||
}
|
||||
}
|
||||
|
||||
void ResolveExternalReferences()
|
||||
{
|
||||
foreach (HighlightRuleSet ruleSet in Rules) {
|
||||
ruleSet.Highlighter = this;
|
||||
if (ruleSet.Reference != null) {
|
||||
IHighlightingStrategy highlighter = HighlightingManager.Manager.FindHighlighter (ruleSet.Reference);
|
||||
|
||||
if (highlighter == null)
|
||||
throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + this.Name + " mode definition could not be found");
|
||||
if (highlighter is IHighlightingStrategyUsingRuleSets)
|
||||
ruleSet.Highlighter = (IHighlightingStrategyUsingRuleSets)highlighter;
|
||||
else
|
||||
throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + this.Name + " mode definition does not implement IHighlightingStrategyUsingRuleSets");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// internal void SetDefaultColor(HighlightBackground color)
|
||||
// {
|
||||
// return (HighlightColor)environmentColors[name];
|
||||
// defaultColor = color;
|
||||
// }
|
||||
|
||||
HighlightColor defaultTextColor;
|
||||
|
||||
public HighlightColor DefaultTextColor {
|
||||
get {
|
||||
return defaultTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetColorFor(string name, HighlightColor color)
|
||||
{
|
||||
if (name == "Default")
|
||||
defaultTextColor = new HighlightColor(color.Color, color.Bold, color.Italic);
|
||||
environmentColors[name] = color;
|
||||
}
|
||||
|
||||
public HighlightColor GetColorFor(string name)
|
||||
{
|
||||
HighlightColor color;
|
||||
if (environmentColors.TryGetValue(name, out color))
|
||||
return color;
|
||||
else
|
||||
return defaultTextColor;
|
||||
}
|
||||
|
||||
public HighlightColor GetColor(IDocument document, LineSegment currentSegment, int currentOffset, int currentLength)
|
||||
{
|
||||
return GetColor(defaultRuleSet, document, currentSegment, currentOffset, currentLength);
|
||||
}
|
||||
|
||||
protected virtual HighlightColor GetColor(HighlightRuleSet ruleSet, IDocument document, LineSegment currentSegment, int currentOffset, int currentLength)
|
||||
{
|
||||
if (ruleSet != null) {
|
||||
if (ruleSet.Reference != null) {
|
||||
return ruleSet.Highlighter.GetColor(document, currentSegment, currentOffset, currentLength);
|
||||
} else {
|
||||
return (HighlightColor)ruleSet.KeyWords[document, currentSegment, currentOffset, currentLength];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public HighlightRuleSet GetRuleSet(Span aSpan)
|
||||
{
|
||||
if (aSpan == null) {
|
||||
return this.defaultRuleSet;
|
||||
} else {
|
||||
if (aSpan.RuleSet != null)
|
||||
{
|
||||
if (aSpan.RuleSet.Reference != null) {
|
||||
return aSpan.RuleSet.Highlighter.GetRuleSet(null);
|
||||
} else {
|
||||
return aSpan.RuleSet;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Line state variable
|
||||
protected LineSegment currentLine;
|
||||
protected int currentLineNumber;
|
||||
|
||||
// Span stack state variable
|
||||
protected SpanStack currentSpanStack;
|
||||
|
||||
public virtual void MarkTokens(IDocument document)
|
||||
{
|
||||
if (Rules.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int lineNumber = 0;
|
||||
|
||||
while (lineNumber < document.TotalNumberOfLines) {
|
||||
LineSegment previousLine = (lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null);
|
||||
if (lineNumber >= document.LineSegmentCollection.Count) { // may be, if the last line ends with a delimiter
|
||||
break; // then the last line is not in the collection :)
|
||||
}
|
||||
|
||||
currentSpanStack = ((previousLine != null && previousLine.HighlightSpanStack != null) ? previousLine.HighlightSpanStack.Clone() : null);
|
||||
|
||||
if (currentSpanStack != null) {
|
||||
while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL)
|
||||
{
|
||||
currentSpanStack.Pop();
|
||||
}
|
||||
if (currentSpanStack.IsEmpty) currentSpanStack = null;
|
||||
}
|
||||
|
||||
currentLine = (LineSegment)document.LineSegmentCollection[lineNumber];
|
||||
|
||||
if (currentLine.Length == -1) { // happens when buffer is empty !
|
||||
return;
|
||||
}
|
||||
|
||||
currentLineNumber = lineNumber;
|
||||
List<TextWord> words = ParseLine(document);
|
||||
// Alex: clear old words
|
||||
if (currentLine.Words != null) {
|
||||
currentLine.Words.Clear();
|
||||
}
|
||||
currentLine.Words = words;
|
||||
currentLine.HighlightSpanStack = (currentSpanStack==null || currentSpanStack.IsEmpty) ? null : currentSpanStack;
|
||||
|
||||
++lineNumber;
|
||||
}
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
||||
document.CommitUpdate();
|
||||
currentLine = null;
|
||||
}
|
||||
|
||||
bool MarkTokensInLine(IDocument document, int lineNumber, ref bool spanChanged)
|
||||
{
|
||||
currentLineNumber = lineNumber;
|
||||
bool processNextLine = false;
|
||||
LineSegment previousLine = (lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null);
|
||||
|
||||
currentSpanStack = ((previousLine != null && previousLine.HighlightSpanStack != null) ? previousLine.HighlightSpanStack.Clone() : null);
|
||||
if (currentSpanStack != null) {
|
||||
while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL) {
|
||||
currentSpanStack.Pop();
|
||||
}
|
||||
if (currentSpanStack.IsEmpty) {
|
||||
currentSpanStack = null;
|
||||
}
|
||||
}
|
||||
|
||||
currentLine = (LineSegment)document.LineSegmentCollection[lineNumber];
|
||||
|
||||
if (currentLine.Length == -1) { // happens when buffer is empty !
|
||||
return false;
|
||||
}
|
||||
|
||||
List<TextWord> words = ParseLine(document);
|
||||
|
||||
if (currentSpanStack != null && currentSpanStack.IsEmpty) {
|
||||
currentSpanStack = null;
|
||||
}
|
||||
|
||||
// Check if the span state has changed, if so we must re-render the next line
|
||||
// This check may seem utterly complicated but I didn't want to introduce any function calls
|
||||
// or allocations here for perf reasons.
|
||||
if(currentLine.HighlightSpanStack != currentSpanStack) {
|
||||
if (currentLine.HighlightSpanStack == null) {
|
||||
processNextLine = false;
|
||||
foreach (Span sp in currentSpanStack) {
|
||||
if (!sp.StopEOL) {
|
||||
spanChanged = true;
|
||||
processNextLine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (currentSpanStack == null) {
|
||||
processNextLine = false;
|
||||
foreach (Span sp in currentLine.HighlightSpanStack) {
|
||||
if (!sp.StopEOL) {
|
||||
spanChanged = true;
|
||||
processNextLine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SpanStack.Enumerator e1 = currentSpanStack.GetEnumerator();
|
||||
SpanStack.Enumerator e2 = currentLine.HighlightSpanStack.GetEnumerator();
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
bool blockSpanIn1 = false;
|
||||
while (e1.MoveNext()) {
|
||||
if (!((Span)e1.Current).StopEOL) {
|
||||
blockSpanIn1 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool blockSpanIn2 = false;
|
||||
while (e2.MoveNext()) {
|
||||
if (!((Span)e2.Current).StopEOL) {
|
||||
blockSpanIn2 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (blockSpanIn1 || blockSpanIn2) {
|
||||
if (blockSpanIn1 && blockSpanIn2) {
|
||||
if (e1.Current != e2.Current) {
|
||||
done = true;
|
||||
processNextLine = true;
|
||||
spanChanged = true;
|
||||
}
|
||||
} else {
|
||||
spanChanged = true;
|
||||
done = true;
|
||||
processNextLine = true;
|
||||
}
|
||||
} else {
|
||||
done = true;
|
||||
processNextLine = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
processNextLine = false;
|
||||
}
|
||||
|
||||
//// Alex: remove old words
|
||||
if (currentLine.Words!=null) currentLine.Words.Clear();
|
||||
currentLine.Words = words;
|
||||
currentLine.HighlightSpanStack = (currentSpanStack != null && !currentSpanStack.IsEmpty) ? currentSpanStack : null;
|
||||
|
||||
return processNextLine;
|
||||
}
|
||||
|
||||
public virtual void MarkTokens(IDocument document, List<LineSegment> inputLines)
|
||||
{
|
||||
if (Rules.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<LineSegment, bool> processedLines = new Dictionary<LineSegment, bool>();
|
||||
|
||||
bool spanChanged = false;
|
||||
int documentLineSegmentCount = document.LineSegmentCollection.Count;
|
||||
|
||||
foreach (LineSegment lineToProcess in inputLines) {
|
||||
if (!processedLines.ContainsKey(lineToProcess)) {
|
||||
int lineNumber = lineToProcess.LineNumber;
|
||||
bool processNextLine = true;
|
||||
|
||||
if (lineNumber != -1) {
|
||||
while (processNextLine && lineNumber < documentLineSegmentCount) {
|
||||
processNextLine = MarkTokensInLine(document, lineNumber, ref spanChanged);
|
||||
processedLines[currentLine] = true;
|
||||
++lineNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spanChanged || inputLines.Count > 20) {
|
||||
// if the span was changed (more than inputLines lines had to be reevaluated)
|
||||
// or if there are many lines in inputLines, it's faster to update the whole
|
||||
// text area instead of many small segments
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
||||
} else {
|
||||
// document.Caret.ValidateCaretPos();
|
||||
// document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, document.GetLineNumberForOffset(document.Caret.Offset)));
|
||||
//
|
||||
foreach (LineSegment lineToProcess in inputLines) {
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, lineToProcess.LineNumber));
|
||||
}
|
||||
|
||||
}
|
||||
document.CommitUpdate();
|
||||
currentLine = null;
|
||||
}
|
||||
|
||||
// Span state variables
|
||||
protected bool inSpan;
|
||||
protected Span activeSpan;
|
||||
protected HighlightRuleSet activeRuleSet;
|
||||
|
||||
// Line scanning state variables
|
||||
protected int currentOffset;
|
||||
protected int currentLength;
|
||||
protected bool isDelimiter = false;
|
||||
|
||||
void UpdateSpanStateVariables()
|
||||
{
|
||||
inSpan = (currentSpanStack != null && !currentSpanStack.IsEmpty);
|
||||
activeSpan = inSpan ? currentSpanStack.Peek() : null;
|
||||
activeRuleSet = GetRuleSet(activeSpan);
|
||||
}
|
||||
|
||||
List<TextWord> ParseLine(IDocument document)
|
||||
{
|
||||
List<TextWord> words = new List<TextWord>();
|
||||
HighlightColor markNext = null;
|
||||
|
||||
currentOffset = 0;
|
||||
currentLength = 0;
|
||||
UpdateSpanStateVariables();
|
||||
|
||||
int currentLineLength = currentLine.Length;
|
||||
int currentLineOffset = currentLine.Offset;
|
||||
|
||||
for (int i = 0; i < currentLineLength; ++i) {
|
||||
char ch = document.GetCharAt(currentLineOffset + i);
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
PushCurWord(document, ref markNext, words);
|
||||
++currentOffset;
|
||||
break;
|
||||
case ' ':
|
||||
PushCurWord(document, ref markNext, words);
|
||||
if (activeSpan != null && activeSpan.Color.HasBackground) {
|
||||
words.Add(new TextWord.SpaceTextWord(activeSpan.Color));
|
||||
} else {
|
||||
words.Add(TextWord.Space);
|
||||
}
|
||||
++currentOffset;
|
||||
break;
|
||||
case '\t':
|
||||
PushCurWord(document, ref markNext, words);
|
||||
if (activeSpan != null && activeSpan.Color.HasBackground) {
|
||||
words.Add(new TextWord.TabTextWord(activeSpan.Color));
|
||||
} else {
|
||||
words.Add(TextWord.Tab);
|
||||
}
|
||||
++currentOffset;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// handle escape characters
|
||||
char escapeCharacter = '\0';
|
||||
if (activeSpan != null && activeSpan.EscapeCharacter != '\0') {
|
||||
escapeCharacter = activeSpan.EscapeCharacter;
|
||||
} else if (activeRuleSet != null) {
|
||||
escapeCharacter = activeRuleSet.EscapeCharacter;
|
||||
}
|
||||
if (escapeCharacter != '\0' && escapeCharacter == ch) {
|
||||
// we found the escape character
|
||||
if (activeSpan != null && activeSpan.End != null && activeSpan.End.Length == 1
|
||||
&& escapeCharacter == activeSpan.End[0])
|
||||
{
|
||||
// the escape character is a end-doubling escape character
|
||||
// it may count as escape only when the next character is the escape, too
|
||||
if (i + 1 < currentLineLength) {
|
||||
if (document.GetCharAt(currentLineOffset + i + 1) == escapeCharacter) {
|
||||
currentLength += 2;
|
||||
PushCurWord(document, ref markNext, words);
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this is a normal \-style escape
|
||||
++currentLength;
|
||||
if (i + 1 < currentLineLength) {
|
||||
++currentLength;
|
||||
}
|
||||
PushCurWord(document, ref markNext, words);
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// highlight digits
|
||||
if (!inSpan && (char.IsDigit(ch) || (ch == '.' && i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1)))) && currentLength == 0) {
|
||||
bool ishex = false;
|
||||
bool isfloatingpoint = false;
|
||||
|
||||
if (ch == '0' && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'X') { // hex digits
|
||||
const string hex = "0123456789ABCDEF";
|
||||
++currentLength;
|
||||
++i; // skip 'x'
|
||||
++currentLength;
|
||||
ishex = true;
|
||||
while (i + 1 < currentLineLength && hex.IndexOf(char.ToUpper(document.GetCharAt(currentLineOffset + i + 1))) != -1) {
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
} else {
|
||||
++currentLength;
|
||||
while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) {
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
}
|
||||
if (!ishex && i + 1 < currentLineLength && document.GetCharAt(currentLineOffset + i + 1) == '.') {
|
||||
isfloatingpoint = true;
|
||||
++i;
|
||||
++currentLength;
|
||||
while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) {
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'E') {
|
||||
isfloatingpoint = true;
|
||||
++i;
|
||||
++currentLength;
|
||||
if (i + 1 < currentLineLength && (document.GetCharAt(currentLineOffset + i + 1) == '+' || document.GetCharAt(currentLine.Offset + i + 1) == '-')) {
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
while (i + 1 < currentLine.Length && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) {
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 < currentLine.Length) {
|
||||
char nextch = char.ToUpper(document.GetCharAt(currentLineOffset + i + 1));
|
||||
if (nextch == 'F' || nextch == 'M' || nextch == 'D') {
|
||||
isfloatingpoint = true;
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isfloatingpoint) {
|
||||
bool isunsigned = false;
|
||||
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U') {
|
||||
++i;
|
||||
++currentLength;
|
||||
isunsigned = true;
|
||||
}
|
||||
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'L') {
|
||||
++i;
|
||||
++currentLength;
|
||||
if (!isunsigned && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U') {
|
||||
++i;
|
||||
++currentLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, DigitColor, false));
|
||||
currentOffset += currentLength;
|
||||
currentLength = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for SPAN ENDs
|
||||
if (inSpan) {
|
||||
if (activeSpan.End != null && activeSpan.End.Length > 0) {
|
||||
if (MatchExpr(currentLine, activeSpan.End, i, document, activeSpan.IgnoreCase)) {
|
||||
PushCurWord(document, ref markNext, words);
|
||||
string regex = GetRegString(currentLine, activeSpan.End, i, document);
|
||||
currentLength += regex.Length;
|
||||
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, activeSpan.EndColor, false));
|
||||
currentOffset += currentLength;
|
||||
currentLength = 0;
|
||||
i += regex.Length - 1;
|
||||
currentSpanStack.Pop();
|
||||
UpdateSpanStateVariables();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for SPAN BEGIN
|
||||
if (activeRuleSet != null) {
|
||||
foreach (Span span in activeRuleSet.Spans) {
|
||||
if ((!span.IsBeginSingleWord || currentLength == 0)
|
||||
&& (!span.IsBeginStartOfLine.HasValue || span.IsBeginStartOfLine.Value == (currentLength == 0 && words.TrueForAll(delegate(TextWord textWord) { return textWord.Type != TextWordType.Word; })))
|
||||
&& MatchExpr(currentLine, span.Begin, i, document, activeRuleSet.IgnoreCase)) {
|
||||
PushCurWord(document, ref markNext, words);
|
||||
string regex = GetRegString(currentLine, span.Begin, i, document);
|
||||
|
||||
if (!OverrideSpan(regex, document, words, span, ref i)) {
|
||||
currentLength += regex.Length;
|
||||
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, span.BeginColor, false));
|
||||
currentOffset += currentLength;
|
||||
currentLength = 0;
|
||||
|
||||
i += regex.Length - 1;
|
||||
if (currentSpanStack == null) {
|
||||
currentSpanStack = new SpanStack();
|
||||
}
|
||||
currentSpanStack.Push(span);
|
||||
span.IgnoreCase = activeRuleSet.IgnoreCase;
|
||||
|
||||
UpdateSpanStateVariables();
|
||||
}
|
||||
|
||||
goto skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the char is a delimiter
|
||||
if (activeRuleSet != null && (int)ch < 256 && activeRuleSet.Delimiters[(int)ch]) {
|
||||
PushCurWord(document, ref markNext, words);
|
||||
isDelimiter = true;
|
||||
if (currentOffset + currentLength +1 < currentLine.Length) {
|
||||
++currentLength;
|
||||
PushCurWord(document, ref markNext, words);
|
||||
goto skip;
|
||||
}
|
||||
}
|
||||
|
||||
++currentLength;
|
||||
skip: continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PushCurWord(document, ref markNext, words);
|
||||
|
||||
OnParsedLine(document, currentLine, words);
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
protected virtual void OnParsedLine(IDocument document, LineSegment currentLine, List<TextWord> words)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual bool OverrideSpan(string spanBegin, IDocument document, List<TextWord> words, Span span, ref int lineOffset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// pushes the curWord string on the word list, with the
|
||||
/// correct color.
|
||||
/// </summary>
|
||||
void PushCurWord(IDocument document, ref HighlightColor markNext, List<TextWord> words)
|
||||
{
|
||||
// Svante Lidman : Need to look through the next prev logic.
|
||||
if (currentLength > 0) {
|
||||
if (words.Count > 0 && activeRuleSet != null) {
|
||||
TextWord prevWord = null;
|
||||
int pInd = words.Count - 1;
|
||||
while (pInd >= 0) {
|
||||
if (!((TextWord)words[pInd]).IsWhiteSpace) {
|
||||
prevWord = (TextWord)words[pInd];
|
||||
if (prevWord.HasDefaultColor) {
|
||||
PrevMarker marker = (PrevMarker)activeRuleSet.PrevMarkers[document, currentLine, currentOffset, currentLength];
|
||||
if (marker != null) {
|
||||
prevWord.SyntaxColor = marker.Color;
|
||||
// document.Caret.ValidateCaretPos();
|
||||
// document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, document.GetLineNumberForOffset(document.Caret.Offset)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
pInd--;
|
||||
}
|
||||
}
|
||||
|
||||
if (inSpan) {
|
||||
HighlightColor c = null;
|
||||
bool hasDefaultColor = true;
|
||||
if (activeSpan.Rule == null) {
|
||||
c = activeSpan.Color;
|
||||
} else {
|
||||
c = GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength);
|
||||
hasDefaultColor = false;
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
c = activeSpan.Color;
|
||||
if (c.Color == Color.Transparent) {
|
||||
c = this.DefaultTextColor;
|
||||
}
|
||||
hasDefaultColor = true;
|
||||
}
|
||||
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, markNext != null ? markNext : c, hasDefaultColor));
|
||||
} else {
|
||||
HighlightColor c = markNext != null ? markNext : GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength);
|
||||
if (c == null) {
|
||||
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, this.DefaultTextColor, true) {
|
||||
IsDelimiter = isDelimiter
|
||||
});
|
||||
} else {
|
||||
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, c, false)
|
||||
{
|
||||
IsDelimiter = isDelimiter
|
||||
});
|
||||
}
|
||||
isDelimiter = false;
|
||||
|
||||
}
|
||||
|
||||
if (activeRuleSet != null) {
|
||||
NextMarker nextMarker = (NextMarker)activeRuleSet.NextMarkers[document, currentLine, currentOffset, currentLength];
|
||||
if (nextMarker != null) {
|
||||
if (nextMarker.MarkMarker && words.Count > 0) {
|
||||
TextWord prevword = ((TextWord)words[words.Count - 1]);
|
||||
prevword.SyntaxColor = nextMarker.Color;
|
||||
}
|
||||
markNext = nextMarker.Color;
|
||||
} else {
|
||||
markNext = null;
|
||||
}
|
||||
}
|
||||
currentOffset += currentLength;
|
||||
currentLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#region Matching
|
||||
/// <summary>
|
||||
/// get the string, which matches the regular expression expr,
|
||||
/// in string s2 at index
|
||||
/// </summary>
|
||||
static string GetRegString(LineSegment lineSegment, char[] expr, int index, IDocument document)
|
||||
{
|
||||
int j = 0;
|
||||
StringBuilder regexpr = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < expr.Length; ++i, ++j) {
|
||||
if (index + j >= lineSegment.Length)
|
||||
break;
|
||||
|
||||
switch (expr[i]) {
|
||||
case '@': // "special" meaning
|
||||
++i;
|
||||
if (i == expr.Length)
|
||||
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @.");
|
||||
switch (expr[i]) {
|
||||
case '!': // don't match the following expression
|
||||
StringBuilder whatmatch = new StringBuilder();
|
||||
++i;
|
||||
while (i < expr.Length && expr[i] != '@') {
|
||||
whatmatch.Append(expr[i++]);
|
||||
}
|
||||
break;
|
||||
case '@': // matches @
|
||||
regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (expr[i] != document.GetCharAt(lineSegment.Offset + index + j)) {
|
||||
return regexpr.ToString();
|
||||
}
|
||||
regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return regexpr.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns true, if the get the string s2 at index matches the expression expr
|
||||
/// </summary>
|
||||
static bool MatchExpr(LineSegment lineSegment, char[] expr, int index, IDocument document, bool ignoreCase)
|
||||
{
|
||||
for (int i = 0, j = 0; i < expr.Length; ++i, ++j) {
|
||||
switch (expr[i]) {
|
||||
case '@': // "special" meaning
|
||||
++i;
|
||||
if (i == expr.Length)
|
||||
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @.");
|
||||
switch (expr[i]) {
|
||||
case 'C': // match whitespace or punctuation
|
||||
if (index + j == lineSegment.Offset || index + j >= lineSegment.Offset + lineSegment.Length) {
|
||||
// nothing (EOL or SOL)
|
||||
} else {
|
||||
char ch = document.GetCharAt(lineSegment.Offset + index + j);
|
||||
if (!char.IsWhiteSpace(ch) && !char.IsPunctuation(ch)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '!': // don't match the following expression
|
||||
{
|
||||
StringBuilder whatmatch = new StringBuilder();
|
||||
++i;
|
||||
while (i < expr.Length && expr[i] != '@') {
|
||||
whatmatch.Append(expr[i++]);
|
||||
}
|
||||
if (lineSegment.Offset + index + j + whatmatch.Length < document.TextLength) {
|
||||
int k = 0;
|
||||
for (; k < whatmatch.Length; ++k) {
|
||||
char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j + k)) : document.GetCharAt(lineSegment.Offset + index + j + k);
|
||||
char spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k];
|
||||
if (docChar != spanChar) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (k >= whatmatch.Length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// --j;
|
||||
break;
|
||||
}
|
||||
case '-': // don't match the expression before
|
||||
{
|
||||
StringBuilder whatmatch = new StringBuilder();
|
||||
++i;
|
||||
while (i < expr.Length && expr[i] != '@') {
|
||||
whatmatch.Append(expr[i++]);
|
||||
}
|
||||
if (index - whatmatch.Length >= 0) {
|
||||
int k = 0;
|
||||
for (; k < whatmatch.Length; ++k) {
|
||||
char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k)) : document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k);
|
||||
char spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k];
|
||||
if (docChar != spanChar)
|
||||
break;
|
||||
}
|
||||
if (k >= whatmatch.Length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// --j;
|
||||
break;
|
||||
}
|
||||
case '@': // matches @
|
||||
if (index + j >= lineSegment.Length || '@' != document.GetCharAt(lineSegment.Offset + index + j)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (index + j >= lineSegment.Length) {
|
||||
return false;
|
||||
}
|
||||
char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j)) : document.GetCharAt(lineSegment.Offset + index + j);
|
||||
char spanChar = ignoreCase ? char.ToUpperInvariant(expr[i]) : expr[i];
|
||||
if (docChar != spanChar) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to generate bold, italic and bold/italic fonts out
|
||||
/// of a base font.
|
||||
/// </summary>
|
||||
public class FontContainer
|
||||
{
|
||||
Font defaultFont;
|
||||
Font regularfont, boldfont, italicfont, bolditalicfont;
|
||||
|
||||
/// <value>
|
||||
/// The scaled, regular version of the base font
|
||||
/// </value>
|
||||
public Font RegularFont {
|
||||
get {
|
||||
return regularfont;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The scaled, bold version of the base font
|
||||
/// </value>
|
||||
public Font BoldFont {
|
||||
get {
|
||||
return boldfont;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The scaled, italic version of the base font
|
||||
/// </value>
|
||||
public Font ItalicFont {
|
||||
get {
|
||||
return italicfont;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The scaled, bold/italic version of the base font
|
||||
/// </value>
|
||||
public Font BoldItalicFont {
|
||||
get {
|
||||
return bolditalicfont;
|
||||
}
|
||||
}
|
||||
|
||||
static float twipsPerPixelY;
|
||||
|
||||
public static float TwipsPerPixelY {
|
||||
get {
|
||||
if (twipsPerPixelY == 0) {
|
||||
using (Bitmap bmp = new Bitmap(1,1)) {
|
||||
using (Graphics g = Graphics.FromImage(bmp)) {
|
||||
twipsPerPixelY = 1440 / g.DpiY;
|
||||
}
|
||||
}
|
||||
}
|
||||
return twipsPerPixelY;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The base font
|
||||
/// </value>
|
||||
public Font DefaultFont {
|
||||
get {
|
||||
return defaultFont;
|
||||
}
|
||||
set {
|
||||
// 1440 twips is one inch
|
||||
float pixelSize = (float)Math.Round(value.SizeInPoints * 20 / TwipsPerPixelY);
|
||||
|
||||
defaultFont = value;
|
||||
regularfont = new Font(value.FontFamily, pixelSize * TwipsPerPixelY / 20f, FontStyle.Regular);
|
||||
boldfont = new Font(regularfont, FontStyle.Bold);
|
||||
italicfont = new Font(regularfont, FontStyle.Italic);
|
||||
bolditalicfont = new Font(regularfont, FontStyle.Bold | FontStyle.Italic);
|
||||
}
|
||||
}
|
||||
|
||||
public static Font ParseFont(string font)
|
||||
{
|
||||
string[] descr = font.Split(new char[]{',', '='});
|
||||
return new Font(descr[1], float.Parse(descr[3]));
|
||||
}
|
||||
|
||||
public FontContainer(Font defaultFont)
|
||||
{
|
||||
this.DefaultFont = defaultFont;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Extens the highlighting color with a background image.
|
||||
/// </summary>
|
||||
public class HighlightBackground : HighlightColor
|
||||
{
|
||||
Image backgroundImage;
|
||||
|
||||
/// <value>
|
||||
/// The image used as background
|
||||
/// </value>
|
||||
public Image BackgroundImage {
|
||||
get {
|
||||
return backgroundImage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightBackground"/>
|
||||
/// </summary>
|
||||
public HighlightBackground(XmlElement el) : base(el)
|
||||
{
|
||||
if (el.Attributes["image"] != null) {
|
||||
backgroundImage = new Bitmap(el.Attributes["image"].InnerText);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightBackground"/>
|
||||
/// </summary>
|
||||
public HighlightBackground(Color color, Color backgroundcolor, bool bold, bool italic) : base(color, backgroundcolor, bold, italic)
|
||||
{
|
||||
}
|
||||
|
||||
public HighlightBackground(string systemColor, string systemBackgroundColor, bool bold, bool italic) : base(systemColor, systemBackgroundColor, bold, italic)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// A color used for highlighting
|
||||
/// </summary>
|
||||
public class HighlightColor
|
||||
{
|
||||
Color color;
|
||||
Color backgroundcolor = System.Drawing.Color.WhiteSmoke;
|
||||
|
||||
bool bold = false;
|
||||
bool italic = false;
|
||||
bool hasForeground = false;
|
||||
bool hasBackground = false;
|
||||
|
||||
public bool HasForeground {
|
||||
get {
|
||||
return hasForeground;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasBackground {
|
||||
get {
|
||||
return hasBackground;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <value>
|
||||
/// If true the font will be displayed bold style
|
||||
/// </value>
|
||||
public bool Bold {
|
||||
get {
|
||||
return bold;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// If true the font will be displayed italic style
|
||||
/// </value>
|
||||
public bool Italic {
|
||||
get {
|
||||
return italic;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The background color used
|
||||
/// </value>
|
||||
public Color BackgroundColor {
|
||||
get {
|
||||
return backgroundcolor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The foreground color used
|
||||
/// </value>
|
||||
public Color Color {
|
||||
get {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The font used
|
||||
/// </value>
|
||||
public Font GetFont(FontContainer fontContainer)
|
||||
{
|
||||
if (Bold) {
|
||||
return Italic ? fontContainer.BoldItalicFont : fontContainer.BoldFont;
|
||||
}
|
||||
return Italic ? fontContainer.ItalicFont : fontContainer.RegularFont;
|
||||
}
|
||||
|
||||
Color ParseColorString(string colorName)
|
||||
{
|
||||
string[] cNames = colorName.Split('*');
|
||||
PropertyInfo myPropInfo = typeof(System.Drawing.SystemColors).GetProperty(cNames[0], BindingFlags.Public |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static);
|
||||
Color c = (Color)myPropInfo.GetValue(null, null);
|
||||
|
||||
if (cNames.Length == 2) {
|
||||
// hack : can't figure out how to parse doubles with '.' (culture info might set the '.' to ',')
|
||||
double factor = double.Parse(cNames[1]) / 100;
|
||||
c = Color.FromArgb((int)((double)c.R * factor), (int)((double)c.G * factor), (int)((double)c.B * factor));
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightColor"/>
|
||||
/// </summary>
|
||||
public HighlightColor(XmlElement el)
|
||||
{
|
||||
Debug.Assert(el != null, "ICSharpCode.TextEditor.Document.SyntaxColor(XmlElement el) : el == null");
|
||||
if (el.Attributes["bold"] != null) {
|
||||
bold = bool.Parse(el.Attributes["bold"].InnerText);
|
||||
}
|
||||
|
||||
if (el.Attributes["italic"] != null) {
|
||||
italic = bool.Parse(el.Attributes["italic"].InnerText);
|
||||
}
|
||||
|
||||
if (el.Attributes["color"] != null) {
|
||||
string c = el.Attributes["color"].InnerText;
|
||||
if (c[0] == '#') {
|
||||
color = ParseColor(c);
|
||||
} else if (c.StartsWith("SystemColors.")) {
|
||||
color = ParseColorString(c.Substring("SystemColors.".Length));
|
||||
} else {
|
||||
color = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
|
||||
}
|
||||
hasForeground = true;
|
||||
} else {
|
||||
color = Color.Transparent; // to set it to the default value.
|
||||
}
|
||||
|
||||
if (el.Attributes["bgcolor"] != null) {
|
||||
string c = el.Attributes["bgcolor"].InnerText;
|
||||
if (c[0] == '#') {
|
||||
backgroundcolor = ParseColor(c);
|
||||
} else if (c.StartsWith("SystemColors.")) {
|
||||
backgroundcolor = ParseColorString(c.Substring("SystemColors.".Length));
|
||||
} else {
|
||||
backgroundcolor = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
|
||||
}
|
||||
hasBackground = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightColor"/>
|
||||
/// </summary>
|
||||
public HighlightColor(XmlElement el, HighlightColor defaultColor)
|
||||
{
|
||||
Debug.Assert(el != null, "ICSharpCode.TextEditor.Document.SyntaxColor(XmlElement el) : el == null");
|
||||
if (el.Attributes["bold"] != null) {
|
||||
bold = bool.Parse(el.Attributes["bold"].InnerText);
|
||||
} else {
|
||||
bold = defaultColor.Bold;
|
||||
}
|
||||
|
||||
if (el.Attributes["italic"] != null) {
|
||||
italic = bool.Parse(el.Attributes["italic"].InnerText);
|
||||
} else {
|
||||
italic = defaultColor.Italic;
|
||||
}
|
||||
|
||||
if (el.Attributes["color"] != null) {
|
||||
string c = el.Attributes["color"].InnerText;
|
||||
if (c[0] == '#') {
|
||||
color = ParseColor(c);
|
||||
} else if (c.StartsWith("SystemColors.")) {
|
||||
color = ParseColorString(c.Substring("SystemColors.".Length));
|
||||
} else {
|
||||
color = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
|
||||
}
|
||||
hasForeground = true;
|
||||
} else {
|
||||
color = defaultColor.color;
|
||||
}
|
||||
|
||||
if (el.Attributes["bgcolor"] != null) {
|
||||
string c = el.Attributes["bgcolor"].InnerText;
|
||||
if (c[0] == '#') {
|
||||
backgroundcolor = ParseColor(c);
|
||||
} else if (c.StartsWith("SystemColors.")) {
|
||||
backgroundcolor = ParseColorString(c.Substring("SystemColors.".Length));
|
||||
} else {
|
||||
backgroundcolor = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]);
|
||||
}
|
||||
hasBackground = true;
|
||||
} else {
|
||||
backgroundcolor = defaultColor.BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightColor"/>
|
||||
/// </summary>
|
||||
public HighlightColor(Color color, bool bold, bool italic)
|
||||
{
|
||||
hasForeground = true;
|
||||
this.color = color;
|
||||
this.bold = bold;
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightColor"/>
|
||||
/// </summary>
|
||||
public HighlightColor(Color color, Color backgroundcolor, bool bold, bool italic)
|
||||
{
|
||||
hasForeground = true;
|
||||
hasBackground = true;
|
||||
this.color = color;
|
||||
this.backgroundcolor = backgroundcolor;
|
||||
this.bold = bold;
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightColor"/>
|
||||
/// </summary>
|
||||
public HighlightColor(string systemColor, string systemBackgroundColor, bool bold, bool italic)
|
||||
{
|
||||
hasForeground = true;
|
||||
hasBackground = true;
|
||||
|
||||
this.color = ParseColorString(systemColor);
|
||||
this.backgroundcolor = ParseColorString(systemBackgroundColor);
|
||||
|
||||
this.bold = bold;
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HighlightColor"/>
|
||||
/// </summary>
|
||||
public HighlightColor(string systemColor, bool bold, bool italic)
|
||||
{
|
||||
hasForeground = true;
|
||||
|
||||
this.color = ParseColorString(systemColor);
|
||||
|
||||
this.bold = bold;
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
static Color ParseColor(string c)
|
||||
{
|
||||
int a = 255;
|
||||
int offset = 0;
|
||||
if (c.Length > 7) {
|
||||
offset = 2;
|
||||
a = int.Parse(c.Substring(1,2), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
int r = int.Parse(c.Substring(1 + offset,2), NumberStyles.HexNumber);
|
||||
int g = int.Parse(c.Substring(3 + offset,2), NumberStyles.HexNumber);
|
||||
int b = int.Parse(c.Substring(5 + offset,2), NumberStyles.HexNumber);
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="HighlightColor"/> instance to string (for debug purposes)
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[HighlightColor: Bold = " + Bold +
|
||||
", Italic = " + Italic +
|
||||
", Color = " + Color +
|
||||
", BackgroundColor = " + BackgroundColor + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class HighlightInfo
|
||||
{
|
||||
public bool BlockSpanOn = false;
|
||||
public bool Span = false;
|
||||
public Span CurSpan = null;
|
||||
|
||||
public HighlightInfo(Span curSpan, bool span, bool blockSpanOn)
|
||||
{
|
||||
this.CurSpan = curSpan;
|
||||
this.Span = span;
|
||||
this.BlockSpanOn = blockSpanOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Xml;
|
||||
|
||||
using ICSharpCode.TextEditor.Util;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class HighlightRuleSet
|
||||
{
|
||||
LookupTable keyWords;
|
||||
ArrayList spans = new ArrayList();
|
||||
LookupTable prevMarkers;
|
||||
LookupTable nextMarkers;
|
||||
char escapeCharacter;
|
||||
|
||||
bool ignoreCase = false;
|
||||
string name = null;
|
||||
|
||||
bool[] delimiters = new bool[256];
|
||||
|
||||
string reference = null;
|
||||
|
||||
public ArrayList Spans {
|
||||
get {
|
||||
return spans;
|
||||
}
|
||||
}
|
||||
|
||||
internal IHighlightingStrategyUsingRuleSets Highlighter;
|
||||
|
||||
public LookupTable KeyWords {
|
||||
get {
|
||||
return keyWords;
|
||||
}
|
||||
}
|
||||
|
||||
public LookupTable PrevMarkers {
|
||||
get {
|
||||
return prevMarkers;
|
||||
}
|
||||
}
|
||||
|
||||
public LookupTable NextMarkers {
|
||||
get {
|
||||
return nextMarkers;
|
||||
}
|
||||
}
|
||||
|
||||
public bool[] Delimiters {
|
||||
get {
|
||||
return delimiters;
|
||||
}
|
||||
}
|
||||
|
||||
public char EscapeCharacter {
|
||||
get {
|
||||
return escapeCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IgnoreCase {
|
||||
get {
|
||||
return ignoreCase;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name {
|
||||
get {
|
||||
return name;
|
||||
}
|
||||
set {
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Reference {
|
||||
get {
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightRuleSet()
|
||||
{
|
||||
keyWords = new LookupTable(false);
|
||||
prevMarkers = new LookupTable(false);
|
||||
nextMarkers = new LookupTable(false);
|
||||
}
|
||||
|
||||
public HighlightRuleSet(XmlElement el)
|
||||
{
|
||||
XmlNodeList nodes;
|
||||
|
||||
if (el.Attributes["name"] != null) {
|
||||
Name = el.Attributes["name"].InnerText;
|
||||
}
|
||||
|
||||
if (el.HasAttribute("escapecharacter")) {
|
||||
escapeCharacter = el.GetAttribute("escapecharacter")[0];
|
||||
}
|
||||
|
||||
if (el.Attributes["reference"] != null) {
|
||||
reference = el.Attributes["reference"].InnerText;
|
||||
}
|
||||
|
||||
if (el.Attributes["ignorecase"] != null) {
|
||||
ignoreCase = bool.Parse(el.Attributes["ignorecase"].InnerText);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Delimiters.Length; ++i) {
|
||||
delimiters[i] = false;
|
||||
}
|
||||
|
||||
if (el["Delimiters"] != null) {
|
||||
string delimiterString = el["Delimiters"].InnerText;
|
||||
foreach (char ch in delimiterString) {
|
||||
delimiters[(int)ch] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Spans = new LookupTable(!IgnoreCase);
|
||||
|
||||
keyWords = new LookupTable(!IgnoreCase);
|
||||
prevMarkers = new LookupTable(!IgnoreCase);
|
||||
nextMarkers = new LookupTable(!IgnoreCase);
|
||||
|
||||
nodes = el.GetElementsByTagName("KeyWords");
|
||||
foreach (XmlElement el2 in nodes) {
|
||||
HighlightColor color = new HighlightColor(el2);
|
||||
|
||||
XmlNodeList keys = el2.GetElementsByTagName("Key");
|
||||
foreach (XmlElement node in keys) {
|
||||
keyWords[node.Attributes["word"].InnerText] = color;
|
||||
}
|
||||
}
|
||||
|
||||
nodes = el.GetElementsByTagName("Span");
|
||||
foreach (XmlElement el2 in nodes) {
|
||||
Spans.Add(new Span(el2));
|
||||
/*
|
||||
Span span = new Span(el2);
|
||||
Spans[span.Begin] = span;*/
|
||||
}
|
||||
|
||||
nodes = el.GetElementsByTagName("MarkPrevious");
|
||||
foreach (XmlElement el2 in nodes) {
|
||||
PrevMarker prev = new PrevMarker(el2);
|
||||
prevMarkers[prev.What] = prev;
|
||||
}
|
||||
|
||||
nodes = el.GetElementsByTagName("MarkFollowing");
|
||||
foreach (XmlElement el2 in nodes) {
|
||||
NextMarker next = new NextMarker(el2);
|
||||
nextMarkers[next.What] = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges spans etc. from the other rule set into this rule set.
|
||||
/// </summary>
|
||||
public void MergeFrom(HighlightRuleSet ruleSet)
|
||||
{
|
||||
for (int i = 0; i < delimiters.Length; i++) {
|
||||
delimiters[i] |= ruleSet.delimiters[i];
|
||||
}
|
||||
// insert merged spans in front of old spans
|
||||
ArrayList oldSpans = spans;
|
||||
spans = (ArrayList)ruleSet.spans.Clone();
|
||||
spans.AddRange(oldSpans);
|
||||
//keyWords.MergeFrom(ruleSet.keyWords);
|
||||
//prevMarkers.MergeFrom(ruleSet.prevMarkers);
|
||||
//nextMarkers.MergeFrom(ruleSet.nextMarkers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
[Serializable()]
|
||||
public class HighlightingColorNotFoundException : Exception
|
||||
{
|
||||
public HighlightingColorNotFoundException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public HighlightingColorNotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public HighlightingColorNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected HighlightingColorNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the highlighting definition that was tried to load was invalid.
|
||||
/// You get this exception only once per highlighting definition, after that the definition
|
||||
/// is replaced with the default highlighter.
|
||||
/// </summary>
|
||||
[Serializable()]
|
||||
public class HighlightingDefinitionInvalidException : Exception
|
||||
{
|
||||
public HighlightingDefinitionInvalidException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public HighlightingDefinitionInvalidException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public HighlightingDefinitionInvalidException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected HighlightingDefinitionInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public static class HighlightingDefinitionParser
|
||||
{
|
||||
public static DefaultHighlightingStrategy Parse(SyntaxMode syntaxMode, XmlReader xmlReader)
|
||||
{
|
||||
return Parse(null, syntaxMode, xmlReader);
|
||||
}
|
||||
|
||||
public static DefaultHighlightingStrategy Parse(DefaultHighlightingStrategy highlighter, SyntaxMode syntaxMode, XmlReader xmlReader)
|
||||
{
|
||||
if (syntaxMode == null)
|
||||
throw new ArgumentNullException("syntaxMode");
|
||||
if (xmlReader == null)
|
||||
throw new ArgumentNullException("xmlTextReader");
|
||||
try {
|
||||
List<ValidationEventArgs> errors = null;
|
||||
XmlReaderSettings settings = new XmlReaderSettings();
|
||||
Stream shemaStream = typeof(HighlightingDefinitionParser).Assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources.Mode.xsd");
|
||||
settings.Schemas.Add("", new XmlTextReader(shemaStream));
|
||||
settings.Schemas.ValidationEventHandler += delegate(object sender, ValidationEventArgs args) {
|
||||
if (errors == null) {
|
||||
errors = new List<ValidationEventArgs>();
|
||||
}
|
||||
errors.Add(args);
|
||||
};
|
||||
settings.ValidationType = ValidationType.Schema;
|
||||
XmlReader validatingReader = XmlReader.Create(xmlReader, settings);
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(validatingReader);
|
||||
|
||||
if (highlighter == null)
|
||||
highlighter = new DefaultHighlightingStrategy(doc.DocumentElement.Attributes["name"].InnerText);
|
||||
|
||||
if (doc.DocumentElement.HasAttribute("extends")) {
|
||||
KeyValuePair<SyntaxMode, ISyntaxModeFileProvider> entry = HighlightingManager.Manager.FindHighlighterEntry(doc.DocumentElement.GetAttribute("extends"));
|
||||
if (entry.Key == null) {
|
||||
throw new HighlightingDefinitionInvalidException("Cannot find referenced highlighting source " + doc.DocumentElement.GetAttribute("extends"));
|
||||
} else {
|
||||
highlighter = Parse(highlighter, entry.Key, entry.Value.GetSyntaxModeFile(entry.Key));
|
||||
if (highlighter == null) return null;
|
||||
}
|
||||
}
|
||||
if (doc.DocumentElement.HasAttribute("extensions")) {
|
||||
highlighter.Extensions = doc.DocumentElement.GetAttribute("extensions").Split(new char[] { ';', '|' });
|
||||
}
|
||||
|
||||
XmlElement environment = doc.DocumentElement["Environment"];
|
||||
if (environment != null) {
|
||||
foreach (XmlNode node in environment.ChildNodes) {
|
||||
if (node is XmlElement) {
|
||||
XmlElement el = (XmlElement)node;
|
||||
if (el.Name == "Custom") {
|
||||
highlighter.SetColorFor(el.GetAttribute("name"), el.HasAttribute("bgcolor") ? new HighlightBackground(el) : new HighlightColor(el));
|
||||
} else {
|
||||
highlighter.SetColorFor(el.Name, el.HasAttribute("bgcolor") ? new HighlightBackground(el) : new HighlightColor(el));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse properties
|
||||
if (doc.DocumentElement["Properties"]!= null) {
|
||||
foreach (XmlElement propertyElement in doc.DocumentElement["Properties"].ChildNodes) {
|
||||
highlighter.Properties[propertyElement.Attributes["name"].InnerText] = propertyElement.Attributes["value"].InnerText;
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.DocumentElement["Digits"]!= null) {
|
||||
highlighter.DigitColor = new HighlightColor(doc.DocumentElement["Digits"]);
|
||||
}
|
||||
|
||||
XmlNodeList nodes = doc.DocumentElement.GetElementsByTagName("RuleSet");
|
||||
foreach (XmlElement element in nodes) {
|
||||
highlighter.AddRuleSet(new HighlightRuleSet(element));
|
||||
}
|
||||
|
||||
xmlReader.Close();
|
||||
|
||||
if (errors != null) {
|
||||
StringBuilder msg = new StringBuilder();
|
||||
foreach (ValidationEventArgs args in errors) {
|
||||
msg.AppendLine(args.Message);
|
||||
}
|
||||
throw new HighlightingDefinitionInvalidException(msg.ToString());
|
||||
} else {
|
||||
return highlighter;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new HighlightingDefinitionInvalidException("Could not load mode definition file '" + syntaxMode.FileName + "'.\n", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class HighlightingManager
|
||||
{
|
||||
ArrayList syntaxModeFileProviders = new ArrayList();
|
||||
static HighlightingManager highlightingManager;
|
||||
|
||||
// hash table from extension name to highlighting definition,
|
||||
// OR from extension name to Pair SyntaxMode,ISyntaxModeFileProvider
|
||||
Hashtable highlightingDefs = new Hashtable();
|
||||
|
||||
Hashtable extensionsToName = new Hashtable();
|
||||
|
||||
public Hashtable HighlightingDefinitions {
|
||||
get {
|
||||
return highlightingDefs;
|
||||
}
|
||||
}
|
||||
|
||||
public static HighlightingManager Manager {
|
||||
get {
|
||||
return highlightingManager;
|
||||
}
|
||||
}
|
||||
|
||||
static HighlightingManager()
|
||||
{
|
||||
highlightingManager = new HighlightingManager();
|
||||
highlightingManager.AddSyntaxModeFileProvider(new ResourceSyntaxModeProvider());
|
||||
}
|
||||
|
||||
public HighlightingManager()
|
||||
{
|
||||
CreateDefaultHighlightingStrategy();
|
||||
}
|
||||
|
||||
public void AddSyntaxModeFileProvider(ISyntaxModeFileProvider syntaxModeFileProvider)
|
||||
{
|
||||
foreach (SyntaxMode syntaxMode in syntaxModeFileProvider.SyntaxModes) {
|
||||
highlightingDefs[syntaxMode.Name] = new DictionaryEntry(syntaxMode, syntaxModeFileProvider);
|
||||
foreach (string extension in syntaxMode.Extensions) {
|
||||
extensionsToName[extension.ToUpperInvariant()] = syntaxMode.Name;
|
||||
}
|
||||
}
|
||||
if (!syntaxModeFileProviders.Contains(syntaxModeFileProvider)) {
|
||||
syntaxModeFileProviders.Add(syntaxModeFileProvider);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHighlightingStrategy(IHighlightingStrategy highlightingStrategy)
|
||||
{
|
||||
highlightingDefs[highlightingStrategy.Name] = highlightingStrategy;
|
||||
foreach (string extension in highlightingStrategy.Extensions)
|
||||
{
|
||||
extensionsToName[extension.ToUpperInvariant()] = highlightingStrategy.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadSyntaxModes()
|
||||
{
|
||||
highlightingDefs.Clear();
|
||||
extensionsToName.Clear();
|
||||
CreateDefaultHighlightingStrategy();
|
||||
foreach (ISyntaxModeFileProvider provider in syntaxModeFileProviders) {
|
||||
provider.UpdateSyntaxModeList();
|
||||
AddSyntaxModeFileProvider(provider);
|
||||
}
|
||||
OnReloadSyntaxHighlighting(EventArgs.Empty);
|
||||
}
|
||||
|
||||
void CreateDefaultHighlightingStrategy()
|
||||
{
|
||||
DefaultHighlightingStrategy defaultHighlightingStrategy = new DefaultHighlightingStrategy();
|
||||
defaultHighlightingStrategy.Extensions = new string[] {};
|
||||
defaultHighlightingStrategy.Rules.Add(new HighlightRuleSet());
|
||||
highlightingDefs["Default"] = defaultHighlightingStrategy;
|
||||
}
|
||||
|
||||
IHighlightingStrategy LoadDefinition(DictionaryEntry entry)
|
||||
{
|
||||
SyntaxMode syntaxMode = (SyntaxMode)entry.Key;
|
||||
ISyntaxModeFileProvider syntaxModeFileProvider = (ISyntaxModeFileProvider)entry.Value;
|
||||
|
||||
DefaultHighlightingStrategy highlightingStrategy = null;
|
||||
try {
|
||||
var reader = syntaxModeFileProvider.GetSyntaxModeFile(syntaxMode);
|
||||
if (reader == null)
|
||||
throw new HighlightingDefinitionInvalidException("Could not get syntax mode file for " + syntaxMode.Name);
|
||||
highlightingStrategy = HighlightingDefinitionParser.Parse(syntaxMode, reader);
|
||||
if (highlightingStrategy.Name != syntaxMode.Name) {
|
||||
throw new HighlightingDefinitionInvalidException("The name specified in the .xshd '" + highlightingStrategy.Name + "' must be equal the syntax mode name '" + syntaxMode.Name + "'");
|
||||
}
|
||||
} finally {
|
||||
if (highlightingStrategy == null) {
|
||||
highlightingStrategy = DefaultHighlighting;
|
||||
}
|
||||
highlightingDefs[syntaxMode.Name] = highlightingStrategy;
|
||||
highlightingStrategy.ResolveReferences();
|
||||
}
|
||||
return highlightingStrategy;
|
||||
}
|
||||
|
||||
public DefaultHighlightingStrategy DefaultHighlighting {
|
||||
get {
|
||||
return (DefaultHighlightingStrategy)highlightingDefs["Default"];
|
||||
}
|
||||
}
|
||||
|
||||
internal KeyValuePair<SyntaxMode, ISyntaxModeFileProvider> FindHighlighterEntry(string name)
|
||||
{
|
||||
foreach (ISyntaxModeFileProvider provider in syntaxModeFileProviders) {
|
||||
foreach (SyntaxMode mode in provider.SyntaxModes) {
|
||||
if (mode.Name == name) {
|
||||
return new KeyValuePair<SyntaxMode, ISyntaxModeFileProvider>(mode, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new KeyValuePair<SyntaxMode, ISyntaxModeFileProvider>(null, null);
|
||||
}
|
||||
|
||||
public IHighlightingStrategy FindHighlighter(string name)
|
||||
{
|
||||
object def = highlightingDefs[name];
|
||||
if (def is DictionaryEntry) {
|
||||
return LoadDefinition((DictionaryEntry)def);
|
||||
}
|
||||
return def == null ? DefaultHighlighting : (IHighlightingStrategy)def;
|
||||
}
|
||||
|
||||
public IHighlightingStrategy FindHighlighterForFile(string fileName)
|
||||
{
|
||||
string highlighterName = (string)extensionsToName[Path.GetExtension(fileName).ToUpperInvariant()];
|
||||
if (highlighterName != null) {
|
||||
object def = highlightingDefs[highlighterName];
|
||||
if (def is DictionaryEntry) {
|
||||
return LoadDefinition((DictionaryEntry)def);
|
||||
}
|
||||
return def == null ? DefaultHighlighting : (IHighlightingStrategy)def;
|
||||
} else {
|
||||
return DefaultHighlighting;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnReloadSyntaxHighlighting(EventArgs e)
|
||||
{
|
||||
if (ReloadSyntaxHighlighting != null) {
|
||||
ReloadSyntaxHighlighting(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler ReloadSyntaxHighlighting;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class HighlightingStrategyFactory
|
||||
{
|
||||
public static IHighlightingStrategy CreateHighlightingStrategy()
|
||||
{
|
||||
return (IHighlightingStrategy)HighlightingManager.Manager.HighlightingDefinitions["Default"];
|
||||
}
|
||||
|
||||
public static IHighlightingStrategy CreateHighlightingStrategy(string name)
|
||||
{
|
||||
IHighlightingStrategy highlightingStrategy = HighlightingManager.Manager.FindHighlighter(name);
|
||||
|
||||
if (highlightingStrategy == null)
|
||||
{
|
||||
return CreateHighlightingStrategy();
|
||||
}
|
||||
return highlightingStrategy;
|
||||
}
|
||||
|
||||
public static IHighlightingStrategy CreateHighlightingStrategyForFile(string fileName)
|
||||
{
|
||||
IHighlightingStrategy highlightingStrategy = HighlightingManager.Manager.FindHighlighterForFile(fileName);
|
||||
if (highlightingStrategy == null)
|
||||
{
|
||||
return CreateHighlightingStrategy();
|
||||
}
|
||||
return highlightingStrategy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// A highlighting strategy for a buffer.
|
||||
/// </summary>
|
||||
public interface IHighlightingStrategy
|
||||
{
|
||||
/// <value>
|
||||
/// The name of the highlighting strategy, must be unique
|
||||
/// </value>
|
||||
string Name {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The file extenstions on which this highlighting strategy gets
|
||||
/// used
|
||||
/// </value>
|
||||
string[] Extensions {
|
||||
get;
|
||||
}
|
||||
|
||||
Dictionary<string, string> Properties {
|
||||
get;
|
||||
}
|
||||
|
||||
// returns special color. (BackGround Color, Cursor Color and so on)
|
||||
|
||||
/// <remarks>
|
||||
/// Gets the color of an Environment element.
|
||||
/// </remarks>
|
||||
HighlightColor GetColorFor(string name);
|
||||
|
||||
/// <remarks>
|
||||
/// Used internally, do not call
|
||||
/// </remarks>
|
||||
void MarkTokens(IDocument document, List<LineSegment> lines);
|
||||
|
||||
/// <remarks>
|
||||
/// Used internally, do not call
|
||||
/// </remarks>
|
||||
void MarkTokens(IDocument document);
|
||||
}
|
||||
|
||||
public interface IHighlightingStrategyUsingRuleSets : IHighlightingStrategy
|
||||
{
|
||||
/// <remarks>
|
||||
/// Used internally, do not call
|
||||
/// </remarks>
|
||||
HighlightRuleSet GetRuleSet(Span span);
|
||||
|
||||
/// <remarks>
|
||||
/// Used internally, do not call
|
||||
/// </remarks>
|
||||
HighlightColor GetColor(IDocument document, LineSegment keyWord, int index, int length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for mark next token
|
||||
/// </summary>
|
||||
public class NextMarker
|
||||
{
|
||||
string what;
|
||||
HighlightColor color;
|
||||
bool markMarker = false;
|
||||
|
||||
/// <value>
|
||||
/// String value to indicate to mark next token
|
||||
/// </value>
|
||||
public string What {
|
||||
get {
|
||||
return what;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Color for marking next token
|
||||
/// </value>
|
||||
public HighlightColor Color {
|
||||
get {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// If true the indication text will be marked with the same color
|
||||
/// too
|
||||
/// </value>
|
||||
public bool MarkMarker {
|
||||
get {
|
||||
return markMarker;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="NextMarker"/>
|
||||
/// </summary>
|
||||
public NextMarker(XmlElement mark)
|
||||
{
|
||||
color = new HighlightColor(mark);
|
||||
what = mark.InnerText;
|
||||
if (mark.Attributes["markmarker"] != null) {
|
||||
markMarker = bool.Parse(mark.Attributes["markmarker"].InnerText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for mark previous token
|
||||
/// </summary>
|
||||
public class PrevMarker
|
||||
{
|
||||
string what;
|
||||
HighlightColor color;
|
||||
bool markMarker = false;
|
||||
|
||||
/// <value>
|
||||
/// String value to indicate to mark previous token
|
||||
/// </value>
|
||||
public string What {
|
||||
get {
|
||||
return what;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Color for marking previous token
|
||||
/// </value>
|
||||
public HighlightColor Color {
|
||||
get {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// If true the indication text will be marked with the same color
|
||||
/// too
|
||||
/// </value>
|
||||
public bool MarkMarker {
|
||||
get {
|
||||
return markMarker;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="PrevMarker"/>
|
||||
/// </summary>
|
||||
public PrevMarker(XmlElement mark)
|
||||
{
|
||||
color = new HighlightColor(mark);
|
||||
what = mark.InnerText;
|
||||
if (mark.Attributes["markmarker"] != null) {
|
||||
markMarker = bool.Parse(mark.Attributes["markmarker"].InnerText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public sealed class Span
|
||||
{
|
||||
bool stopEOL;
|
||||
HighlightColor color;
|
||||
HighlightColor beginColor;
|
||||
HighlightColor endColor;
|
||||
char[] begin;
|
||||
char[] end;
|
||||
string name;
|
||||
string rule;
|
||||
HighlightRuleSet ruleSet;
|
||||
char escapeCharacter;
|
||||
bool ignoreCase;
|
||||
bool isBeginSingleWord;
|
||||
bool? isBeginStartOfLine;
|
||||
bool isEndSingleWord;
|
||||
|
||||
internal HighlightRuleSet RuleSet {
|
||||
get {
|
||||
return ruleSet;
|
||||
}
|
||||
set {
|
||||
ruleSet = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IgnoreCase {
|
||||
get {
|
||||
return ignoreCase;
|
||||
}
|
||||
set {
|
||||
ignoreCase = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool StopEOL {
|
||||
get {
|
||||
return stopEOL;
|
||||
}
|
||||
}
|
||||
|
||||
public bool? IsBeginStartOfLine {
|
||||
get {
|
||||
return isBeginStartOfLine;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBeginSingleWord {
|
||||
get {
|
||||
return isBeginSingleWord;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEndSingleWord {
|
||||
get {
|
||||
return isEndSingleWord;
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightColor Color {
|
||||
get {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightColor BeginColor {
|
||||
get {
|
||||
if(beginColor != null) {
|
||||
return beginColor;
|
||||
} else {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightColor EndColor {
|
||||
get {
|
||||
return endColor!=null ? endColor : color;
|
||||
}
|
||||
}
|
||||
|
||||
public char[] Begin {
|
||||
get { return begin; }
|
||||
}
|
||||
|
||||
public char[] End {
|
||||
get { return end; }
|
||||
}
|
||||
|
||||
public string Name {
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string Rule {
|
||||
get { return rule; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the escape character of the span. The escape character is a character that can be used in front
|
||||
/// of the span end to make it not end the span. The escape character followed by another escape character
|
||||
/// means the escape character was escaped like in @"a "" b" literals in C#.
|
||||
/// The default value '\0' means no escape character is allowed.
|
||||
/// </summary>
|
||||
public char EscapeCharacter {
|
||||
get { return escapeCharacter; }
|
||||
}
|
||||
|
||||
public Span(XmlElement span)
|
||||
{
|
||||
color = new HighlightColor(span);
|
||||
|
||||
if (span.HasAttribute("rule")) {
|
||||
rule = span.GetAttribute("rule");
|
||||
}
|
||||
|
||||
if (span.HasAttribute("escapecharacter")) {
|
||||
escapeCharacter = span.GetAttribute("escapecharacter")[0];
|
||||
}
|
||||
|
||||
name = span.GetAttribute("name");
|
||||
if (span.HasAttribute("stopateol")) {
|
||||
stopEOL = bool.Parse(span.GetAttribute("stopateol"));
|
||||
}
|
||||
|
||||
begin = span["Begin"].InnerText.ToCharArray();
|
||||
beginColor = new HighlightColor(span["Begin"], color);
|
||||
|
||||
if (span["Begin"].HasAttribute("singleword")) {
|
||||
this.isBeginSingleWord = bool.Parse(span["Begin"].GetAttribute("singleword"));
|
||||
}
|
||||
if (span["Begin"].HasAttribute("startofline")) {
|
||||
this.isBeginStartOfLine = bool.Parse(span["Begin"].GetAttribute("startofline"));
|
||||
}
|
||||
|
||||
if (span["End"] != null) {
|
||||
end = span["End"].InnerText.ToCharArray();
|
||||
endColor = new HighlightColor(span["End"], color);
|
||||
if (span["End"].HasAttribute("singleword")) {
|
||||
this.isEndSingleWord = bool.Parse(span["End"].GetAttribute("singleword"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// A stack of Span instances. Works like Stack<Span>, but can be cloned quickly
|
||||
/// because it is implemented as linked list.
|
||||
/// </summary>
|
||||
public sealed class SpanStack : ICloneable, IEnumerable<Span>
|
||||
{
|
||||
internal sealed class StackNode
|
||||
{
|
||||
public readonly StackNode Previous;
|
||||
public readonly Span Data;
|
||||
|
||||
public StackNode(StackNode previous, Span data)
|
||||
{
|
||||
this.Previous = previous;
|
||||
this.Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
StackNode top = null;
|
||||
|
||||
public Span Pop()
|
||||
{
|
||||
Span s = top.Data;
|
||||
top = top.Previous;
|
||||
return s;
|
||||
}
|
||||
|
||||
public Span Peek()
|
||||
{
|
||||
return top.Data;
|
||||
}
|
||||
|
||||
public void Push(Span s)
|
||||
{
|
||||
top = new StackNode(top, s);
|
||||
}
|
||||
|
||||
public bool IsEmpty {
|
||||
get {
|
||||
return top == null;
|
||||
}
|
||||
}
|
||||
|
||||
public SpanStack Clone()
|
||||
{
|
||||
SpanStack n = new SpanStack();
|
||||
n.top = this.top;
|
||||
return n;
|
||||
}
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return this.Clone();
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(new StackNode(top, null));
|
||||
}
|
||||
IEnumerator<Span> IEnumerable<Span>.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<Span>
|
||||
{
|
||||
StackNode c;
|
||||
|
||||
internal Enumerator(StackNode node)
|
||||
{
|
||||
c = node;
|
||||
}
|
||||
|
||||
public Span Current {
|
||||
get {
|
||||
return c.Data;
|
||||
}
|
||||
}
|
||||
|
||||
object System.Collections.IEnumerator.Current {
|
||||
get {
|
||||
return c.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
c = null;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
c = c.Previous;
|
||||
return c != null;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class FileSyntaxModeProvider : ISyntaxModeFileProvider
|
||||
{
|
||||
string directory;
|
||||
List<SyntaxMode> syntaxModes = null;
|
||||
|
||||
public ICollection<SyntaxMode> SyntaxModes {
|
||||
get {
|
||||
return syntaxModes;
|
||||
}
|
||||
}
|
||||
|
||||
public FileSyntaxModeProvider(string directory)
|
||||
{
|
||||
this.directory = directory;
|
||||
UpdateSyntaxModeList();
|
||||
}
|
||||
|
||||
public void UpdateSyntaxModeList()
|
||||
{
|
||||
string syntaxModeFile = Path.Combine(directory, "SyntaxModes.xml");
|
||||
if (File.Exists(syntaxModeFile)) {
|
||||
Stream s = File.OpenRead(syntaxModeFile);
|
||||
syntaxModes = SyntaxMode.GetSyntaxModes(s);
|
||||
s.Close();
|
||||
} else {
|
||||
syntaxModes = ScanDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)
|
||||
{
|
||||
string syntaxModeFile = Path.Combine(directory, syntaxMode.FileName);
|
||||
if (!File.Exists(syntaxModeFile)) {
|
||||
throw new HighlightingDefinitionInvalidException("Can't load highlighting definition " + syntaxModeFile + " (file not found)!");
|
||||
}
|
||||
return new XmlTextReader(File.OpenRead(syntaxModeFile));
|
||||
}
|
||||
|
||||
List<SyntaxMode> ScanDirectory(string directory)
|
||||
{
|
||||
string[] files = Directory.GetFiles(directory);
|
||||
List<SyntaxMode> modes = new List<SyntaxMode>();
|
||||
foreach (string file in files) {
|
||||
if (Path.GetExtension(file).Equals(".XSHD", StringComparison.OrdinalIgnoreCase)) {
|
||||
XmlTextReader reader = new XmlTextReader(file);
|
||||
while (reader.Read()) {
|
||||
if (reader.NodeType == XmlNodeType.Element) {
|
||||
switch (reader.Name) {
|
||||
case "SyntaxDefinition":
|
||||
string name = reader.GetAttribute("name");
|
||||
string extensions = reader.GetAttribute("extensions");
|
||||
modes.Add(new SyntaxMode(Path.GetFileName(file),
|
||||
name,
|
||||
extensions));
|
||||
goto bailout;
|
||||
default:
|
||||
throw new HighlightingDefinitionInvalidException("Unknown root node in syntax highlighting file :" + reader.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
bailout:
|
||||
reader.Close();
|
||||
|
||||
}
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public interface ISyntaxModeFileProvider
|
||||
{
|
||||
ICollection<SyntaxMode> SyntaxModes {
|
||||
get;
|
||||
}
|
||||
|
||||
XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode);
|
||||
void UpdateSyntaxModeList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class ResourceSyntaxModeProvider : ISyntaxModeFileProvider
|
||||
{
|
||||
List<SyntaxMode> syntaxModes = null;
|
||||
|
||||
public ICollection<SyntaxMode> SyntaxModes {
|
||||
get {
|
||||
return syntaxModes;
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceSyntaxModeProvider()
|
||||
{
|
||||
Assembly assembly = typeof(SyntaxMode).Assembly;
|
||||
Stream syntaxModeStream = assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources.SyntaxModes.xml");
|
||||
if (syntaxModeStream != null) {
|
||||
syntaxModes = SyntaxMode.GetSyntaxModes(syntaxModeStream);
|
||||
} else {
|
||||
syntaxModes = new List<SyntaxMode>();
|
||||
}
|
||||
}
|
||||
|
||||
public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)
|
||||
{
|
||||
Assembly assembly = typeof(SyntaxMode).Assembly;
|
||||
return new XmlTextReader(assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources." + syntaxMode.FileName));
|
||||
}
|
||||
|
||||
public void UpdateSyntaxModeList()
|
||||
{
|
||||
// resources don't change during runtime
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using ICSharpCode.TextEditor.Document;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Src.Document.HighlightingStrategy.SyntaxModes
|
||||
{
|
||||
public class ResourceSyntaxModeProviderEx : ISyntaxModeFileProvider
|
||||
{
|
||||
private const string ResourcesDir = "ICSharpCode.TextEditor.Resources.";
|
||||
|
||||
readonly List<SyntaxMode> _syntaxModes;
|
||||
|
||||
public ICollection<SyntaxMode> SyntaxModes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _syntaxModes;
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceSyntaxModeProviderEx()
|
||||
{
|
||||
var syntaxModeStream = GetSyntaxModeStream("SyntaxModesEx.xml");
|
||||
|
||||
_syntaxModes = syntaxModeStream != null ? SyntaxMode.GetSyntaxModes(syntaxModeStream) : new List<SyntaxMode>();
|
||||
}
|
||||
|
||||
public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)
|
||||
{
|
||||
var stream = GetSyntaxModeStream(syntaxMode.FileName);
|
||||
|
||||
return stream != null ? new XmlTextReader(stream) : null;
|
||||
}
|
||||
|
||||
public void UpdateSyntaxModeList()
|
||||
{
|
||||
// resources don't change during runtime
|
||||
}
|
||||
|
||||
private Stream GetSyntaxModeStream(string filename)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
return assembly.GetManifestResourceStream(string.Format("{0}{1}", ResourcesDir, filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class SyntaxMode
|
||||
{
|
||||
string fileName;
|
||||
string name;
|
||||
string[] extensions;
|
||||
|
||||
public string FileName {
|
||||
get {
|
||||
return fileName;
|
||||
}
|
||||
set {
|
||||
fileName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name {
|
||||
get {
|
||||
return name;
|
||||
}
|
||||
set {
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] Extensions {
|
||||
get {
|
||||
return extensions;
|
||||
}
|
||||
set {
|
||||
extensions = value;
|
||||
}
|
||||
}
|
||||
|
||||
public SyntaxMode(string fileName, string name, string extensions)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
this.name = name;
|
||||
this.extensions = extensions.Split(';', '|', ',');
|
||||
}
|
||||
|
||||
public SyntaxMode(string fileName, string name, string[] extensions)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
this.name = name;
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
public static List<SyntaxMode> GetSyntaxModes(Stream xmlSyntaxModeStream)
|
||||
{
|
||||
XmlTextReader reader = new XmlTextReader(xmlSyntaxModeStream);
|
||||
List<SyntaxMode> syntaxModes = new List<SyntaxMode>();
|
||||
while (reader.Read()) {
|
||||
switch (reader.NodeType) {
|
||||
case XmlNodeType.Element:
|
||||
switch (reader.Name) {
|
||||
case "SyntaxModes":
|
||||
string version = reader.GetAttribute("version");
|
||||
if (version != "1.0") {
|
||||
throw new HighlightingDefinitionInvalidException("Unknown syntax mode file defininition with version " + version);
|
||||
}
|
||||
break;
|
||||
case "Mode":
|
||||
syntaxModes.Add(new SyntaxMode(reader.GetAttribute("file"),
|
||||
reader.GetAttribute("name"),
|
||||
reader.GetAttribute("extensions")));
|
||||
break;
|
||||
default:
|
||||
throw new HighlightingDefinitionInvalidException("Unknown node in syntax mode file :" + reader.Name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
reader.Close();
|
||||
return syntaxModes;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[SyntaxMode: FileName={0}, Name={1}, Extensions=({2})]", fileName, name, string.Join(",", extensions));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public enum TextWordType {
|
||||
Word,
|
||||
Space,
|
||||
Tab
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents single words with color information, two special versions of a word are
|
||||
/// spaces and tabs.
|
||||
/// </summary>
|
||||
public class TextWord
|
||||
{
|
||||
HighlightColor color;
|
||||
LineSegment line;
|
||||
IDocument document;
|
||||
|
||||
|
||||
int offset;
|
||||
int length;
|
||||
|
||||
public sealed class SpaceTextWord : TextWord
|
||||
{
|
||||
public SpaceTextWord()
|
||||
{
|
||||
length = 1;
|
||||
}
|
||||
|
||||
public SpaceTextWord(HighlightColor color)
|
||||
{
|
||||
length = 1;
|
||||
base.SyntaxColor = color;
|
||||
}
|
||||
|
||||
public override Font GetFont(FontContainer fontContainer)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override TextWordType Type {
|
||||
get {
|
||||
return TextWordType.Space;
|
||||
}
|
||||
}
|
||||
public override bool IsWhiteSpace {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TabTextWord : TextWord
|
||||
{
|
||||
public TabTextWord()
|
||||
{
|
||||
length = 1;
|
||||
}
|
||||
public TabTextWord(HighlightColor color)
|
||||
{
|
||||
length = 1;
|
||||
base.SyntaxColor = color;
|
||||
}
|
||||
|
||||
public override Font GetFont(FontContainer fontContainer)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override TextWordType Type {
|
||||
get {
|
||||
return TextWordType.Tab;
|
||||
}
|
||||
}
|
||||
public override bool IsWhiteSpace {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TextWord spaceWord = new SpaceTextWord();
|
||||
static TextWord tabWord = new TabTextWord();
|
||||
|
||||
bool hasDefaultColor;
|
||||
|
||||
public static TextWord Space {
|
||||
get {
|
||||
return spaceWord;
|
||||
}
|
||||
}
|
||||
|
||||
public static TextWord Tab {
|
||||
get {
|
||||
return tabWord;
|
||||
}
|
||||
}
|
||||
|
||||
public int Offset {
|
||||
get {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the <paramref name="word"/> into two parts: the part before <paramref name="pos"/> is assigned to
|
||||
/// the reference parameter <paramref name="word"/>, the part after <paramref name="pos"/> is returned.
|
||||
/// </summary>
|
||||
public static TextWord Split(ref TextWord word, int pos)
|
||||
{
|
||||
#if DEBUG
|
||||
if (word.Type != TextWordType.Word)
|
||||
throw new ArgumentException("word.Type must be Word");
|
||||
if (pos <= 0)
|
||||
throw new ArgumentOutOfRangeException("pos", pos, "pos must be > 0");
|
||||
if (pos >= word.Length)
|
||||
throw new ArgumentOutOfRangeException("pos", pos, "pos must be < word.Length");
|
||||
#endif
|
||||
TextWord after = new TextWord(word.document, word.line, word.offset + pos, word.length - pos, word.color, word.hasDefaultColor);
|
||||
word = new TextWord(word.document, word.line, word.offset, pos, word.color, word.hasDefaultColor);
|
||||
return after;
|
||||
}
|
||||
|
||||
public bool HasDefaultColor {
|
||||
get {
|
||||
return hasDefaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual TextWordType Type {
|
||||
get {
|
||||
return TextWordType.Word;
|
||||
}
|
||||
}
|
||||
|
||||
public string Word {
|
||||
get {
|
||||
if (document == null) {
|
||||
return string.Empty;
|
||||
}
|
||||
return document.GetText(line.Offset + offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Font GetFont(FontContainer fontContainer)
|
||||
{
|
||||
return color.GetFont(fontContainer);
|
||||
}
|
||||
|
||||
public Color Color {
|
||||
get {
|
||||
if (color == null)
|
||||
return Color.Black;
|
||||
else
|
||||
return color.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Bold {
|
||||
get {
|
||||
if (color == null)
|
||||
return false;
|
||||
else
|
||||
return color.Bold;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Italic {
|
||||
get {
|
||||
if (color == null)
|
||||
return false;
|
||||
else
|
||||
return color.Italic;
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightColor SyntaxColor {
|
||||
get {
|
||||
return color;
|
||||
}
|
||||
set {
|
||||
Debug.Assert(value != null);
|
||||
color = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsWhiteSpace {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsDelimiter { get; set; } = false;
|
||||
|
||||
protected TextWord()
|
||||
{
|
||||
}
|
||||
|
||||
// TAB
|
||||
public TextWord(IDocument document, LineSegment line, int offset, int length, HighlightColor color, bool hasDefaultColor)
|
||||
{
|
||||
Debug.Assert(document != null);
|
||||
Debug.Assert(line != null);
|
||||
Debug.Assert(color != null);
|
||||
|
||||
this.document = document;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.color = color;
|
||||
this.hasDefaultColor = hasDefaultColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="TextWord"/> instance to string (for debug purposes)
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[TextWord: Word = " + Word + ", Color = " + Color + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
315
ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs
Normal file
315
ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
using ICSharpCode.TextEditor.Undo;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface represents a container which holds a text sequence and
|
||||
/// all necessary information about it. It is used as the base for a text editor.
|
||||
/// </summary>
|
||||
public interface IDocument
|
||||
{
|
||||
ITextEditorProperties TextEditorProperties {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
UndoStack UndoStack {
|
||||
get;
|
||||
}
|
||||
/// <value>
|
||||
/// If true the document can't be altered
|
||||
/// </value>
|
||||
bool ReadOnly {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFormattingStrategy"/> attached to the <see cref="IDocument"/> instance
|
||||
/// </summary>
|
||||
IFormattingStrategy FormattingStrategy {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ITextBufferStrategy"/> attached to the <see cref="IDocument"/> instance
|
||||
/// </summary>
|
||||
ITextBufferStrategy TextBufferStrategy {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FoldingManager"/> attached to the <see cref="IDocument"/> instance
|
||||
/// </summary>
|
||||
FoldingManager FoldingManager {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IHighlightingStrategy"/> attached to the <see cref="IDocument"/> instance
|
||||
/// </summary>
|
||||
IHighlightingStrategy HighlightingStrategy {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IBookMarkManager"/> attached to the <see cref="IDocument"/> instance
|
||||
/// </summary>
|
||||
BookmarkManager BookmarkManager {
|
||||
get;
|
||||
}
|
||||
|
||||
MarkerStrategy MarkerStrategy {
|
||||
get;
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// The <see cref="SelectionManager"/> attached to the <see cref="IDocument"/> instance
|
||||
// /// </summary>
|
||||
// SelectionManager SelectionManager {
|
||||
// get;
|
||||
// }
|
||||
|
||||
#region ILineManager interface
|
||||
/// <value>
|
||||
/// A collection of all line segments
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// The collection should only be used if you're aware
|
||||
/// of the 'last line ends with a delimiter problem'. Otherwise
|
||||
/// the <see cref="GetLineSegment"/> method should be used.
|
||||
/// </remarks>
|
||||
IList<LineSegment> LineSegmentCollection {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The total number of lines in the document.
|
||||
/// </value>
|
||||
int TotalNumberOfLines {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Returns a valid line number for the given offset.
|
||||
/// </remarks>
|
||||
/// <param name="offset">
|
||||
/// A offset which points to a character in the line which
|
||||
/// line number is returned.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An int which value is the line number.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentException">If offset points not to a valid position</exception>
|
||||
int GetLineNumberForOffset(int offset);
|
||||
|
||||
/// <remarks>
|
||||
/// Returns a <see cref="LineSegment"/> for the given offset.
|
||||
/// </remarks>
|
||||
/// <param name="offset">
|
||||
/// A offset which points to a character in the line which
|
||||
/// is returned.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="LineSegment"/> object.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentException">If offset points not to a valid position</exception>
|
||||
LineSegment GetLineSegmentForOffset(int offset);
|
||||
|
||||
/// <remarks>
|
||||
/// Returns a <see cref="LineSegment"/> for the given line number.
|
||||
/// This function should be used to get a line instead of getting the
|
||||
/// line using the <see cref="ArrayList"/>.
|
||||
/// </remarks>
|
||||
/// <param name="lineNumber">
|
||||
/// The line number which is requested.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="LineSegment"/> object.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentException">If offset points not to a valid position</exception>
|
||||
LineSegment GetLineSegment(int lineNumber);
|
||||
|
||||
/// <remarks>
|
||||
/// Get the first logical line for a given visible line.
|
||||
/// example : lineNumber == 100 foldings are in the linetracker
|
||||
/// between 0..1 (2 folded, invisible lines) this method returns 102
|
||||
/// the 'logical' line number
|
||||
/// </remarks>
|
||||
int GetFirstLogicalLine(int lineNumber);
|
||||
|
||||
/// <remarks>
|
||||
/// Get the last logical line for a given visible line.
|
||||
/// example : lineNumber == 100 foldings are in the linetracker
|
||||
/// between 0..1 (2 folded, invisible lines) this method returns 102
|
||||
/// the 'logical' line number
|
||||
/// </remarks>
|
||||
int GetLastLogicalLine(int lineNumber);
|
||||
|
||||
/// <remarks>
|
||||
/// Get the visible line for a given logical line.
|
||||
/// example : lineNumber == 100 foldings are in the linetracker
|
||||
/// between 0..1 (2 folded, invisible lines) this method returns 98
|
||||
/// the 'visible' line number
|
||||
/// </remarks>
|
||||
int GetVisibleLine(int lineNumber);
|
||||
|
||||
// /// <remarks>
|
||||
// /// Get the visible column for a given logical line and logical column.
|
||||
// /// </remarks>
|
||||
// int GetVisibleColumn(int logicalLine, int logicalColumn);
|
||||
|
||||
/// <remarks>
|
||||
/// Get the next visible line after lineNumber
|
||||
/// </remarks>
|
||||
int GetNextVisibleLineAbove(int lineNumber, int lineCount);
|
||||
|
||||
/// <remarks>
|
||||
/// Get the next visible line below lineNumber
|
||||
/// </remarks>
|
||||
int GetNextVisibleLineBelow(int lineNumber, int lineCount);
|
||||
|
||||
event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;
|
||||
event EventHandler<LineCountChangeEventArgs> LineCountChanged;
|
||||
event EventHandler<LineEventArgs> LineDeleted;
|
||||
#endregion
|
||||
|
||||
#region ITextBufferStrategy interface
|
||||
/// <value>
|
||||
/// Get the whole text as string.
|
||||
/// When setting the text using the TextContent property, the undo stack is cleared.
|
||||
/// Set TextContent only for actions such as loading a file; if you want to change the current document
|
||||
/// use the Replace method instead.
|
||||
/// </value>
|
||||
string TextContent {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The current length of the sequence of characters that can be edited.
|
||||
/// </value>
|
||||
int TextLength {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a string of characters into the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// offset where to insert the string.
|
||||
/// </param>
|
||||
/// <param name="text">
|
||||
/// text to be inserted.
|
||||
/// </param>
|
||||
void Insert(int offset, string text);
|
||||
|
||||
/// <summary>
|
||||
/// Removes some portion of the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// offset of the remove.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// number of characters to remove.
|
||||
/// </param>
|
||||
void Remove(int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Replace some portion of the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// offset.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// number of characters to replace.
|
||||
/// </param>
|
||||
/// <param name="text">
|
||||
/// text to be replaced with.
|
||||
/// </param>
|
||||
void Replace(int offset, int length, string text);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific char of the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// Offset of the char to get.
|
||||
/// </param>
|
||||
char GetCharAt(int offset);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a string of characters contained in the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// Offset into the sequence to fetch
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// number of characters to copy.
|
||||
/// </param>
|
||||
string GetText(int offset, int length);
|
||||
#endregion
|
||||
string GetText(ISegment segment);
|
||||
|
||||
#region ITextModel interface
|
||||
/// <summary>
|
||||
/// returns the logical line/column position from an offset
|
||||
/// </summary>
|
||||
TextLocation OffsetToPosition(int offset);
|
||||
|
||||
/// <summary>
|
||||
/// returns the offset from a logical line/column position
|
||||
/// </summary>
|
||||
int PositionToOffset(TextLocation p);
|
||||
#endregion
|
||||
/// <value>
|
||||
/// A container where all TextAreaUpdate objects get stored
|
||||
/// </value>
|
||||
List<TextAreaUpdate> UpdateQueue {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Requests an update of the textarea
|
||||
/// </remarks>
|
||||
void RequestUpdate(TextAreaUpdate update);
|
||||
|
||||
/// <remarks>
|
||||
/// Commits all updates in the queue to the textarea (the
|
||||
/// textarea will be painted)
|
||||
/// </remarks>
|
||||
void CommitUpdate();
|
||||
|
||||
/// <summary>
|
||||
/// Moves, Resizes, Removes a list of segments on insert/remove/replace events.
|
||||
/// </summary>
|
||||
void UpdateSegmentListOnDocumentChange<T>(List<T> list, DocumentEventArgs e) where T : ISegment;
|
||||
|
||||
/// <summary>
|
||||
/// Is fired when CommitUpdate is called
|
||||
/// </summary>
|
||||
event EventHandler UpdateCommited;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
event DocumentEventHandler DocumentAboutToBeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
event DocumentEventHandler DocumentChanged;
|
||||
|
||||
event EventHandler TextContentChanged;
|
||||
}
|
||||
}
|
||||
32
ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs
Normal file
32
ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used to describe a span inside a text sequence
|
||||
/// </summary>
|
||||
public interface ISegment
|
||||
{
|
||||
/// <value>
|
||||
/// The offset where the span begins
|
||||
/// </value>
|
||||
int Offset {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The length of the span
|
||||
/// </value>
|
||||
int Length {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="none" email=""/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public interface ITextEditorProperties
|
||||
{
|
||||
bool CaretLine
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool AutoInsertCurlyBracket { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool HideMouseCursor { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool IsIconBarVisible { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool AllowCaretBeyondEOL {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowMatchingBracket { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool CutCopyWholeLine {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
System.Drawing.Text.TextRenderingHint TextRenderingHint { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool MouseWheelScrollDown {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool MouseWheelTextZoom {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
string LineTerminator {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
LineViewerStyle LineViewerStyle { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowInvalidLines { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
int VerticalRulerRow { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowSpaces { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowTabs { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowEOLMarker { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ConvertTabsToSpaces { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowHorizontalRuler { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowVerticalRuler { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
Encoding Encoding {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool EnableFolding { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool ShowLineNumbers { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The width of a tab.
|
||||
/// </summary>
|
||||
int TabIndent { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of spaces a tab is converted to if ConvertTabsToSpaces is true.
|
||||
/// </summary>
|
||||
int IndentationSize {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
IndentStyle IndentStyle { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
DocumentSelectionMode DocumentSelectionMode {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
Font Font { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
FontContainer FontContainer {
|
||||
get;
|
||||
}
|
||||
|
||||
BracketMatchingStyle BracketMatchingStyle { // is wrapped in text editor control
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
bool SupportReadOnlySegments {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of events that are fired after the line manager has finished working.
|
||||
/// </summary>
|
||||
struct DeferredEventList
|
||||
{
|
||||
internal List<LineSegment> removedLines;
|
||||
internal List<TextAnchor> textAnchor;
|
||||
|
||||
public void AddRemovedLine(LineSegment line)
|
||||
{
|
||||
if (removedLines == null)
|
||||
removedLines = new List<LineSegment>();
|
||||
removedLines.Add(line);
|
||||
}
|
||||
|
||||
public void AddDeletedAnchor(TextAnchor anchor)
|
||||
{
|
||||
if (textAnchor == null)
|
||||
textAnchor = new List<TextAnchor>();
|
||||
textAnchor.Add(anchor);
|
||||
}
|
||||
|
||||
public void RaiseEvents()
|
||||
{
|
||||
// removedLines is raised by the LineManager
|
||||
if (textAnchor != null) {
|
||||
foreach (TextAnchor a in textAnchor) {
|
||||
a.RaiseDeleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
internal sealed class LineManager
|
||||
{
|
||||
LineSegmentTree lineCollection = new LineSegmentTree();
|
||||
|
||||
IDocument document;
|
||||
IHighlightingStrategy highlightingStrategy;
|
||||
|
||||
public IList<LineSegment> LineSegmentCollection {
|
||||
get {
|
||||
return lineCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public int TotalNumberOfLines {
|
||||
get {
|
||||
return lineCollection.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public IHighlightingStrategy HighlightingStrategy {
|
||||
get {
|
||||
return highlightingStrategy;
|
||||
}
|
||||
set {
|
||||
if (highlightingStrategy != value) {
|
||||
highlightingStrategy = value;
|
||||
if (highlightingStrategy != null) {
|
||||
highlightingStrategy.MarkTokens(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LineManager(IDocument document, IHighlightingStrategy highlightingStrategy)
|
||||
{
|
||||
this.document = document;
|
||||
this.highlightingStrategy = highlightingStrategy;
|
||||
}
|
||||
|
||||
public int GetLineNumberForOffset(int offset)
|
||||
{
|
||||
return GetLineSegmentForOffset(offset).LineNumber;
|
||||
}
|
||||
|
||||
public LineSegment GetLineSegmentForOffset(int offset)
|
||||
{
|
||||
return lineCollection.GetByOffset(offset);
|
||||
}
|
||||
|
||||
public LineSegment GetLineSegment(int lineNr)
|
||||
{
|
||||
return lineCollection[lineNr];
|
||||
}
|
||||
|
||||
public void Insert(int offset, string text)
|
||||
{
|
||||
Replace(offset, 0, text);
|
||||
}
|
||||
|
||||
public void Remove(int offset, int length)
|
||||
{
|
||||
Replace(offset, length, string.Empty);
|
||||
}
|
||||
|
||||
public void Replace(int offset, int length, string text)
|
||||
{
|
||||
Debug.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length);
|
||||
int lineStart = GetLineNumberForOffset(offset);
|
||||
int oldNumberOfLines = this.TotalNumberOfLines;
|
||||
DeferredEventList deferredEventList = new DeferredEventList();
|
||||
RemoveInternal(ref deferredEventList, offset, length);
|
||||
int numberOfLinesAfterRemoving = this.TotalNumberOfLines;
|
||||
if (!string.IsNullOrEmpty(text)) {
|
||||
InsertInternal(offset, text);
|
||||
}
|
||||
// #if DEBUG
|
||||
// Console.WriteLine("New line collection:");
|
||||
// Console.WriteLine(lineCollection.GetTreeAsString());
|
||||
// Console.WriteLine("New text:");
|
||||
// Console.WriteLine("'" + document.TextContent + "'");
|
||||
// #endif
|
||||
// Only fire events after RemoveInternal+InsertInternal finished completely:
|
||||
// Otherwise we would expose inconsistent state to the event handlers.
|
||||
RunHighlighter(lineStart, 1 + Math.Max(0, this.TotalNumberOfLines - numberOfLinesAfterRemoving));
|
||||
|
||||
if (deferredEventList.removedLines != null) {
|
||||
foreach (LineSegment ls in deferredEventList.removedLines)
|
||||
OnLineDeleted(new LineEventArgs(document, ls));
|
||||
}
|
||||
deferredEventList.RaiseEvents();
|
||||
if (this.TotalNumberOfLines != oldNumberOfLines) {
|
||||
OnLineCountChanged(new LineCountChangeEventArgs(document, lineStart, this.TotalNumberOfLines - oldNumberOfLines));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveInternal(ref DeferredEventList deferredEventList, int offset, int length)
|
||||
{
|
||||
Debug.Assert(length >= 0);
|
||||
if (length == 0) return;
|
||||
LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForOffset(offset);
|
||||
LineSegment startSegment = it.Current;
|
||||
int startSegmentOffset = startSegment.Offset;
|
||||
if (offset + length < startSegmentOffset + startSegment.TotalLength) {
|
||||
// just removing a part of this line segment
|
||||
startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, length);
|
||||
SetSegmentLength(startSegment, startSegment.TotalLength - length);
|
||||
return;
|
||||
}
|
||||
// merge startSegment with another line segment because startSegment's delimiter was deleted
|
||||
// possibly remove lines in between if multiple delimiters were deleted
|
||||
int charactersRemovedInStartLine = startSegmentOffset + startSegment.TotalLength - offset;
|
||||
Debug.Assert(charactersRemovedInStartLine > 0);
|
||||
startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, charactersRemovedInStartLine);
|
||||
|
||||
|
||||
LineSegment endSegment = lineCollection.GetByOffset(offset + length);
|
||||
if (endSegment == startSegment) {
|
||||
// special case: we are removing a part of the last line up to the
|
||||
// end of the document
|
||||
SetSegmentLength(startSegment, startSegment.TotalLength - length);
|
||||
return;
|
||||
}
|
||||
int endSegmentOffset = endSegment.Offset;
|
||||
int charactersLeftInEndLine = endSegmentOffset + endSegment.TotalLength - (offset + length);
|
||||
endSegment.RemovedLinePart(ref deferredEventList, 0, endSegment.TotalLength - charactersLeftInEndLine);
|
||||
startSegment.MergedWith(endSegment, offset - startSegmentOffset);
|
||||
SetSegmentLength(startSegment, startSegment.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine);
|
||||
startSegment.DelimiterLength = endSegment.DelimiterLength;
|
||||
// remove all segments between startSegment (excl.) and endSegment (incl.)
|
||||
it.MoveNext();
|
||||
LineSegment segmentToRemove;
|
||||
do {
|
||||
segmentToRemove = it.Current;
|
||||
it.MoveNext();
|
||||
lineCollection.RemoveSegment(segmentToRemove);
|
||||
segmentToRemove.Deleted(ref deferredEventList);
|
||||
} while (segmentToRemove != endSegment);
|
||||
}
|
||||
|
||||
void InsertInternal(int offset, string text)
|
||||
{
|
||||
LineSegment segment = lineCollection.GetByOffset(offset);
|
||||
DelimiterSegment ds = NextDelimiter(text, 0);
|
||||
if (ds == null) {
|
||||
// no newline is being inserted, all text is inserted in a single line
|
||||
segment.InsertedLinePart(offset - segment.Offset, text.Length);
|
||||
SetSegmentLength(segment, segment.TotalLength + text.Length);
|
||||
return;
|
||||
}
|
||||
LineSegment firstLine = segment;
|
||||
firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset);
|
||||
int lastDelimiterEnd = 0;
|
||||
while (ds != null) {
|
||||
// split line segment at line delimiter
|
||||
int lineBreakOffset = offset + ds.Offset + ds.Length;
|
||||
int segmentOffset = segment.Offset;
|
||||
int lengthAfterInsertionPos = segmentOffset + segment.TotalLength - (offset + lastDelimiterEnd);
|
||||
lineCollection.SetSegmentLength(segment, lineBreakOffset - segmentOffset);
|
||||
LineSegment newSegment = lineCollection.InsertSegmentAfter(segment, lengthAfterInsertionPos);
|
||||
segment.DelimiterLength = ds.Length;
|
||||
|
||||
segment = newSegment;
|
||||
lastDelimiterEnd = ds.Offset + ds.Length;
|
||||
|
||||
ds = NextDelimiter(text, lastDelimiterEnd);
|
||||
}
|
||||
firstLine.SplitTo(segment);
|
||||
// insert rest after last delimiter
|
||||
if (lastDelimiterEnd != text.Length) {
|
||||
segment.InsertedLinePart(0, text.Length - lastDelimiterEnd);
|
||||
SetSegmentLength(segment, segment.TotalLength + text.Length - lastDelimiterEnd);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSegmentLength(LineSegment segment, int newTotalLength)
|
||||
{
|
||||
int delta = newTotalLength - segment.TotalLength;
|
||||
if (delta != 0) {
|
||||
lineCollection.SetSegmentLength(segment, newTotalLength);
|
||||
OnLineLengthChanged(new LineLengthChangeEventArgs(document, segment, delta));
|
||||
}
|
||||
}
|
||||
|
||||
void RunHighlighter(int firstLine, int lineCount)
|
||||
{
|
||||
if (highlightingStrategy != null) {
|
||||
List<LineSegment> markLines = new List<LineSegment>();
|
||||
LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForIndex(firstLine);
|
||||
for (int i = 0; i < lineCount && it.IsValid; i++) {
|
||||
markLines.Add(it.Current);
|
||||
it.MoveNext();
|
||||
}
|
||||
highlightingStrategy.MarkTokens(document, markLines);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetContent(string text)
|
||||
{
|
||||
lineCollection.Clear();
|
||||
if (text != null) {
|
||||
Replace(0, 0, text);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetVisibleLine(int logicalLineNumber)
|
||||
{
|
||||
if (!document.TextEditorProperties.EnableFolding) {
|
||||
return logicalLineNumber;
|
||||
}
|
||||
|
||||
int visibleLine = 0;
|
||||
int foldEnd = 0;
|
||||
List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
|
||||
foreach (FoldMarker fm in foldings) {
|
||||
if (fm.StartLine >= logicalLineNumber) {
|
||||
break;
|
||||
}
|
||||
if (fm.StartLine >= foldEnd) {
|
||||
visibleLine += fm.StartLine - foldEnd;
|
||||
if (fm.EndLine > logicalLineNumber) {
|
||||
return visibleLine;
|
||||
}
|
||||
foldEnd = fm.EndLine;
|
||||
}
|
||||
}
|
||||
// Debug.Assert(logicalLineNumber >= foldEnd);
|
||||
visibleLine += logicalLineNumber - foldEnd;
|
||||
return visibleLine;
|
||||
}
|
||||
|
||||
public int GetFirstLogicalLine(int visibleLineNumber)
|
||||
{
|
||||
if (!document.TextEditorProperties.EnableFolding) {
|
||||
return visibleLineNumber;
|
||||
}
|
||||
int v = 0;
|
||||
int foldEnd = 0;
|
||||
List<FoldMarker> foldings = document.FoldingManager.GetTopLevelFoldedFoldings();
|
||||
foreach (FoldMarker fm in foldings) {
|
||||
if (fm.StartLine >= foldEnd) {
|
||||
if (v + fm.StartLine - foldEnd >= visibleLineNumber) {
|
||||
break;
|
||||
}
|
||||
v += fm.StartLine - foldEnd;
|
||||
foldEnd = fm.EndLine;
|
||||
}
|
||||
}
|
||||
// help GC
|
||||
foldings.Clear();
|
||||
foldings = null;
|
||||
return foldEnd + visibleLineNumber - v;
|
||||
}
|
||||
|
||||
public int GetLastLogicalLine(int visibleLineNumber)
|
||||
{
|
||||
if (!document.TextEditorProperties.EnableFolding) {
|
||||
return visibleLineNumber;
|
||||
}
|
||||
return GetFirstLogicalLine(visibleLineNumber + 1) - 1;
|
||||
}
|
||||
|
||||
// TODO : speedup the next/prev visible line search
|
||||
// HOW? : save the foldings in a sorted list and lookup the
|
||||
// line numbers in this list
|
||||
public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
|
||||
{
|
||||
int curLineNumber = lineNumber;
|
||||
if (document.TextEditorProperties.EnableFolding) {
|
||||
for (int i = 0; i < lineCount && curLineNumber < TotalNumberOfLines; ++i) {
|
||||
++curLineNumber;
|
||||
while (curLineNumber < TotalNumberOfLines && (curLineNumber >= lineCollection.Count || !document.FoldingManager.IsLineVisible(curLineNumber))) {
|
||||
++curLineNumber;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
curLineNumber += lineCount;
|
||||
}
|
||||
return Math.Min(TotalNumberOfLines - 1, curLineNumber);
|
||||
}
|
||||
|
||||
public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
|
||||
{
|
||||
int curLineNumber = lineNumber;
|
||||
if (document.TextEditorProperties.EnableFolding) {
|
||||
for (int i = 0; i < lineCount; ++i) {
|
||||
--curLineNumber;
|
||||
while (curLineNumber >= 0 && !document.FoldingManager.IsLineVisible(curLineNumber)) {
|
||||
--curLineNumber;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
curLineNumber -= lineCount;
|
||||
}
|
||||
return Math.Max(0, curLineNumber);
|
||||
}
|
||||
|
||||
// use always the same DelimiterSegment object for the NextDelimiter
|
||||
DelimiterSegment delimiterSegment = new DelimiterSegment();
|
||||
|
||||
DelimiterSegment NextDelimiter(string text, int offset)
|
||||
{
|
||||
for (int i = offset; i < text.Length; i++) {
|
||||
switch (text[i]) {
|
||||
case '\r':
|
||||
if (i + 1 < text.Length) {
|
||||
if (text[i + 1] == '\n') {
|
||||
delimiterSegment.Offset = i;
|
||||
delimiterSegment.Length = 2;
|
||||
return delimiterSegment;
|
||||
}
|
||||
}
|
||||
#if DATACONSISTENCYTEST
|
||||
Debug.Assert(false, "Found lone \\r, data consistency problems?");
|
||||
#endif
|
||||
goto case '\n';
|
||||
case '\n':
|
||||
delimiterSegment.Offset = i;
|
||||
delimiterSegment.Length = 1;
|
||||
return delimiterSegment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void OnLineCountChanged(LineCountChangeEventArgs e)
|
||||
{
|
||||
if (LineCountChanged != null) {
|
||||
LineCountChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
void OnLineLengthChanged(LineLengthChangeEventArgs e)
|
||||
{
|
||||
if (LineLengthChanged != null) {
|
||||
LineLengthChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
void OnLineDeleted(LineEventArgs e)
|
||||
{
|
||||
if (LineDeleted != null) {
|
||||
LineDeleted(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;
|
||||
public event EventHandler<LineCountChangeEventArgs> LineCountChanged;
|
||||
public event EventHandler<LineEventArgs> LineDeleted;
|
||||
|
||||
sealed class DelimiterSegment
|
||||
{
|
||||
internal int Offset;
|
||||
internal int Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class LineCountChangeEventArgs : EventArgs
|
||||
{
|
||||
IDocument document;
|
||||
int start;
|
||||
int moved;
|
||||
|
||||
/// <returns>
|
||||
/// always a valid Document which is related to the Event.
|
||||
/// </returns>
|
||||
public IDocument Document {
|
||||
get {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// -1 if no offset was specified for this event
|
||||
/// </returns>
|
||||
public int LineStart {
|
||||
get {
|
||||
return start;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// -1 if no length was specified for this event
|
||||
/// </returns>
|
||||
public int LinesMoved {
|
||||
get {
|
||||
return moved;
|
||||
}
|
||||
}
|
||||
|
||||
public LineCountChangeEventArgs(IDocument document, int lineStart, int linesMoved)
|
||||
{
|
||||
this.document = document;
|
||||
this.start = lineStart;
|
||||
this.moved = linesMoved;
|
||||
}
|
||||
}
|
||||
|
||||
public class LineEventArgs : EventArgs
|
||||
{
|
||||
IDocument document;
|
||||
LineSegment lineSegment;
|
||||
|
||||
public IDocument Document {
|
||||
get { return document; }
|
||||
}
|
||||
|
||||
public LineSegment LineSegment {
|
||||
get { return lineSegment; }
|
||||
}
|
||||
|
||||
public LineEventArgs(IDocument document, LineSegment lineSegment)
|
||||
{
|
||||
this.document = document;
|
||||
this.lineSegment = lineSegment;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[LineEventArgs Document={0} LineSegment={1}]", this.document, this.lineSegment);
|
||||
}
|
||||
}
|
||||
|
||||
public class LineLengthChangeEventArgs : LineEventArgs
|
||||
{
|
||||
int lengthDelta;
|
||||
|
||||
public int LengthDelta {
|
||||
get { return lengthDelta; }
|
||||
}
|
||||
|
||||
public LineLengthChangeEventArgs(IDocument document, LineSegment lineSegment, int moved)
|
||||
: base(document, lineSegment)
|
||||
{
|
||||
this.lengthDelta = moved;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[LineLengthEventArgs Document={0} LineSegment={1} LengthDelta={2}]", this.Document, this.LineSegment, this.lengthDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public sealed class LineSegment : ISegment
|
||||
{
|
||||
internal LineSegmentTree.Enumerator treeEntry;
|
||||
int totalLength, delimiterLength;
|
||||
|
||||
List<TextWord> words;
|
||||
SpanStack highlightSpanStack;
|
||||
|
||||
public TextWord GetWord(int column)
|
||||
{
|
||||
int curColumn = 0;
|
||||
foreach (TextWord word in words) {
|
||||
if (column < curColumn + word.Length) {
|
||||
return word;
|
||||
}
|
||||
curColumn += word.Length;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsDeleted {
|
||||
get { return !treeEntry.IsValid; }
|
||||
}
|
||||
|
||||
public int LineNumber {
|
||||
get { return treeEntry.CurrentIndex; }
|
||||
}
|
||||
|
||||
public int Offset {
|
||||
get { return treeEntry.CurrentOffset; }
|
||||
}
|
||||
|
||||
public int Length {
|
||||
get { return totalLength - delimiterLength; }
|
||||
}
|
||||
|
||||
int ISegment.Offset {
|
||||
get { return this.Offset; }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
int ISegment.Length {
|
||||
get { return this.Length; }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public int TotalLength {
|
||||
get { return totalLength; }
|
||||
internal set { totalLength = value; }
|
||||
}
|
||||
|
||||
public int DelimiterLength {
|
||||
get { return delimiterLength; }
|
||||
internal set { delimiterLength = value; }
|
||||
}
|
||||
|
||||
// highlighting information
|
||||
public List<TextWord> Words {
|
||||
get {
|
||||
return words;
|
||||
}
|
||||
set {
|
||||
words = value;
|
||||
}
|
||||
}
|
||||
|
||||
public HighlightColor GetColorForPosition(int x)
|
||||
{
|
||||
if (Words != null) {
|
||||
int xPos = 0;
|
||||
foreach (TextWord word in Words) {
|
||||
if (x < xPos + word.Length) {
|
||||
return word.SyntaxColor;
|
||||
}
|
||||
xPos += word.Length;
|
||||
}
|
||||
}
|
||||
return new HighlightColor(Color.Black, false, false);
|
||||
}
|
||||
|
||||
public SpanStack HighlightSpanStack {
|
||||
get {
|
||||
return highlightSpanStack;
|
||||
}
|
||||
set {
|
||||
highlightSpanStack = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="LineSegment"/> instance to string (for debug purposes)
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
if (IsDeleted)
|
||||
return "[LineSegment: (deleted) Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]";
|
||||
else
|
||||
return "[LineSegment: LineNumber=" + LineNumber + ", Offset = "+ Offset +", Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]";
|
||||
}
|
||||
|
||||
#region Anchor management
|
||||
Util.WeakCollection<TextAnchor> anchors;
|
||||
|
||||
public TextAnchor CreateAnchor(int column)
|
||||
{
|
||||
if (column < 0 || column > Length)
|
||||
throw new ArgumentOutOfRangeException("column");
|
||||
TextAnchor anchor = new TextAnchor(this, column);
|
||||
AddAnchor(anchor);
|
||||
return anchor;
|
||||
}
|
||||
|
||||
void AddAnchor(TextAnchor anchor)
|
||||
{
|
||||
Debug.Assert(anchor.Line == this);
|
||||
|
||||
if (anchors == null)
|
||||
anchors = new Util.WeakCollection<TextAnchor>();
|
||||
|
||||
anchors.Add(anchor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when the LineSegment is deleted.
|
||||
/// </summary>
|
||||
internal void Deleted(ref DeferredEventList deferredEventList)
|
||||
{
|
||||
//Console.WriteLine("Deleted");
|
||||
treeEntry = LineSegmentTree.Enumerator.Invalid;
|
||||
if (anchors != null) {
|
||||
foreach (TextAnchor a in anchors) {
|
||||
a.Delete(ref deferredEventList);
|
||||
}
|
||||
anchors = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when a part of the line is removed.
|
||||
/// </summary>
|
||||
internal void RemovedLinePart(ref DeferredEventList deferredEventList, int startColumn, int length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
Debug.Assert(length > 0);
|
||||
|
||||
//Console.WriteLine("RemovedLinePart " + startColumn + ", " + length);
|
||||
if (anchors != null) {
|
||||
List<TextAnchor> deletedAnchors = null;
|
||||
foreach (TextAnchor a in anchors) {
|
||||
if (a.ColumnNumber > startColumn) {
|
||||
if (a.ColumnNumber >= startColumn + length) {
|
||||
a.ColumnNumber -= length;
|
||||
} else {
|
||||
if (deletedAnchors == null)
|
||||
deletedAnchors = new List<TextAnchor>();
|
||||
a.Delete(ref deferredEventList);
|
||||
deletedAnchors.Add(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deletedAnchors != null) {
|
||||
foreach (TextAnchor a in deletedAnchors) {
|
||||
anchors.Remove(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when a part of the line is inserted.
|
||||
/// </summary>
|
||||
internal void InsertedLinePart(int startColumn, int length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
Debug.Assert(length > 0);
|
||||
|
||||
//Console.WriteLine("InsertedLinePart " + startColumn + ", " + length);
|
||||
if (anchors != null) {
|
||||
foreach (TextAnchor a in anchors) {
|
||||
if (a.MovementType == AnchorMovementType.BeforeInsertion
|
||||
? a.ColumnNumber > startColumn
|
||||
: a.ColumnNumber >= startColumn)
|
||||
{
|
||||
a.ColumnNumber += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called after another line's content is appended to this line because the newline in between
|
||||
/// was deleted.
|
||||
/// The DefaultLineManager will call Deleted() on the deletedLine after the MergedWith call.
|
||||
///
|
||||
/// firstLineLength: the length of the line before the merge.
|
||||
/// </summary>
|
||||
internal void MergedWith(LineSegment deletedLine, int firstLineLength)
|
||||
{
|
||||
//Console.WriteLine("MergedWith");
|
||||
|
||||
if (deletedLine.anchors != null) {
|
||||
foreach (TextAnchor a in deletedLine.anchors) {
|
||||
a.Line = this;
|
||||
AddAnchor(a);
|
||||
a.ColumnNumber += firstLineLength;
|
||||
}
|
||||
deletedLine.anchors = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called after a newline was inserted into this line, splitting it into this and followingLine.
|
||||
/// </summary>
|
||||
internal void SplitTo(LineSegment followingLine)
|
||||
{
|
||||
//Console.WriteLine("SplitTo");
|
||||
|
||||
if (anchors != null) {
|
||||
List<TextAnchor> movedAnchors = null;
|
||||
foreach (TextAnchor a in anchors) {
|
||||
if (a.MovementType == AnchorMovementType.BeforeInsertion
|
||||
? a.ColumnNumber > this.Length
|
||||
: a.ColumnNumber >= this.Length)
|
||||
{
|
||||
a.Line = followingLine;
|
||||
followingLine.AddAnchor(a);
|
||||
a.ColumnNumber -= this.Length;
|
||||
|
||||
if (movedAnchors == null)
|
||||
movedAnchors = new List<TextAnchor>();
|
||||
movedAnchors.Add(a);
|
||||
}
|
||||
}
|
||||
if (movedAnchors != null) {
|
||||
foreach (TextAnchor a in movedAnchors) {
|
||||
anchors.Remove(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using ICSharpCode.TextEditor.Util;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Data structure for efficient management of the line segments (most operations are O(lg n)).
|
||||
/// This implements an augmented red-black tree where each node has fields for the number of
|
||||
/// nodes in its subtree (like an order statistics tree) for access by index(=line number).
|
||||
/// Additionally, each node knows the total length of all segments in its subtree.
|
||||
/// This means we can find nodes by offset in O(lg n) time. Since the offset itself is not stored in
|
||||
/// the line segment but computed from the lengths stored in the tree, we adjusting the offsets when
|
||||
/// text is inserted in one line means we just have to increment the totalLength of the affected line and
|
||||
/// its parent nodes - an O(lg n) operation.
|
||||
/// However this means getting the line number or offset from a LineSegment is not a constant time
|
||||
/// operation, but takes O(lg n).
|
||||
///
|
||||
/// NOTE: The tree is never empty, Clear() causes it to contain an empty segment.
|
||||
/// </summary>
|
||||
sealed class LineSegmentTree : IList<LineSegment>
|
||||
{
|
||||
internal struct RBNode
|
||||
{
|
||||
internal LineSegment lineSegment;
|
||||
internal int count;
|
||||
internal int totalLength;
|
||||
|
||||
public RBNode(LineSegment lineSegment)
|
||||
{
|
||||
this.lineSegment = lineSegment;
|
||||
this.count = 1;
|
||||
this.totalLength = lineSegment.TotalLength;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[RBNode count=" + count + " totalLength="+totalLength
|
||||
+ " lineSegment.LineNumber=" + lineSegment.LineNumber
|
||||
+ " lineSegment.Offset=" + lineSegment.Offset
|
||||
+ " lineSegment.TotalLength=" + lineSegment.TotalLength
|
||||
+ " lineSegment.DelimiterLength=" + lineSegment.DelimiterLength + "]";
|
||||
}
|
||||
}
|
||||
|
||||
struct MyHost : IRedBlackTreeHost<RBNode>
|
||||
{
|
||||
public int Compare(RBNode x, RBNode y)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Equals(RBNode a, RBNode b)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UpdateAfterChildrenChange(RedBlackTreeNode<RBNode> node)
|
||||
{
|
||||
int count = 1;
|
||||
int totalLength = node.val.lineSegment.TotalLength;
|
||||
if (node.left != null) {
|
||||
count += node.left.val.count;
|
||||
totalLength += node.left.val.totalLength;
|
||||
}
|
||||
if (node.right != null) {
|
||||
count += node.right.val.count;
|
||||
totalLength += node.right.val.totalLength;
|
||||
}
|
||||
if (count != node.val.count || totalLength != node.val.totalLength) {
|
||||
node.val.count = count;
|
||||
node.val.totalLength = totalLength;
|
||||
if (node.parent != null) UpdateAfterChildrenChange(node.parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAfterRotateLeft(RedBlackTreeNode<RBNode> node)
|
||||
{
|
||||
UpdateAfterChildrenChange(node);
|
||||
UpdateAfterChildrenChange(node.parent);
|
||||
}
|
||||
|
||||
public void UpdateAfterRotateRight(RedBlackTreeNode<RBNode> node)
|
||||
{
|
||||
UpdateAfterChildrenChange(node);
|
||||
UpdateAfterChildrenChange(node.parent);
|
||||
}
|
||||
}
|
||||
|
||||
readonly AugmentableRedBlackTree<RBNode, MyHost> tree = new AugmentableRedBlackTree<RBNode, MyHost>(new MyHost());
|
||||
|
||||
RedBlackTreeNode<RBNode> GetNode(int index)
|
||||
{
|
||||
if (index < 0 || index >= tree.Count)
|
||||
throw new ArgumentOutOfRangeException("index", index, "index should be between 0 and " + (tree.Count-1));
|
||||
RedBlackTreeNode<RBNode> node = tree.root;
|
||||
while (true) {
|
||||
if (node.left != null && index < node.left.val.count) {
|
||||
node = node.left;
|
||||
} else {
|
||||
if (node.left != null) {
|
||||
index -= node.left.val.count;
|
||||
}
|
||||
if (index == 0)
|
||||
return node;
|
||||
index--;
|
||||
node = node.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int GetIndexFromNode(RedBlackTreeNode<RBNode> node)
|
||||
{
|
||||
int index = (node.left != null) ? node.left.val.count : 0;
|
||||
while (node.parent != null) {
|
||||
if (node == node.parent.right) {
|
||||
if (node.parent.left != null)
|
||||
index += node.parent.left.val.count;
|
||||
index++;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
RedBlackTreeNode<RBNode> GetNodeByOffset(int offset)
|
||||
{
|
||||
if (offset < 0 || offset > this.TotalLength)
|
||||
throw new ArgumentOutOfRangeException("offset", offset, "offset should be between 0 and " + this.TotalLength);
|
||||
if (offset == this.TotalLength) {
|
||||
if (tree.root == null)
|
||||
throw new InvalidOperationException("Cannot call GetNodeByOffset while tree is empty.");
|
||||
return tree.root.RightMost;
|
||||
}
|
||||
RedBlackTreeNode<RBNode> node = tree.root;
|
||||
while (true) {
|
||||
if (node.left != null && offset < node.left.val.totalLength) {
|
||||
node = node.left;
|
||||
} else {
|
||||
if (node.left != null) {
|
||||
offset -= node.left.val.totalLength;
|
||||
}
|
||||
offset -= node.val.lineSegment.TotalLength;
|
||||
if (offset < 0)
|
||||
return node;
|
||||
node = node.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int GetOffsetFromNode(RedBlackTreeNode<RBNode> node)
|
||||
{
|
||||
int offset = (node.left != null) ? node.left.val.totalLength : 0;
|
||||
while (node.parent != null) {
|
||||
if (node == node.parent.right) {
|
||||
if (node.parent.left != null)
|
||||
offset += node.parent.left.val.totalLength;
|
||||
offset += node.parent.val.lineSegment.TotalLength;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public LineSegment GetByOffset(int offset)
|
||||
{
|
||||
return GetNodeByOffset(offset).val.lineSegment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total length of all line segments. Runs in O(1).
|
||||
/// </summary>
|
||||
public int TotalLength {
|
||||
get {
|
||||
if (tree.root == null)
|
||||
return 0;
|
||||
else
|
||||
return tree.root.val.totalLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the length of a line segment. Runs in O(lg n).
|
||||
/// </summary>
|
||||
public void SetSegmentLength(LineSegment segment, int newTotalLength)
|
||||
{
|
||||
if (segment == null)
|
||||
throw new ArgumentNullException("segment");
|
||||
RedBlackTreeNode<RBNode> node = segment.treeEntry.it.node;
|
||||
segment.TotalLength = newTotalLength;
|
||||
default(MyHost).UpdateAfterChildrenChange(node);
|
||||
#if DEBUG
|
||||
CheckProperties();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void RemoveSegment(LineSegment segment)
|
||||
{
|
||||
tree.RemoveAt(segment.treeEntry.it);
|
||||
#if DEBUG
|
||||
CheckProperties();
|
||||
#endif
|
||||
}
|
||||
|
||||
public LineSegment InsertSegmentAfter(LineSegment segment, int length)
|
||||
{
|
||||
LineSegment newSegment = new LineSegment();
|
||||
newSegment.TotalLength = length;
|
||||
newSegment.DelimiterLength = segment.DelimiterLength;
|
||||
|
||||
newSegment.treeEntry = InsertAfter(segment.treeEntry.it.node, newSegment);
|
||||
return newSegment;
|
||||
}
|
||||
|
||||
Enumerator InsertAfter(RedBlackTreeNode<RBNode> node, LineSegment newSegment)
|
||||
{
|
||||
RedBlackTreeNode<RBNode> newNode = new RedBlackTreeNode<RBNode>(new RBNode(newSegment));
|
||||
if (node.right == null) {
|
||||
tree.InsertAsRight(node, newNode);
|
||||
} else {
|
||||
tree.InsertAsLeft(node.right.LeftMost, newNode);
|
||||
}
|
||||
#if DEBUG
|
||||
CheckProperties();
|
||||
#endif
|
||||
return new Enumerator(new RedBlackTreeIterator<RBNode>(newNode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the collections. Runs in O(1).
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return tree.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an item by index. Runs in O(lg n).
|
||||
/// </summary>
|
||||
public LineSegment this[int index] {
|
||||
get {
|
||||
return GetNode(index).val.lineSegment;
|
||||
}
|
||||
set {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<LineSegment>.IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an item. Runs in O(lg n).
|
||||
/// </summary>
|
||||
public int IndexOf(LineSegment item)
|
||||
{
|
||||
int index = item.LineNumber;
|
||||
if (index < 0 || index >= this.Count)
|
||||
return -1;
|
||||
if (item != this[index])
|
||||
return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
void IList<LineSegment>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Conditional("DATACONSISTENCYTEST")]
|
||||
void CheckProperties()
|
||||
{
|
||||
if (tree.root == null) {
|
||||
Debug.Assert(this.Count == 0);
|
||||
} else {
|
||||
Debug.Assert(tree.root.val.count == this.Count);
|
||||
CheckProperties(tree.root);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckProperties(RedBlackTreeNode<RBNode> node)
|
||||
{
|
||||
int count = 1;
|
||||
int totalLength = node.val.lineSegment.TotalLength;
|
||||
if (node.left != null) {
|
||||
CheckProperties(node.left);
|
||||
count += node.left.val.count;
|
||||
totalLength += node.left.val.totalLength;
|
||||
}
|
||||
if (node.right != null) {
|
||||
CheckProperties(node.right);
|
||||
count += node.right.val.count;
|
||||
totalLength += node.right.val.totalLength;
|
||||
}
|
||||
Debug.Assert(node.val.count == count);
|
||||
Debug.Assert(node.val.totalLength == totalLength);
|
||||
}
|
||||
|
||||
public string GetTreeAsString()
|
||||
{
|
||||
return tree.GetTreeAsString();
|
||||
}
|
||||
#endif
|
||||
|
||||
public LineSegmentTree()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the list. Runs in O(1).
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
tree.Clear();
|
||||
LineSegment emptySegment = new LineSegment();
|
||||
emptySegment.TotalLength = 0;
|
||||
emptySegment.DelimiterLength = 0;
|
||||
tree.Add(new RBNode(emptySegment));
|
||||
emptySegment.treeEntry = GetEnumeratorForIndex(0);
|
||||
#if DEBUG
|
||||
CheckProperties();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an item is in the list. Runs in O(n).
|
||||
/// </summary>
|
||||
public bool Contains(LineSegment item)
|
||||
{
|
||||
return IndexOf(item) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all elements from the list to the array.
|
||||
/// </summary>
|
||||
public void CopyTo(LineSegment[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null) throw new ArgumentNullException("array");
|
||||
foreach (LineSegment val in this)
|
||||
array[arrayIndex++] = val;
|
||||
}
|
||||
|
||||
IEnumerator<LineSegment> IEnumerable<LineSegment>.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(tree.GetEnumerator());
|
||||
}
|
||||
|
||||
public Enumerator GetEnumeratorForIndex(int index)
|
||||
{
|
||||
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNode(index)));
|
||||
}
|
||||
|
||||
public Enumerator GetEnumeratorForOffset(int offset)
|
||||
{
|
||||
return new Enumerator(new RedBlackTreeIterator<RBNode>(GetNodeByOffset(offset)));
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<LineSegment>
|
||||
{
|
||||
/// <summary>
|
||||
/// An invalid enumerator value. Calling MoveNext on the invalid enumerator
|
||||
/// will always return false, accessing Current will throw an exception.
|
||||
/// </summary>
|
||||
public static readonly Enumerator Invalid = default(Enumerator);
|
||||
|
||||
internal RedBlackTreeIterator<RBNode> it;
|
||||
|
||||
internal Enumerator(RedBlackTreeIterator<RBNode> it)
|
||||
{
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value. Runs in O(1).
|
||||
/// </summary>
|
||||
public LineSegment Current {
|
||||
get {
|
||||
return it.Current.lineSegment;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid {
|
||||
get {
|
||||
return it.IsValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the current value. Runs in O(lg n).
|
||||
/// </summary>
|
||||
public int CurrentIndex {
|
||||
get {
|
||||
if (it.node == null)
|
||||
throw new InvalidOperationException();
|
||||
return GetIndexFromNode(it.node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of the current value. Runs in O(lg n).
|
||||
/// </summary>
|
||||
public int CurrentOffset {
|
||||
get {
|
||||
if (it.node == null)
|
||||
throw new InvalidOperationException();
|
||||
return GetOffsetFromNode(it.node);
|
||||
}
|
||||
}
|
||||
|
||||
object System.Collections.IEnumerator.Current {
|
||||
get {
|
||||
return it.Current.lineSegment;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
|
||||
/// </summary>
|
||||
public bool MoveNext()
|
||||
{
|
||||
return it.MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
|
||||
/// </summary>
|
||||
public bool MoveBack()
|
||||
{
|
||||
return it.MoveBack();
|
||||
}
|
||||
|
||||
void System.Collections.IEnumerator.Reset()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
void IList<LineSegment>.Insert(int index, LineSegment item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void ICollection<LineSegment>.Add(LineSegment item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<LineSegment>.Remove(LineSegment item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the list of markers and provides ways to retrieve markers for specific positions.
|
||||
/// </summary>
|
||||
public sealed class MarkerStrategy
|
||||
{
|
||||
List<TextMarker> textMarker = new List<TextMarker>();
|
||||
IDocument document;
|
||||
|
||||
public IDocument Document {
|
||||
get {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TextMarker> TextMarker {
|
||||
get {
|
||||
return textMarker.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMarker(TextMarker item)
|
||||
{
|
||||
markersTable.Clear();
|
||||
textMarker.Add(item);
|
||||
}
|
||||
|
||||
public void InsertMarker(int index, TextMarker item)
|
||||
{
|
||||
markersTable.Clear();
|
||||
textMarker.Insert(index, item);
|
||||
}
|
||||
|
||||
public void RemoveMarker(TextMarker item)
|
||||
{
|
||||
markersTable.Clear();
|
||||
textMarker.Remove(item);
|
||||
}
|
||||
|
||||
public void RemoveAll(Predicate<TextMarker> match)
|
||||
{
|
||||
markersTable.Clear();
|
||||
textMarker.RemoveAll(match);
|
||||
}
|
||||
|
||||
public MarkerStrategy(IDocument document)
|
||||
{
|
||||
this.document = document;
|
||||
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
|
||||
}
|
||||
|
||||
Dictionary<int, List<TextMarker>> markersTable = new Dictionary<int, List<TextMarker>>();
|
||||
|
||||
public List<TextMarker> GetMarkers(int offset)
|
||||
{
|
||||
if (!markersTable.ContainsKey(offset)) {
|
||||
List<TextMarker> markers = new List<TextMarker>();
|
||||
for (int i = 0; i < textMarker.Count; ++i) {
|
||||
TextMarker marker = (TextMarker)textMarker[i];
|
||||
if (marker.Offset <= offset && offset <= marker.EndOffset) {
|
||||
markers.Add(marker);
|
||||
}
|
||||
}
|
||||
markersTable[offset] = markers;
|
||||
}
|
||||
return markersTable[offset];
|
||||
}
|
||||
|
||||
public List<TextMarker> GetMarkers(int offset, int length)
|
||||
{
|
||||
int endOffset = offset + length - 1;
|
||||
List<TextMarker> markers = new List<TextMarker>();
|
||||
for (int i = 0; i < textMarker.Count; ++i) {
|
||||
TextMarker marker = (TextMarker)textMarker[i];
|
||||
if (// start in marker region
|
||||
marker.Offset <= offset && offset <= marker.EndOffset ||
|
||||
// end in marker region
|
||||
marker.Offset <= endOffset && endOffset <= marker.EndOffset ||
|
||||
// marker start in region
|
||||
offset <= marker.Offset && marker.Offset <= endOffset ||
|
||||
// marker end in region
|
||||
offset <= marker.EndOffset && marker.EndOffset <= endOffset
|
||||
)
|
||||
{
|
||||
markers.Add(marker);
|
||||
}
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
public List<TextMarker> GetMarkers(TextLocation position)
|
||||
{
|
||||
if (position.Y >= document.TotalNumberOfLines || position.Y < 0) {
|
||||
return new List<TextMarker>();
|
||||
}
|
||||
LineSegment segment = document.GetLineSegment(position.Y);
|
||||
return GetMarkers(segment.Offset + position.X);
|
||||
}
|
||||
|
||||
void DocumentChanged(object sender, DocumentEventArgs e)
|
||||
{
|
||||
// reset markers table
|
||||
markersTable.Clear();
|
||||
document.UpdateSegmentListOnDocumentChange(textMarker, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public enum TextMarkerType
|
||||
{
|
||||
Invisible,
|
||||
SolidBlock,
|
||||
Underlined,
|
||||
WaveLine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a part of a document.
|
||||
/// </summary>
|
||||
public class TextMarker : AbstractSegment
|
||||
{
|
||||
TextMarkerType textMarkerType;
|
||||
Color color;
|
||||
Color foreColor;
|
||||
string toolTip = null;
|
||||
bool overrideForeColor = false;
|
||||
|
||||
public TextMarkerType TextMarkerType {
|
||||
get {
|
||||
return textMarkerType;
|
||||
}
|
||||
}
|
||||
|
||||
public Color Color {
|
||||
get {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
public Color ForeColor {
|
||||
get {
|
||||
return foreColor;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OverrideForeColor {
|
||||
get {
|
||||
return overrideForeColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the text segment as read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get; set; }
|
||||
|
||||
public string ToolTip {
|
||||
get {
|
||||
return toolTip;
|
||||
}
|
||||
set {
|
||||
toolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last offset that is inside the marker region.
|
||||
/// </summary>
|
||||
public int EndOffset {
|
||||
get {
|
||||
return Offset + Length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public TextMarker(int offset, int length, TextMarkerType textMarkerType) : this(offset, length, textMarkerType, Color.Red)
|
||||
{
|
||||
}
|
||||
|
||||
public TextMarker(int offset, int length, TextMarkerType textMarkerType, Color color)
|
||||
{
|
||||
if (length < 1) length = 1;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.textMarkerType = textMarkerType;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public TextMarker(int offset, int length, TextMarkerType textMarkerType, Color color, Color foreColor)
|
||||
{
|
||||
if (length < 1) length = 1;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.textMarkerType = textMarkerType;
|
||||
this.color = color;
|
||||
this.foreColor = foreColor;
|
||||
this.overrideForeColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class ColumnRange
|
||||
{
|
||||
public static readonly ColumnRange NoColumn = new ColumnRange(-2, -2);
|
||||
public static readonly ColumnRange WholeColumn = new ColumnRange(-1, -1);
|
||||
|
||||
int startColumn;
|
||||
int endColumn;
|
||||
|
||||
public int StartColumn {
|
||||
get {
|
||||
return startColumn;
|
||||
}
|
||||
set {
|
||||
startColumn = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int EndColumn {
|
||||
get {
|
||||
return endColumn;
|
||||
}
|
||||
set {
|
||||
endColumn = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ColumnRange(int startColumn, int endColumn)
|
||||
{
|
||||
this.startColumn = startColumn;
|
||||
this.endColumn = endColumn;
|
||||
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return startColumn + (endColumn << 16);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is ColumnRange) {
|
||||
return ((ColumnRange)obj).startColumn == startColumn &&
|
||||
((ColumnRange)obj).endColumn == endColumn;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[ColumnRange: StartColumn={0}, EndColumn={1}]", startColumn, endColumn);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the <see cref="ICSharpCode.TextEditor.Document.ISelection"/> interface.
|
||||
/// </summary>
|
||||
public class DefaultSelection : ISelection
|
||||
{
|
||||
IDocument document;
|
||||
bool isRectangularSelection;
|
||||
TextLocation startPosition;
|
||||
TextLocation endPosition;
|
||||
|
||||
public TextLocation StartPosition {
|
||||
get {
|
||||
return startPosition;
|
||||
}
|
||||
set {
|
||||
DefaultDocument.ValidatePosition(document, value);
|
||||
startPosition = value;
|
||||
}
|
||||
}
|
||||
|
||||
public TextLocation EndPosition {
|
||||
get {
|
||||
return endPosition;
|
||||
}
|
||||
set {
|
||||
DefaultDocument.ValidatePosition(document, value);
|
||||
endPosition = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Offset {
|
||||
get {
|
||||
return document.PositionToOffset(startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public int EndOffset {
|
||||
get {
|
||||
return document.PositionToOffset(endPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
return EndOffset - Offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Returns true, if the selection is empty
|
||||
/// </value>
|
||||
public bool IsEmpty {
|
||||
get {
|
||||
return startPosition == endPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Returns true, if the selection is rectangular
|
||||
/// </value>
|
||||
// TODO : make this unused property used.
|
||||
public bool IsRectangularSelection {
|
||||
get {
|
||||
return isRectangularSelection;
|
||||
}
|
||||
set {
|
||||
isRectangularSelection = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The text which is selected by this selection.
|
||||
/// </value>
|
||||
public string SelectedText {
|
||||
get {
|
||||
if (document != null) {
|
||||
if (Length < 0) {
|
||||
return null;
|
||||
}
|
||||
return document.GetText(Offset, Length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="DefaultSelection"/>
|
||||
/// </summary>
|
||||
public DefaultSelection(IDocument document, TextLocation startPosition, TextLocation endPosition)
|
||||
{
|
||||
DefaultDocument.ValidatePosition(document, startPosition);
|
||||
DefaultDocument.ValidatePosition(document, endPosition);
|
||||
Debug.Assert(startPosition <= endPosition);
|
||||
this.document = document;
|
||||
this.startPosition = startPosition;
|
||||
this.endPosition = endPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="DefaultSelection"/> instance to string (for debug purposes)
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[DefaultSelection : StartPosition={0}, EndPosition={1}]", startPosition, endPosition);
|
||||
}
|
||||
public bool ContainsPosition(TextLocation position)
|
||||
{
|
||||
if (this.IsEmpty)
|
||||
return false;
|
||||
return startPosition.Y < position.Y && position.Y < endPosition.Y ||
|
||||
startPosition.Y == position.Y && startPosition.X <= position.X && (startPosition.Y != endPosition.Y || position.X <= endPosition.X) ||
|
||||
endPosition.Y == position.Y && startPosition.Y != endPosition.Y && position.X <= endPosition.X;
|
||||
}
|
||||
|
||||
public bool ContainsOffset(int offset)
|
||||
{
|
||||
return Offset <= offset && offset <= EndOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface representing a portion of the current selection.
|
||||
/// </summary>
|
||||
public interface ISelection
|
||||
{
|
||||
TextLocation StartPosition {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
TextLocation EndPosition {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
int Offset {
|
||||
get;
|
||||
}
|
||||
|
||||
int EndOffset {
|
||||
get;
|
||||
}
|
||||
|
||||
int Length {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Returns true, if the selection is rectangular
|
||||
/// </value>
|
||||
bool IsRectangularSelection {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// Returns true, if the selection is empty
|
||||
/// </value>
|
||||
bool IsEmpty {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The text which is selected by this selection.
|
||||
/// </value>
|
||||
string SelectedText {
|
||||
get;
|
||||
}
|
||||
|
||||
bool ContainsOffset(int offset);
|
||||
|
||||
bool ContainsPosition(TextLocation position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// This class manages the selections in a document.
|
||||
/// </summary>
|
||||
public class SelectionManager : IDisposable
|
||||
{
|
||||
TextLocation selectionStart;
|
||||
|
||||
internal TextLocation SelectionStart {
|
||||
get { return selectionStart; }
|
||||
set {
|
||||
DefaultDocument.ValidatePosition(document, value);
|
||||
selectionStart = value;
|
||||
}
|
||||
}
|
||||
IDocument document;
|
||||
TextArea textArea;
|
||||
internal SelectFrom selectFrom = new SelectFrom();
|
||||
|
||||
internal List<ISelection> selectionCollection = new List<ISelection>();
|
||||
|
||||
/// <value>
|
||||
/// A collection containing all selections.
|
||||
/// </value>
|
||||
public List<ISelection> SelectionCollection {
|
||||
get {
|
||||
return selectionCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// true if the <see cref="SelectionCollection"/> is not empty, false otherwise.
|
||||
/// </value>
|
||||
public bool HasSomethingSelected {
|
||||
get {
|
||||
return selectionCollection.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SelectionIsReadonly {
|
||||
get {
|
||||
if (document.ReadOnly)
|
||||
return true;
|
||||
foreach (ISelection sel in selectionCollection) {
|
||||
if (SelectionIsReadOnly(document, sel))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool SelectionIsReadOnly(IDocument document, ISelection sel)
|
||||
{
|
||||
if (document.TextEditorProperties.SupportReadOnlySegments)
|
||||
return document.MarkerStrategy.GetMarkers(sel.Offset, sel.Length).Exists(m=>m.IsReadOnly);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The text that is currently selected.
|
||||
/// </value>
|
||||
public string SelectedText {
|
||||
get {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// PriorityQueue queue = new PriorityQueue();
|
||||
|
||||
foreach (ISelection s in selectionCollection) {
|
||||
builder.Append(s.SelectedText);
|
||||
// queue.Insert(-s.Offset, s);
|
||||
}
|
||||
|
||||
// while (queue.Count > 0) {
|
||||
// ISelection s = ((ISelection)queue.Remove());
|
||||
// builder.Append(s.SelectedText);
|
||||
// }
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="SelectionManager"/>
|
||||
/// </summary>
|
||||
public SelectionManager(IDocument document)
|
||||
{
|
||||
this.document = document;
|
||||
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="SelectionManager"/>
|
||||
/// </summary>
|
||||
public SelectionManager(IDocument document, TextArea textArea)
|
||||
{
|
||||
this.document = document;
|
||||
this.textArea = textArea;
|
||||
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.document != null) {
|
||||
document.DocumentChanged -= new DocumentEventHandler(DocumentChanged);
|
||||
this.document = null;
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentChanged(object sender, DocumentEventArgs e)
|
||||
{
|
||||
if (e.Text == null) {
|
||||
Remove(e.Offset, e.Length);
|
||||
} else {
|
||||
if (e.Length < 0) {
|
||||
Insert(e.Offset, e.Text);
|
||||
} else {
|
||||
Replace(e.Offset, e.Length, e.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Clears the selection and sets a new selection
|
||||
/// using the given <see cref="ISelection"/> object.
|
||||
/// </remarks>
|
||||
public void SetSelection(ISelection selection)
|
||||
{
|
||||
// autoClearSelection = false;
|
||||
if (selection != null) {
|
||||
if (SelectionCollection.Count == 1 &&
|
||||
selection.StartPosition == SelectionCollection[0].StartPosition &&
|
||||
selection.EndPosition == SelectionCollection[0].EndPosition ) {
|
||||
return;
|
||||
}
|
||||
ClearWithoutUpdate();
|
||||
selectionCollection.Add(selection);
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
|
||||
document.CommitUpdate();
|
||||
OnSelectionChanged(EventArgs.Empty);
|
||||
} else {
|
||||
ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelection(TextLocation startPosition, TextLocation endPosition)
|
||||
{
|
||||
SetSelection(new DefaultSelection(document, startPosition, endPosition));
|
||||
}
|
||||
|
||||
public bool GreaterEqPos(TextLocation p1, TextLocation p2)
|
||||
{
|
||||
return p1.Y > p2.Y || p1.Y == p2.Y && p1.X >= p2.X;
|
||||
}
|
||||
|
||||
public void ExtendSelection(TextLocation oldPosition, TextLocation newPosition)
|
||||
{
|
||||
// where oldposition is where the cursor was,
|
||||
// and newposition is where it has ended up from a click (both zero based)
|
||||
|
||||
if (oldPosition == newPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TextLocation min;
|
||||
TextLocation max;
|
||||
int oldnewX = newPosition.X;
|
||||
bool oldIsGreater = GreaterEqPos(oldPosition, newPosition);
|
||||
if (oldIsGreater) {
|
||||
min = newPosition;
|
||||
max = oldPosition;
|
||||
} else {
|
||||
min = oldPosition;
|
||||
max = newPosition;
|
||||
}
|
||||
|
||||
if (min == max) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasSomethingSelected)
|
||||
{
|
||||
SetSelection(new DefaultSelection(document, min, max));
|
||||
// initialise selectFrom for a cursor selection
|
||||
if (selectFrom.where == WhereFrom.None)
|
||||
SelectionStart = oldPosition; //textArea.Caret.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
ISelection selection = this.selectionCollection[0];
|
||||
|
||||
if (min == max) {
|
||||
//selection.StartPosition = newPosition;
|
||||
return;
|
||||
} else {
|
||||
// changed selection via gutter
|
||||
if (selectFrom.where == WhereFrom.Gutter)
|
||||
{
|
||||
// selection new position is always at the left edge for gutter selections
|
||||
newPosition.X = 0;
|
||||
}
|
||||
|
||||
if (GreaterEqPos(newPosition, SelectionStart)) // selecting forward
|
||||
{
|
||||
selection.StartPosition = SelectionStart;
|
||||
// this handles last line selection
|
||||
if (selectFrom.where == WhereFrom.Gutter ) //&& newPosition.Y != oldPosition.Y)
|
||||
selection.EndPosition = new TextLocation(textArea.Caret.Column, textArea.Caret.Line);
|
||||
else {
|
||||
newPosition.X = oldnewX;
|
||||
selection.EndPosition = newPosition;
|
||||
}
|
||||
} else { // selecting back
|
||||
if (selectFrom.where == WhereFrom.Gutter && selectFrom.first == WhereFrom.Gutter)
|
||||
{ // gutter selection
|
||||
selection.EndPosition = NextValidPosition(SelectionStart.Y);
|
||||
} else { // internal text selection
|
||||
selection.EndPosition = SelectionStart; //selection.StartPosition;
|
||||
}
|
||||
selection.StartPosition = newPosition;
|
||||
}
|
||||
}
|
||||
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, min.Y, max.Y));
|
||||
document.CommitUpdate();
|
||||
OnSelectionChanged(EventArgs.Empty);
|
||||
}
|
||||
|
||||
// retrieve the next available line
|
||||
// - checks that there are more lines available after the current one
|
||||
// - if there are then the next line is returned
|
||||
// - if there are NOT then the last position on the given line is returned
|
||||
public TextLocation NextValidPosition(int line)
|
||||
{
|
||||
if (line < document.TotalNumberOfLines - 1)
|
||||
return new TextLocation(0, line + 1);
|
||||
else
|
||||
return new TextLocation(document.GetLineSegment(document.TotalNumberOfLines - 1).Length + 1, line);
|
||||
}
|
||||
|
||||
void ClearWithoutUpdate()
|
||||
{
|
||||
while (selectionCollection.Count > 0) {
|
||||
ISelection selection = selectionCollection[selectionCollection.Count - 1];
|
||||
selectionCollection.RemoveAt(selectionCollection.Count - 1);
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y));
|
||||
OnSelectionChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
/// <remarks>
|
||||
/// Clears the selection.
|
||||
/// </remarks>
|
||||
public void ClearSelection()
|
||||
{
|
||||
Point mousepos;
|
||||
mousepos = textArea.mousepos;
|
||||
// this is the most logical place to reset selection starting
|
||||
// positions because it is always called before a new selection
|
||||
selectFrom.first = selectFrom.where;
|
||||
TextLocation newSelectionStart = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y);
|
||||
if (selectFrom.where == WhereFrom.Gutter) {
|
||||
newSelectionStart.X = 0;
|
||||
// selectionStart.Y = -1;
|
||||
}
|
||||
if (newSelectionStart.Line >= document.TotalNumberOfLines) {
|
||||
newSelectionStart.Line = document.TotalNumberOfLines-1;
|
||||
newSelectionStart.Column = document.GetLineSegment(document.TotalNumberOfLines-1).Length;
|
||||
}
|
||||
this.SelectionStart = newSelectionStart;
|
||||
|
||||
ClearWithoutUpdate();
|
||||
document.CommitUpdate();
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Removes the selected text from the buffer and clears
|
||||
/// the selection.
|
||||
/// </remarks>
|
||||
public void RemoveSelectedText()
|
||||
{
|
||||
if (SelectionIsReadonly) {
|
||||
ClearSelection();
|
||||
return;
|
||||
}
|
||||
List<int> lines = new List<int>();
|
||||
int offset = -1;
|
||||
bool oneLine = true;
|
||||
// PriorityQueue queue = new PriorityQueue();
|
||||
foreach (ISelection s in selectionCollection) {
|
||||
// ISelection s = ((ISelection)queue.Remove());
|
||||
if (oneLine) {
|
||||
int lineBegin = s.StartPosition.Y;
|
||||
if (lineBegin != s.EndPosition.Y) {
|
||||
oneLine = false;
|
||||
} else {
|
||||
lines.Add(lineBegin);
|
||||
}
|
||||
}
|
||||
offset = s.Offset;
|
||||
document.Remove(s.Offset, s.Length);
|
||||
|
||||
// queue.Insert(-s.Offset, s);
|
||||
}
|
||||
ClearSelection();
|
||||
if (offset >= 0) {
|
||||
// TODO:
|
||||
// document.Caret.Offset = offset;
|
||||
}
|
||||
if (offset != -1) {
|
||||
if (oneLine) {
|
||||
foreach (int i in lines) {
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, i));
|
||||
}
|
||||
} else {
|
||||
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
||||
}
|
||||
document.CommitUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SelectionsOverlap(ISelection s1, ISelection s2)
|
||||
{
|
||||
return (s1.Offset <= s2.Offset && s2.Offset <= s1.Offset + s1.Length) ||
|
||||
(s1.Offset <= s2.Offset + s2.Length && s2.Offset + s2.Length <= s1.Offset + s1.Length) ||
|
||||
(s1.Offset >= s2.Offset && s1.Offset + s1.Length <= s2.Offset + s2.Length);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Returns true if the given offset points to a section which is
|
||||
/// selected.
|
||||
/// </remarks>
|
||||
public bool IsSelected(int offset)
|
||||
{
|
||||
return GetSelectionAt(offset) != null;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Returns a <see cref="ISelection"/> object giving the selection in which
|
||||
/// the offset points to.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// <code>null</code> if the offset doesn't point to a selection
|
||||
/// </returns>
|
||||
public ISelection GetSelectionAt(int offset)
|
||||
{
|
||||
foreach (ISelection s in selectionCollection) {
|
||||
if (s.ContainsOffset(offset)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Used internally, do not call.
|
||||
/// </remarks>
|
||||
internal void Insert(int offset, string text)
|
||||
{
|
||||
// foreach (ISelection selection in SelectionCollection) {
|
||||
// if (selection.Offset > offset) {
|
||||
// selection.Offset += text.Length;
|
||||
// } else if (selection.Offset + selection.Length > offset) {
|
||||
// selection.Length += text.Length;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Used internally, do not call.
|
||||
/// </remarks>
|
||||
internal void Remove(int offset, int length)
|
||||
{
|
||||
// foreach (ISelection selection in selectionCollection) {
|
||||
// if (selection.Offset > offset) {
|
||||
// selection.Offset -= length;
|
||||
// } else if (selection.Offset + selection.Length > offset) {
|
||||
// selection.Length -= length;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Used internally, do not call.
|
||||
/// </remarks>
|
||||
internal void Replace(int offset, int length, string text)
|
||||
{
|
||||
// foreach (ISelection selection in selectionCollection) {
|
||||
// if (selection.Offset > offset) {
|
||||
// selection.Offset = selection.Offset - length + text.Length;
|
||||
// } else if (selection.Offset + selection.Length > offset) {
|
||||
// selection.Length = selection.Length - length + text.Length;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public ColumnRange GetSelectionAtLine(int lineNumber)
|
||||
{
|
||||
foreach (ISelection selection in selectionCollection) {
|
||||
int startLine = selection.StartPosition.Y;
|
||||
int endLine = selection.EndPosition.Y;
|
||||
if (startLine < lineNumber && lineNumber < endLine) {
|
||||
return ColumnRange.WholeColumn;
|
||||
}
|
||||
|
||||
if (startLine == lineNumber) {
|
||||
LineSegment line = document.GetLineSegment(startLine);
|
||||
int startColumn = selection.StartPosition.X;
|
||||
int endColumn = endLine == lineNumber ? selection.EndPosition.X : line.Length + 1;
|
||||
return new ColumnRange(startColumn, endColumn);
|
||||
}
|
||||
|
||||
if (endLine == lineNumber) {
|
||||
int endColumn = selection.EndPosition.X;
|
||||
return new ColumnRange(0, endColumn);
|
||||
}
|
||||
}
|
||||
|
||||
return ColumnRange.NoColumn;
|
||||
}
|
||||
|
||||
public void FireSelectionChanged()
|
||||
{
|
||||
OnSelectionChanged(EventArgs.Empty);
|
||||
}
|
||||
protected virtual void OnSelectionChanged(EventArgs e)
|
||||
{
|
||||
if (SelectionChanged != null) {
|
||||
SelectionChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler SelectionChanged;
|
||||
}
|
||||
|
||||
// selection initiated from...
|
||||
internal class SelectFrom {
|
||||
public int where = WhereFrom.None; // last selection initiator
|
||||
public int first = WhereFrom.None; // first selection initiator
|
||||
|
||||
public SelectFrom()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// selection initiated from type...
|
||||
internal class WhereFrom {
|
||||
public const int None = 0;
|
||||
public const int Gutter = 1;
|
||||
public const int TArea = 2;
|
||||
}
|
||||
}
|
||||
118
ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs
Normal file
118
ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public enum AnchorMovementType
|
||||
{
|
||||
/// <summary>
|
||||
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay
|
||||
/// before the inserted text.
|
||||
/// </summary>
|
||||
BeforeInsertion,
|
||||
/// <summary>
|
||||
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move
|
||||
/// after the inserted text.
|
||||
/// </summary>
|
||||
AfterInsertion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An anchor that can be put into a document and moves around when the document is changed.
|
||||
/// </summary>
|
||||
public sealed class TextAnchor
|
||||
{
|
||||
static Exception AnchorDeletedError()
|
||||
{
|
||||
return new InvalidOperationException("The text containing the anchor was deleted");
|
||||
}
|
||||
|
||||
LineSegment lineSegment;
|
||||
int columnNumber;
|
||||
|
||||
public LineSegment Line {
|
||||
get {
|
||||
if (lineSegment == null) throw AnchorDeletedError();
|
||||
return lineSegment;
|
||||
}
|
||||
internal set {
|
||||
lineSegment = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDeleted {
|
||||
get {
|
||||
return lineSegment == null;
|
||||
}
|
||||
}
|
||||
|
||||
public int LineNumber {
|
||||
get {
|
||||
return this.Line.LineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
public int ColumnNumber {
|
||||
get {
|
||||
if (lineSegment == null) throw AnchorDeletedError();
|
||||
return columnNumber;
|
||||
}
|
||||
internal set {
|
||||
columnNumber = value;
|
||||
}
|
||||
}
|
||||
|
||||
public TextLocation Location {
|
||||
get {
|
||||
return new TextLocation(this.ColumnNumber, this.LineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public int Offset {
|
||||
get {
|
||||
return this.Line.Offset + columnNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls how the anchor moves.
|
||||
/// </summary>
|
||||
public AnchorMovementType MovementType { get; set; }
|
||||
|
||||
public event EventHandler Deleted;
|
||||
|
||||
internal void Delete(ref DeferredEventList deferredEventList)
|
||||
{
|
||||
// we cannot fire an event here because this method is called while the LineManager adjusts the
|
||||
// lineCollection, so an event handler could see inconsistent state
|
||||
lineSegment = null;
|
||||
deferredEventList.AddDeletedAnchor(this);
|
||||
}
|
||||
|
||||
internal void RaiseDeleted()
|
||||
{
|
||||
if (Deleted != null)
|
||||
Deleted(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal TextAnchor(LineSegment lineSegment, int columnNumber)
|
||||
{
|
||||
this.lineSegment = lineSegment;
|
||||
this.columnNumber = columnNumber;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.IsDeleted)
|
||||
return "[TextAnchor (deleted)]";
|
||||
else
|
||||
return "[TextAnchor " + this.Location.ToString() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
public class GapTextBufferStrategy : ITextBufferStrategy
|
||||
{
|
||||
#if DEBUG
|
||||
int creatorThread = System.Threading.Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
void CheckThread()
|
||||
{
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId != creatorThread)
|
||||
throw new InvalidOperationException("GapTextBufferStategy is not thread-safe!");
|
||||
}
|
||||
#endif
|
||||
|
||||
char[] buffer = new char[0];
|
||||
string cachedContent;
|
||||
|
||||
int gapBeginOffset = 0;
|
||||
int gapEndOffset = 0;
|
||||
int gapLength = 0; // gapLength == gapEndOffset - gapBeginOffset
|
||||
|
||||
const int minGapLength = 128;
|
||||
const int maxGapLength = 2048;
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
return buffer.Length - gapLength;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetContent(string text)
|
||||
{
|
||||
if (text == null) {
|
||||
text = string.Empty;
|
||||
}
|
||||
cachedContent = text;
|
||||
buffer = text.ToCharArray();
|
||||
gapBeginOffset = gapEndOffset = gapLength = 0;
|
||||
}
|
||||
|
||||
public char GetCharAt(int offset)
|
||||
{
|
||||
#if DEBUG
|
||||
CheckThread();
|
||||
#endif
|
||||
|
||||
if (offset < 0 || offset >= Length) {
|
||||
throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset < " + Length.ToString());
|
||||
}
|
||||
|
||||
return offset < gapBeginOffset ? buffer[offset] : buffer[offset + gapLength];
|
||||
}
|
||||
|
||||
public string GetText(int offset, int length)
|
||||
{
|
||||
#if DEBUG
|
||||
CheckThread();
|
||||
#endif
|
||||
|
||||
if (offset < 0 || offset > Length) {
|
||||
throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + Length.ToString());
|
||||
}
|
||||
if (length < 0 || offset + length > Length) {
|
||||
throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + Length.ToString());
|
||||
}
|
||||
if (offset == 0 && length == Length) {
|
||||
if (cachedContent != null)
|
||||
return cachedContent;
|
||||
else
|
||||
return cachedContent = GetTextInternal(offset, length);
|
||||
} else {
|
||||
return GetTextInternal(offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
string GetTextInternal(int offset, int length)
|
||||
{
|
||||
int end = offset + length;
|
||||
|
||||
if (end < gapBeginOffset) {
|
||||
return new string(buffer, offset, length);
|
||||
}
|
||||
|
||||
if (offset > gapBeginOffset) {
|
||||
return new string(buffer, offset + gapLength, length);
|
||||
}
|
||||
|
||||
int block1Size = gapBeginOffset - offset;
|
||||
int block2Size = end - gapBeginOffset;
|
||||
|
||||
StringBuilder buf = new StringBuilder(block1Size + block2Size);
|
||||
buf.Append(buffer, offset, block1Size);
|
||||
buf.Append(buffer, gapEndOffset, block2Size);
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
public void Insert(int offset, string text)
|
||||
{
|
||||
Replace(offset, 0, text);
|
||||
}
|
||||
|
||||
public void Remove(int offset, int length)
|
||||
{
|
||||
Replace(offset, length, string.Empty);
|
||||
}
|
||||
|
||||
public void Replace(int offset, int length, string text)
|
||||
{
|
||||
if (text == null) {
|
||||
text = string.Empty;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
CheckThread();
|
||||
#endif
|
||||
|
||||
if (offset < 0 || offset > Length) {
|
||||
throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + Length.ToString());
|
||||
}
|
||||
if (length < 0 || offset + length > Length) {
|
||||
throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset+length <= " + Length.ToString());
|
||||
}
|
||||
|
||||
cachedContent = null;
|
||||
|
||||
// Math.Max is used so that if we need to resize the array
|
||||
// the new array has enough space for all old chars
|
||||
PlaceGap(offset, text.Length - length);
|
||||
gapEndOffset += length; // delete removed text
|
||||
text.CopyTo(0, buffer, gapBeginOffset, text.Length);
|
||||
gapBeginOffset += text.Length;
|
||||
gapLength = gapEndOffset - gapBeginOffset;
|
||||
if (gapLength > maxGapLength) {
|
||||
MakeNewBuffer(gapBeginOffset, minGapLength);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaceGap(int newGapOffset, int minRequiredGapLength)
|
||||
{
|
||||
if (gapLength < minRequiredGapLength) {
|
||||
// enlarge gap
|
||||
MakeNewBuffer(newGapOffset, minRequiredGapLength);
|
||||
} else {
|
||||
while (newGapOffset < gapBeginOffset) {
|
||||
buffer[--gapEndOffset] = buffer[--gapBeginOffset];
|
||||
}
|
||||
while (newGapOffset > gapBeginOffset) {
|
||||
buffer[gapBeginOffset++] = buffer[gapEndOffset++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MakeNewBuffer(int newGapOffset, int newGapLength)
|
||||
{
|
||||
if (newGapLength < minGapLength) newGapLength = minGapLength;
|
||||
|
||||
char[] newBuffer = new char[Length + newGapLength];
|
||||
if (newGapOffset < gapBeginOffset) {
|
||||
// gap is moving backwards
|
||||
|
||||
// first part:
|
||||
Array.Copy(buffer, 0, newBuffer, 0, newGapOffset);
|
||||
// moving middle part:
|
||||
Array.Copy(buffer, newGapOffset, newBuffer, newGapOffset + newGapLength, gapBeginOffset - newGapOffset);
|
||||
// last part:
|
||||
Array.Copy(buffer, gapEndOffset, newBuffer, newBuffer.Length - (buffer.Length - gapEndOffset), buffer.Length - gapEndOffset);
|
||||
} else {
|
||||
// gap is moving forwards
|
||||
// first part:
|
||||
Array.Copy(buffer, 0, newBuffer, 0, gapBeginOffset);
|
||||
// moving middle part:
|
||||
Array.Copy(buffer, gapEndOffset, newBuffer, gapBeginOffset, newGapOffset - gapBeginOffset);
|
||||
// last part:
|
||||
int lastPartLength = newBuffer.Length - (newGapOffset + newGapLength);
|
||||
Array.Copy(buffer, buffer.Length - lastPartLength, newBuffer, newGapOffset + newGapLength, lastPartLength);
|
||||
}
|
||||
|
||||
gapBeginOffset = newGapOffset;
|
||||
gapEndOffset = newGapOffset + newGapLength;
|
||||
gapLength = newGapLength;
|
||||
buffer = newBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to describe a sequence of characters that can be edited.
|
||||
/// </summary>
|
||||
public interface ITextBufferStrategy
|
||||
{
|
||||
/// <value>
|
||||
/// The current length of the sequence of characters that can be edited.
|
||||
/// </value>
|
||||
int Length {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a string of characters into the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// offset where to insert the string.
|
||||
/// </param>
|
||||
/// <param name="text">
|
||||
/// text to be inserted.
|
||||
/// </param>
|
||||
void Insert(int offset, string text);
|
||||
|
||||
/// <summary>
|
||||
/// Removes some portion of the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// offset of the remove.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// number of characters to remove.
|
||||
/// </param>
|
||||
void Remove(int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Replace some portion of the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// offset.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// number of characters to replace.
|
||||
/// </param>
|
||||
/// <param name="text">
|
||||
/// text to be replaced with.
|
||||
/// </param>
|
||||
void Replace(int offset, int length, string text);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a string of characters contained in the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// Offset into the sequence to fetch
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// number of characters to copy.
|
||||
/// </param>
|
||||
string GetText(int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific char of the sequence.
|
||||
/// </summary>
|
||||
/// <param name="offset">
|
||||
/// Offset of the char to get.
|
||||
/// </param>
|
||||
char GetCharAt(int offset);
|
||||
|
||||
/// <summary>
|
||||
/// This method sets the stored content.
|
||||
/// </summary>
|
||||
/// <param name="text">
|
||||
/// The string that represents the character sequence.
|
||||
/// </param>
|
||||
void SetContent(string text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple implementation of the ITextBuffer interface implemented using a
|
||||
/// string.
|
||||
/// Only for fall-back purposes.
|
||||
/// </summary>
|
||||
public class StringTextBufferStrategy : ITextBufferStrategy
|
||||
{
|
||||
string storedText = "";
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
return storedText.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int offset, string text)
|
||||
{
|
||||
if (text != null) {
|
||||
storedText = storedText.Insert(offset, text);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(int offset, int length)
|
||||
{
|
||||
storedText = storedText.Remove(offset, length);
|
||||
}
|
||||
|
||||
public void Replace(int offset, int length, string text)
|
||||
{
|
||||
Remove(offset, length);
|
||||
Insert(offset, text);
|
||||
}
|
||||
|
||||
public string GetText(int offset, int length)
|
||||
{
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
if (offset == 0 && length >= storedText.Length) {
|
||||
return storedText;
|
||||
}
|
||||
return storedText.Substring(offset, Math.Min(length, storedText.Length - offset));
|
||||
}
|
||||
|
||||
public char GetCharAt(int offset)
|
||||
{
|
||||
if (offset == Length) {
|
||||
return '\0';
|
||||
}
|
||||
return storedText[offset];
|
||||
}
|
||||
|
||||
public void SetContent(string text)
|
||||
{
|
||||
storedText = text;
|
||||
}
|
||||
|
||||
public StringTextBufferStrategy()
|
||||
{
|
||||
}
|
||||
|
||||
public static ITextBufferStrategy CreateTextBufferFromFile(string fileName)
|
||||
{
|
||||
if (!File.Exists(fileName)) {
|
||||
throw new System.IO.FileNotFoundException(fileName);
|
||||
}
|
||||
StringTextBufferStrategy s = new StringTextBufferStrategy();
|
||||
s.SetContent(Util.FileReader.ReadFileContent(fileName, Encoding.Default));
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs
Normal file
128
ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
|
||||
// <version>$Revision: 2658$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// A line/column position.
|
||||
/// Text editor lines/columns are counting from zero.
|
||||
/// </summary>
|
||||
public struct TextLocation : IComparable<TextLocation>, IEquatable<TextLocation>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents no text location (-1, -1).
|
||||
/// </summary>
|
||||
public static readonly TextLocation Empty = new TextLocation(-1, -1);
|
||||
|
||||
public TextLocation(int column, int line)
|
||||
{
|
||||
x = column;
|
||||
y = line;
|
||||
}
|
||||
|
||||
int x, y;
|
||||
|
||||
public int X {
|
||||
get { return x; }
|
||||
set { x = value; }
|
||||
}
|
||||
|
||||
public int Y {
|
||||
get { return y; }
|
||||
set { y = value; }
|
||||
}
|
||||
|
||||
public int Line {
|
||||
get { return y; }
|
||||
set { y = value; }
|
||||
}
|
||||
|
||||
public int Column {
|
||||
get { return x; }
|
||||
set { x = value; }
|
||||
}
|
||||
|
||||
public bool IsEmpty {
|
||||
get {
|
||||
return x <= 0 && y <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("(Line {1}, Col {0})", this.x, this.y);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return unchecked (87 * x.GetHashCode() ^ y.GetHashCode());
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is TextLocation)) return false;
|
||||
return (TextLocation)obj == this;
|
||||
}
|
||||
|
||||
public bool Equals(TextLocation other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
public static bool operator ==(TextLocation a, TextLocation b)
|
||||
{
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
public static bool operator !=(TextLocation a, TextLocation b)
|
||||
{
|
||||
return a.x != b.x || a.y != b.y;
|
||||
}
|
||||
|
||||
public static bool operator <(TextLocation a, TextLocation b)
|
||||
{
|
||||
if (a.y < b.y)
|
||||
return true;
|
||||
else if (a.y == b.y)
|
||||
return a.x < b.x;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool operator >(TextLocation a, TextLocation b)
|
||||
{
|
||||
if (a.y > b.y)
|
||||
return true;
|
||||
else if (a.y == b.y)
|
||||
return a.x > b.x;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool operator <=(TextLocation a, TextLocation b)
|
||||
{
|
||||
return !(a > b);
|
||||
}
|
||||
|
||||
public static bool operator >=(TextLocation a, TextLocation b)
|
||||
{
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
public int CompareTo(TextLocation other)
|
||||
{
|
||||
if (this == other)
|
||||
return 0;
|
||||
if (this < other)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
313
ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs
Normal file
313
ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Document
|
||||
{
|
||||
|
||||
public sealed class TextUtilities
|
||||
{
|
||||
/// <remarks>
|
||||
/// This function takes a string and converts the whitespace in front of
|
||||
/// it to tabs. If the length of the whitespace at the start of the string
|
||||
/// was not a whole number of tabs then there will still be some spaces just
|
||||
/// before the text starts.
|
||||
/// the output string will be of the form:
|
||||
/// 1. zero or more tabs
|
||||
/// 2. zero or more spaces (less than tabIndent)
|
||||
/// 3. the rest of the line
|
||||
/// </remarks>
|
||||
public static string LeadingWhiteSpaceToTabs(string line, int tabIndent) {
|
||||
StringBuilder sb = new StringBuilder(line.Length);
|
||||
int consecutiveSpaces = 0;
|
||||
int i = 0;
|
||||
for(i = 0; i < line.Length; i++) {
|
||||
if(line[i] == ' ') {
|
||||
consecutiveSpaces++;
|
||||
if(consecutiveSpaces == tabIndent) {
|
||||
sb.Append('\t');
|
||||
consecutiveSpaces = 0;
|
||||
}
|
||||
}
|
||||
else if(line[i] == '\t') {
|
||||
sb.Append('\t');
|
||||
// if we had say 3 spaces then a tab and tabIndent was 4 then
|
||||
// we would want to simply replace all of that with 1 tab
|
||||
consecutiveSpaces = 0;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i < line.Length) {
|
||||
sb.Append(line.Substring(i-consecutiveSpaces));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static bool IsLetterDigitOrUnderscore(char c)
|
||||
{
|
||||
if(!char.IsLetterOrDigit(c)) {
|
||||
return c == '_';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public enum CharacterType {
|
||||
LetterDigitOrUnderscore,
|
||||
WhiteSpace,
|
||||
Other
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// This method returns the expression before a specified offset.
|
||||
/// That method is used in code completion to determine the expression given
|
||||
/// to the parser for type resolve.
|
||||
/// </remarks>
|
||||
public static string GetExpressionBeforeOffset(TextArea textArea, int initialOffset)
|
||||
{
|
||||
IDocument document = textArea.Document;
|
||||
int offset = initialOffset;
|
||||
while (offset - 1 > 0) {
|
||||
switch (document.GetCharAt(offset - 1)) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '}':
|
||||
goto done;
|
||||
// offset = SearchBracketBackward(document, offset - 2, '{','}');
|
||||
// break;
|
||||
case ']':
|
||||
offset = SearchBracketBackward(document, offset - 2, '[',']');
|
||||
break;
|
||||
case ')':
|
||||
offset = SearchBracketBackward(document, offset - 2, '(',')');
|
||||
break;
|
||||
case '.':
|
||||
--offset;
|
||||
break;
|
||||
case '"':
|
||||
if (offset < initialOffset - 1) {
|
||||
return null;
|
||||
}
|
||||
return "\"\"";
|
||||
case '\'':
|
||||
if (offset < initialOffset - 1) {
|
||||
return null;
|
||||
}
|
||||
return "'a'";
|
||||
case '>':
|
||||
if (document.GetCharAt(offset - 2) == '-') {
|
||||
offset -= 2;
|
||||
break;
|
||||
}
|
||||
goto done;
|
||||
default:
|
||||
if (char.IsWhiteSpace(document.GetCharAt(offset - 1))) {
|
||||
--offset;
|
||||
break;
|
||||
}
|
||||
int start = offset - 1;
|
||||
if (!IsLetterDigitOrUnderscore(document.GetCharAt(start))) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
while (start > 0 && IsLetterDigitOrUnderscore(document.GetCharAt(start - 1))) {
|
||||
--start;
|
||||
}
|
||||
string word = document.GetText(start, offset - start).Trim();
|
||||
switch (word) {
|
||||
case "ref":
|
||||
case "out":
|
||||
case "in":
|
||||
case "return":
|
||||
case "throw":
|
||||
case "case":
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (word.Length > 0 && !IsLetterDigitOrUnderscore(word[0])) {
|
||||
goto done;
|
||||
}
|
||||
offset = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
done:
|
||||
//// simple exit fails when : is inside comment line or any other character
|
||||
//// we have to check if we got several ids in resulting line, which usually happens when
|
||||
//// id. is typed on next line after comment one
|
||||
//// Would be better if lexer would parse properly such expressions. However this will cause
|
||||
//// modifications in this area too - to get full comment line and remove it afterwards
|
||||
if (offset < 0)
|
||||
return string.Empty;
|
||||
|
||||
string resText=document.GetText(offset, textArea.Caret.Offset - offset ).Trim();
|
||||
int pos=resText.LastIndexOf('\n');
|
||||
if (pos>=0) {
|
||||
offset+=pos+1;
|
||||
//// whitespaces and tabs, which might be inside, will be skipped by trim below
|
||||
}
|
||||
string expression = document.GetText(offset, textArea.Caret.Offset - offset ).Trim();
|
||||
return expression;
|
||||
}
|
||||
|
||||
|
||||
public static CharacterType GetCharacterType(char c)
|
||||
{
|
||||
if(IsLetterDigitOrUnderscore(c))
|
||||
return CharacterType.LetterDigitOrUnderscore;
|
||||
if(char.IsWhiteSpace(c))
|
||||
return CharacterType.WhiteSpace;
|
||||
return CharacterType.Other;
|
||||
}
|
||||
|
||||
public static int GetFirstNonWSChar(IDocument document, int offset)
|
||||
{
|
||||
while (offset < document.TextLength && char.IsWhiteSpace(document.GetCharAt(offset))) {
|
||||
++offset;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int FindWordEnd(IDocument document, int offset)
|
||||
{
|
||||
LineSegment line = document.GetLineSegmentForOffset(offset);
|
||||
int endPos = line.Offset + line.Length;
|
||||
while (offset < endPos && IsLetterDigitOrUnderscore(document.GetCharAt(offset))) {
|
||||
++offset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int FindWordStart(IDocument document, int offset)
|
||||
{
|
||||
LineSegment line = document.GetLineSegmentForOffset(offset);
|
||||
int lineOffset = line.Offset;
|
||||
while (offset > lineOffset && IsLetterDigitOrUnderscore(document.GetCharAt(offset - 1))) {
|
||||
--offset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
// go forward to the start of the next word
|
||||
// if the cursor is at the start or in the middle of a word we move to the end of the word
|
||||
// and then past any whitespace that follows it
|
||||
// if the cursor is at the start or in the middle of some whitespace we move to the start of the
|
||||
// next word
|
||||
public static int FindNextWordStart(IDocument document, int offset)
|
||||
{
|
||||
int originalOffset = offset;
|
||||
LineSegment line = document.GetLineSegmentForOffset(offset);
|
||||
int endPos = line.Offset + line.Length;
|
||||
// lets go to the end of the word, whitespace or operator
|
||||
CharacterType t = GetCharacterType(document.GetCharAt(offset));
|
||||
while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == t) {
|
||||
++offset;
|
||||
}
|
||||
|
||||
// now we're at the end of the word, lets find the start of the next one by skipping whitespace
|
||||
while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == CharacterType.WhiteSpace) {
|
||||
++offset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
// go back to the start of the word we are on
|
||||
// if we are already at the start of a word or if we are in whitespace, then go back
|
||||
// to the start of the previous word
|
||||
public static int FindPrevWordStart(IDocument document, int offset)
|
||||
{
|
||||
int originalOffset = offset;
|
||||
if (offset > 0) {
|
||||
LineSegment line = document.GetLineSegmentForOffset(offset);
|
||||
CharacterType t = GetCharacterType(document.GetCharAt(offset - 1));
|
||||
while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t) {
|
||||
--offset;
|
||||
}
|
||||
|
||||
// if we were in whitespace, and now we're at the end of a word or operator, go back to the beginning of it
|
||||
if(t == CharacterType.WhiteSpace && offset > line.Offset) {
|
||||
t = GetCharacterType(document.GetCharAt(offset - 1));
|
||||
while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t) {
|
||||
--offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static string GetLineAsString(IDocument document, int lineNumber)
|
||||
{
|
||||
LineSegment line = document.GetLineSegment(lineNumber);
|
||||
return document.GetText(line.Offset, line.Length);
|
||||
}
|
||||
|
||||
public static int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket)
|
||||
{
|
||||
return document.FormattingStrategy.SearchBracketBackward(document, offset, openBracket, closingBracket);
|
||||
}
|
||||
|
||||
public static int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket)
|
||||
{
|
||||
return document.FormattingStrategy.SearchBracketForward(document, offset, openBracket, closingBracket);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Returns true, if the line lineNumber is empty or filled with whitespaces.
|
||||
/// </remarks>
|
||||
public static bool IsEmptyLine(IDocument document, int lineNumber)
|
||||
{
|
||||
return IsEmptyLine(document, document.GetLineSegment(lineNumber));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Returns true, if the line lineNumber is empty or filled with whitespaces.
|
||||
/// </remarks>
|
||||
public static bool IsEmptyLine(IDocument document, LineSegment line)
|
||||
{
|
||||
for (int i = line.Offset; i < line.Offset + line.Length; ++i) {
|
||||
char ch = document.GetCharAt(i);
|
||||
if (!char.IsWhiteSpace(ch)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsWordPart(char ch)
|
||||
{
|
||||
return IsLetterDigitOrUnderscore(ch) || ch == '.';
|
||||
}
|
||||
|
||||
public static string GetWordAt(IDocument document, int offset)
|
||||
{
|
||||
if (offset < 0 || offset >= document.TextLength - 1 || !IsWordPart(document.GetCharAt(offset))) {
|
||||
return string.Empty;
|
||||
}
|
||||
int startOffset = offset;
|
||||
int endOffset = offset;
|
||||
while (startOffset > 0 && IsWordPart(document.GetCharAt(startOffset - 1))) {
|
||||
--startOffset;
|
||||
}
|
||||
|
||||
while (endOffset < document.TextLength - 1 && IsWordPart(document.GetCharAt(endOffset + 1))) {
|
||||
++endOffset;
|
||||
}
|
||||
|
||||
Debug.Assert(endOffset >= startOffset);
|
||||
return document.GetText(startOffset, endOffset - startOffset + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
ICSharpCode.TextEditor/Project/Src/Gui/AbstractMargin.cs
Normal file
115
ICSharpCode.TextEditor/Project/Src/Gui/AbstractMargin.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
86
ICSharpCode.TextEditor/Project/Src/Gui/BracketHighlighter.cs
Normal file
86
ICSharpCode.TextEditor/Project/Src/Gui/BracketHighlighter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
ICSharpCode.TextEditor/Project/Src/Gui/BrushRegistry.cs
Normal file
65
ICSharpCode.TextEditor/Project/Src/Gui/BrushRegistry.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
510
ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs
Normal file
510
ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
190
ICSharpCode.TextEditor/Project/Src/Gui/DrawableLine.cs
Normal file
190
ICSharpCode.TextEditor/Project/Src/Gui/DrawableLine.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
276
ICSharpCode.TextEditor/Project/Src/Gui/FoldMargin.cs
Normal file
276
ICSharpCode.TextEditor/Project/Src/Gui/FoldMargin.cs
Normal 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
|
||||
}
|
||||
}
|
||||
159
ICSharpCode.TextEditor/Project/Src/Gui/GutterMargin.cs
Normal file
159
ICSharpCode.TextEditor/Project/Src/Gui/GutterMargin.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
ICSharpCode.TextEditor/Project/Src/Gui/HRuler.cs
Normal file
54
ICSharpCode.TextEditor/Project/Src/Gui/HRuler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
254
ICSharpCode.TextEditor/Project/Src/Gui/IconBarMargin.cs
Normal file
254
ICSharpCode.TextEditor/Project/Src/Gui/IconBarMargin.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
179
ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs
Normal file
179
ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
947
ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs
Normal file
947
ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
463
ICSharpCode.TextEditor/Project/Src/Gui/TextAreaControl.cs
Normal file
463
ICSharpCode.TextEditor/Project/Src/Gui/TextAreaControl.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
492
ICSharpCode.TextEditor/Project/Src/Gui/TextAreaMouseHandler.cs
Normal file
492
ICSharpCode.TextEditor/Project/Src/Gui/TextAreaMouseHandler.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
85
ICSharpCode.TextEditor/Project/Src/Gui/TextAreaUpdate.cs
Normal file
85
ICSharpCode.TextEditor/Project/Src/Gui/TextAreaUpdate.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
396
ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs
Normal file
396
ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs
Normal 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
|
||||
}
|
||||
}
|
||||
759
ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs
Normal file
759
ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
541
ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlEx.cs
Normal file
541
ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlEx.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1096
ICSharpCode.TextEditor/Project/Src/Gui/TextView.cs
Normal file
1096
ICSharpCode.TextEditor/Project/Src/Gui/TextView.cs
Normal file
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
Reference in New Issue
Block a user