4
// Mike Krüger <mkrueger@novell.com>
6
// Copyright (c) 2007 Novell, Inc (http://www.novell.com)
8
// Permission is hereby granted, free of charge, to any person obtaining a copy
9
// of this software and associated documentation files (the "Software"), to deal
10
// in the Software without restriction, including without limitation the rights
11
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
// copies of the Software, and to permit persons to whom the Software is
13
// furnished to do so, subject to the following conditions:
15
// The above copyright notice and this permission notice shall be included in
16
// all copies or substantial portions of the Software.
18
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
using System.Collections.Generic;
30
using System.Collections.ObjectModel;
31
using System.Diagnostics;
33
using Mono.TextEditor.Highlighting;
36
namespace Mono.TextEditor
38
public class Document : AbstractBuffer, IDisposable
41
LineSplitter splitter;
42
SyntaxMode syntaxMode = null;
48
ReadOnlyCheckDelegate readOnlyCheckDelegate;
51
/// The eol mark used in this document - it's taken from the first line in the document,
52
/// if no eol mark is found it's using the default (Environment.NewLine).
53
/// The value is saved, even when all lines are deleted the eol marker will still be the old eol marker.
55
public string EolMarker {
57
if (eol == null && splitter.LineCount > 0) {
58
LineSegment line = splitter.Get (0);
59
if (line.DelimiterLength > 0)
60
eol = buffer.GetTextAt (line.EditableLength, line.DelimiterLength);
62
return !String.IsNullOrEmpty (eol) ? eol : Environment.NewLine;
66
public string MimeType {
72
if (this.SyntaxMode == null)
73
this.SyntaxMode = SyntaxModeService.GetSyntaxMode (value);
77
public string FileName {
86
public SyntaxMode SyntaxMode {
92
UpdateHighlighting ();
98
buffer = new GapBuffer ();
99
splitter = new LineSplitter (buffer);
101
splitter.LineSegmentTree.LineChanged += delegate (object sender, LineEventArgs args) {
102
if (LineChanged != null)
103
LineChanged (this, args);
105
/* splitter.LineSegmentTree.LineInserted += delegate (object sender, LineEventArgs args) {
106
if (LineInserted != null)
107
LineInserted (this, args);
111
public event EventHandler<LineEventArgs> LineChanged;
112
// public event EventHandler<LineEventArgs> LineInserted;
115
public override void Dispose ()
117
buffer = buffer.Kill ();
118
splitter = splitter.Kill ();
119
if (undoStack != null) {
123
if (redoStack != null) {
127
currentAtomicOperation = null;
129
/*if (foldSegments != null) {
130
foldSegments.Clear ();
135
#region Buffer implementation
136
public override int Length {
138
return this.buffer.Length;
142
public override string Text {
144
return this.buffer.Text;
148
int oldLength = Length;
149
ReplaceEventArgs args = new ReplaceEventArgs (0, oldLength, value);
150
this.OnTextReplacing (args);
151
this.buffer.Text = value;
152
splitter.TextReplaced (this, args);
153
UpdateHighlighting ();
154
this.OnTextReplaced (args);
158
void UpdateHighlighting ()
160
if (this.syntaxMode != null) {
161
Mono.TextEditor.Highlighting.SyntaxModeService.StartUpdate (this, this.syntaxMode, 0, buffer.Length);
162
// Mono.TextEditor.Highlighting.SyntaxModeService.WaitForUpdate ();
166
public System.IO.TextReader OpenTextReader ()
168
return new BufferedTextReader (this.buffer);
171
public override void Replace (int offset, int count, string value)
173
InterruptFoldWorker ();
174
// Mono.TextEditor.Highlighting.SyntaxModeService.WaitForUpdate (true);
175
// Debug.Assert (count >= 0);
176
// Debug.Assert (0 <= offset && offset + count <= Length);
177
int oldLineCount = this.LineCount;
178
ReplaceEventArgs args = new ReplaceEventArgs (offset, count, value);
179
OnTextReplacing (args);
182
int endOffset = offset + count;
183
foldSegments = new List<FoldSegment> (foldSegments.Where (s => (s.Offset < offset || s.Offset >= endOffset) &&
184
(s.EndOffset <= offset || s.EndOffset >= endOffset)));
186
UndoOperation operation = null;
188
operation = new UndoOperation (args, GetTextAt (offset, count));
189
if (currentAtomicOperation != null) {
190
currentAtomicOperation.Add (operation);
193
undoStack.Push (operation);
194
OnEndUndo (operation);
196
foreach (UndoOperation redoOp in redoStack) {
202
buffer.Replace (offset, count, value);
203
OnTextReplaced (args);
204
splitter.TextReplaced (this, args);
206
if (operation != null)
207
operation.Setup (this, args);
208
if (this.syntaxMode != null)
209
Mono.TextEditor.Highlighting.SyntaxModeService.StartUpdate (this, this.syntaxMode, offset, value != null ? offset + value.Length : offset + count);
210
if (oldLineCount != LineCount)
211
this.CommitLineToEndUpdate (this.OffsetToLocation (offset).Line);
214
public string GetTextBetween (int startOffset, int endOffset)
216
return buffer.GetTextAt (startOffset, endOffset - startOffset);
219
public override string GetTextAt (int offset, int count)
221
return buffer.GetTextAt (offset, count);
224
public override char GetCharAt (int offset)
226
return buffer.GetCharAt (offset);
229
protected virtual void OnTextReplaced (ReplaceEventArgs args)
231
if (TextReplaced != null)
232
TextReplaced (this, args);
234
public event EventHandler<ReplaceEventArgs> TextReplaced;
236
protected virtual void OnTextReplacing (ReplaceEventArgs args)
238
if (TextReplacing != null)
239
TextReplacing (this, args);
241
public event EventHandler<ReplaceEventArgs> TextReplacing;
245
#region Line Splitter operations
246
public IEnumerable<LineSegment> Lines {
248
return splitter.Lines;
252
public int LineCount {
254
return splitter.LineCount;
258
public int LocationToOffset (int line, int column)
260
return LocationToOffset (new DocumentLocation (line, column));
263
public int LocationToOffset (DocumentLocation location)
265
if (location.Line >= this.splitter.LineCount)
267
LineSegment line = GetLine (location.Line);
268
return System.Math.Min (Length, line.Offset + System.Math.Min (line.EditableLength, location.Column));
271
public DocumentLocation OffsetToLocation (int offset)
273
int lineNr = splitter.OffsetToLineNumber (offset);
275
return DocumentLocation.Empty;
276
LineSegment line = GetLine (lineNr);
277
return new DocumentLocation (lineNr, System.Math.Min (line.Length, offset - line.Offset));
280
public LineSegment GetLine (int lineNumber)
282
return splitter.Get (lineNumber);
285
public LineSegment GetLineByOffset (int offset)
287
return splitter.GetLineByOffset (offset);
290
public int OffsetToLineNumber (int offset)
292
return splitter.OffsetToLineNumber (offset);
297
#region Undo/Redo operations
298
public class UndoOperation : IDisposable
301
ReplaceEventArgs args;
303
int startOffset, length;
304
public virtual string Text {
309
public virtual ReplaceEventArgs Args {
314
protected UndoOperation()
318
public virtual bool ChangedLine (LineSegment line)
320
if (Args == null || line == null)
322
return startOffset <= line.Offset - line.DelimiterLength && line.Offset <= startOffset + length
323
|| line.Offset - line.DelimiterLength <= startOffset && startOffset <= line.Offset + line.EditableLength
325
; //line.Contains (Args.Offset);
328
public UndoOperation (ReplaceEventArgs args, string text)
333
static int GetDelta (ReplaceEventArgs args)
335
int result = -args.Count;
336
if (!String.IsNullOrEmpty (args.Value))
337
result += args.Value.Length;
340
internal void Setup (Document doc, ReplaceEventArgs args)
343
doc.TextReplaced += TextReplaced;
345
this.startOffset = args.Offset;
346
if (!String.IsNullOrEmpty (args.Value))
347
this.length = args.Value.Length;
350
void TextReplaced (object sender, ReplaceEventArgs args)
352
if (args.Offset < startOffset) {
353
startOffset = System.Math.Max (startOffset + GetDelta(args), args.Offset);
354
} else if (args.Offset < startOffset + length) {
355
length = System.Math.Max (length + GetDelta(args), startOffset - args.Offset);
359
public virtual void Dispose ()
363
doc.TextReplaced -= TextReplaced;
366
if (Disposed != null)
367
Disposed (this, EventArgs.Empty);
370
public virtual void Undo (Document doc)
372
if (args.Value != null && args.Value.Length > 0)
373
doc.Remove (args.Offset, args.Value.Length);
374
if (!String.IsNullOrEmpty (text))
375
doc.Insert (args.Offset, text);
379
public virtual void Redo (Document doc)
381
doc.Replace (args.Offset, args.Count, args.Value);
385
protected virtual void OnUndoDone ()
387
if (UndoDone != null)
388
UndoDone (this, EventArgs.Empty);
390
public event EventHandler UndoDone;
392
protected virtual void OnRedoDone ()
394
if (RedoDone != null)
395
RedoDone (this, EventArgs.Empty);
397
public event EventHandler RedoDone;
399
public event EventHandler Disposed;
402
class AtomicUndoOperation : UndoOperation
404
protected List<UndoOperation> operations = new List<UndoOperation> ();
406
public List<UndoOperation> Operations {
412
public override string Text {
417
public override ReplaceEventArgs Args {
423
public override void Dispose ()
425
if (operations != null) {
426
foreach (UndoOperation operation in operations) {
427
operation.Dispose ();
434
public override bool ChangedLine (LineSegment line)
436
foreach (UndoOperation op in Operations) {
437
if (op.ChangedLine (line))
443
public void Add (UndoOperation operation)
445
operations.Add (operation);
448
public override void Undo (Document doc)
450
for (int i = operations.Count - 1; i >= 0; i--) {
451
operations[i].Undo (doc);
456
public override void Redo (Document doc)
458
foreach (UndoOperation operation in this.operations) {
459
operation.Redo (doc);
465
class KeyboardStackUndo : AtomicUndoOperation
467
bool isClosed = false;
469
public bool IsClosed {
478
public override string Text {
480
return operations.Count > 0 ? operations [operations.Count - 1].Text : "";
484
public override ReplaceEventArgs Args {
486
return operations.Count > 0 ? operations [operations.Count - 1].Args : null;
491
bool isInUndo = false;
492
Stack<UndoOperation> undoStack = new Stack<UndoOperation> ();
493
Stack<UndoOperation> redoStack = new Stack<UndoOperation> ();
494
AtomicUndoOperation currentAtomicOperation = null;
496
public bool CanUndo {
498
return this.undoStack.Count > 0 || currentAtomicOperation != null;
502
UndoOperation[] savePoint = null;
503
public bool IsDirty {
505
if (this.currentAtomicOperation != null)
507
if (savePoint == null)
509
if (undoStack.Count != savePoint.Length)
511
UndoOperation[] currentStack = undoStack.ToArray ();
512
for (int i = 0; i < currentStack.Length; i++) {
513
if (savePoint[i] != currentStack[i])
520
public enum LineState {
526
public LineState GetLineState (int lineNumber)
528
LineSegment line = GetLine (lineNumber);
529
foreach (UndoOperation op in undoStack) {
530
if (op.ChangedLine (line)) {
531
if (savePoint != null) {
532
foreach (UndoOperation savedUndo in savePoint) {
534
return LineState.Changed;
537
return LineState.Dirty;
540
return LineState.Unchanged;
545
/// Marks the document not dirty at this point (should be called after save).
547
public void SetNotDirtyState ()
549
OptimizeTypedUndo ();
550
if (undoStack.Count > 0 && undoStack.Peek () is KeyboardStackUndo)
551
((KeyboardStackUndo)undoStack.Peek ()).IsClosed = true;
552
savePoint = undoStack.ToArray ();
553
this.CommitUpdateAll ();
556
public void OptimizeTypedUndo ()
558
if (undoStack.Count == 0)
560
UndoOperation top = undoStack.Pop ();
561
if (top.Args == null || top.Args.Value == null || top.Args.Value.Length != 1 || (top is KeyboardStackUndo && ((KeyboardStackUndo)top).IsClosed)) {
562
undoStack.Push (top);
565
if (undoStack.Count == 0 || !(undoStack.Peek () is KeyboardStackUndo))
566
undoStack.Push (new KeyboardStackUndo ());
567
KeyboardStackUndo keyUndo = (KeyboardStackUndo)undoStack.Pop ();
568
if (keyUndo.IsClosed) {
569
undoStack.Push (keyUndo);
570
keyUndo = new KeyboardStackUndo ();
572
if (keyUndo.Args != null && keyUndo.Args.Offset + 1 != top.Args.Offset) {
573
keyUndo.IsClosed = true;
574
undoStack.Push (keyUndo);
575
keyUndo = new KeyboardStackUndo ();
578
undoStack.Push (keyUndo);
583
if (undoStack.Count <= 0)
586
UndoOperation chunk = undoStack.Pop ();
587
redoStack.Push (chunk);
590
this.RequestUpdate (new UpdateAll ());
591
this.CommitDocumentUpdate ();
594
public bool CanRedo {
596
return this.redoStack.Count > 0;
602
if (redoStack.Count <= 0)
605
UndoOperation chunk = redoStack.Pop ();
606
undoStack.Push (chunk);
609
this.RequestUpdate (new UpdateAll ());
610
this.CommitDocumentUpdate ();
613
public void BeginAtomicUndo ()
615
if (currentAtomicOperation == null) {
616
Debug.Assert (atomicUndoLevel == 0);
617
currentAtomicOperation = new AtomicUndoOperation ();
623
public void EndAtomicUndo ()
626
Debug.Assert (atomicUndoLevel >= 0);
628
if (atomicUndoLevel == 0 && currentAtomicOperation != null) {
629
if (currentAtomicOperation.Operations.Count > 1) {
630
undoStack.Push (currentAtomicOperation);
631
OnEndUndo (currentAtomicOperation);
633
if (currentAtomicOperation.Operations.Count > 0) {
634
undoStack.Push (currentAtomicOperation.Operations [0]);
635
OnEndUndo (currentAtomicOperation.Operations [0]);
640
currentAtomicOperation = null;
644
protected virtual void OnBeginUndo ()
646
if (BeginUndo != null)
647
BeginUndo (this, EventArgs.Empty);
650
protected virtual void OnEndUndo (UndoOperation undo)
653
EndUndo (this, undo);
656
public delegate void UndoOperationHandler (object sender, UndoOperation operation);
658
public event EventHandler BeginUndo;
659
public event UndoOperationHandler EndUndo;
663
class FoldSegmentTreeNode : IComparable<FoldSegmentTreeNode>
665
public FoldSegment FoldSegment {
670
public IEnumerable<FoldSegment> FoldSegments {
672
return Traverse (x => true);
676
IEnumerable<FoldSegment> Traverse (Predicate<FoldSegment> includeChilds)
678
Stack<FoldSegmentTreeNode> stack = new Stack<FoldSegmentTreeNode> ();
680
while (stack.Count > 0) {
681
FoldSegmentTreeNode node = stack.Pop ();
682
if (node.FoldSegment != null)
683
yield return node.FoldSegment;
684
if (node.FoldSegment == null || includeChilds (node.FoldSegment)) {
685
for (int i = node.childNodes.Count - 1; i >= 0; i--) {
686
stack.Push (node.childNodes[i]);
692
List<FoldSegmentTreeNode> childNodes = new List<FoldSegmentTreeNode> ();
693
public List<FoldSegmentTreeNode> ChildNodes {
699
public FoldSegmentTreeNode () : this (null)
703
public FoldSegmentTreeNode (FoldSegment foldSegment)
705
this.FoldSegment = foldSegment;
708
delegate int Comparer (int idx);
710
public int CompareTo (FoldSegmentTreeNode node)
712
return FoldSegment.Offset.CompareTo (node.FoldSegment.Offset);
715
public void AddSegment (FoldSegment segment)
719
int segmentOffset = segment.Offset;
720
for (int i = 0; i < childNodes.Count; i++) {
721
FoldSegmentTreeNode cur = childNodes [i];
722
if (cur.FoldSegment.Contains (segmentOffset)) {
723
cur.AddSegment (segment);
727
childNodes.Add (new FoldSegmentTreeNode (segment));
731
public IEnumerable<FoldSegment> GetFoldingsFromOffset (int offset)
733
// return FoldSegments.Where (s => s.Offset <= offset && offset <= s.EndOffset);
735
Stack<FoldSegmentTreeNode> stack = new Stack<FoldSegmentTreeNode> ();
737
while (stack.Count > 0) {
738
FoldSegmentTreeNode node = stack.Pop ();
739
if (node.FoldSegment != null && node.FoldSegment.Offset < offset && offset < node.FoldSegment.EndOffset)
740
yield return node.FoldSegment;
742
for (int i = node.childNodes.Count - 1; i >= 0; i--) {
743
FoldSegmentTreeNode child = node.childNodes[i];
749
public IEnumerable<FoldSegment> GetFoldingContaining (LineSegment line)
751
// return FoldSegments.Where (s => s.StartLine.Offset <= line.Offset && line.Offset <= s.EndLine.Offset);
753
Stack<FoldSegmentTreeNode> stack = new Stack<FoldSegmentTreeNode> ();
755
while (stack.Count > 0) {
756
FoldSegmentTreeNode node = stack.Pop ();
757
if (node.FoldSegment != null && node.FoldSegment.StartLine.Offset <= line.Offset && line.Offset <= node.FoldSegment.EndLine.Offset)
758
yield return node.FoldSegment;
759
int start = node.childNodes.FindIndex (delegate (FoldSegmentTreeNode s) {
760
return s.FoldSegment.StartLine.Offset <= line.Offset && line.Offset <= s.FoldSegment.EndLine.Offset;
763
for (int i = node.childNodes.Count - 1; i >= start; i--) {
764
FoldSegmentTreeNode child = node.childNodes[i];
765
if (child.FoldSegment.StartLine.Offset <= line.Offset)
772
public IEnumerable<FoldSegment> GetStartFoldings (LineSegment line)
774
// return FoldSegments.Where (s => s.StartLine.Offset == line.Offset);
776
foreach (FoldSegment segment in GetFoldingContaining (line)) {
777
if (segment.StartLine.Offset == line.Offset)
778
yield return segment;
779
if (segment.StartLine.Offset > line.Offset)
784
public int LogicalToVisualLine (Document doc, int logicalLine)
786
int result = logicalLine;
787
LineSegment line = doc.GetLine (result);
790
foreach (FoldSegment segment in Traverse (x => !(x.IsFolded && x.StartLine.Offset < line.Offset))) {
791
if (segment.IsFolded && segment.StartLine.Offset < line.Offset) {
792
result -= doc.GetLineCount (segment);
794
if (segment.StartLine.Offset > line.Offset)
800
public override string ToString ()
802
return string.Format("[FoldSegmentTreeNode: FoldSegment={0}, FoldSegments={1}, ChildNodes={2}]", FoldSegment, FoldSegments, ChildNodes);
805
public int VisualToLogicalLine (Document doc, int visualLineNumber)
807
int result = visualLineNumber;
808
LineSegment line = doc.GetLine (result);
811
foreach (FoldSegment segment in Traverse (x => !(x.IsFolded && x.StartLine.Offset < line.Offset))) {
812
if (segment.IsFolded && segment.StartLine.Offset < line.Offset) {
813
result += doc.GetLineCount (segment);
814
line = doc.GetLine (result);
818
if (segment.StartLine.Offset > line.Offset)
825
FoldSegmentTreeNode foldSegmentTree = new FoldSegmentTreeNode ();
827
public bool HasFoldSegments {
829
return foldSegmentTree.FoldSegments.Any ();
833
public IEnumerable<FoldSegment> FoldSegments {
835
return foldSegmentTree.FoldSegments;
839
class FoldSegmentWorkerThread : WorkerThread
842
List<FoldSegment> newSegments;
844
public FoldSegmentWorkerThread (Document doc, List<FoldSegment> newSegments)
847
this.newSegments = newSegments;
850
protected override void InnerRun ()
853
foreach (FoldSegment foldSegment in newSegments) {
856
LineSegment startLine = doc.splitter.GetLineByOffset (foldSegment.Offset);
857
LineSegment endLine = doc.splitter.GetLineByOffset (foldSegment.EndOffset);
858
foldSegment.EndColumn = foldSegment.EndOffset - endLine.Offset;
859
foldSegment.Column = foldSegment.Offset - startLine.Offset;
860
foldSegment.EndLine = endLine;
861
foldSegment.StartLine = startLine;
863
FoldSegmentTreeNode newFoldSegmentTree = new FoldSegmentTreeNode ();
864
foreach (FoldSegment foldSegment in newSegments) {
865
newFoldSegmentTree.AddSegment (foldSegment);
867
List<FoldSegment> oldSegments = new List<FoldSegment> (doc.foldSegmentTree.FoldSegments);
870
while (i < oldSegments.Count && j < newSegments.Count) {
873
int cmp = oldSegments[i].CompareTo (newSegments [j]);
875
if (newSegments[j].Length == oldSegments[i].Length)
876
newSegments[j].IsFolded = oldSegments[i].IsFolded;
878
} else if (cmp > 0) {
885
while (i < oldSegments.Count) {
886
newFoldSegmentTree.AddSegment (oldSegments [i]);
890
GLib.Timeout.Add (0, delegate {
891
// bool needsUpdate = doc.foldSegments.Count != newSegments.Count;
892
doc.foldSegmentTree = newFoldSegmentTree;
893
// if (needsUpdate) {
894
doc.RequestUpdate (new UpdateAll ());
895
doc.CommitDocumentUpdate ();
903
readonly object syncObject = new object();
904
FoldSegmentWorkerThread foldSegmentWorkerThread = null;
906
public void UpdateFoldSegments (List<FoldSegment> newSegments)
908
if (newSegments == null) {
913
if (foldSegmentWorkerThread != null)
914
foldSegmentWorkerThread.Stop ();
916
foldSegmentWorkerThread = new FoldSegmentWorkerThread (this, newSegments);
917
foldSegmentWorkerThread.Start ();
921
void InterruptFoldWorker ()
924
if (foldSegmentWorkerThread != null) {
925
if (!foldSegmentWorkerThread.IsStopping) {
926
foldSegmentWorkerThread.Stop ();
928
foldSegmentWorkerThread.WaitForFinish ();
929
foldSegmentWorkerThread = null;
934
public void ClearFoldSegments ()
937
if (foldSegmentWorkerThread != null)
938
foldSegmentWorkerThread.Stop ();
939
foldSegmentTree = new FoldSegmentTreeNode ();
944
public IEnumerable<FoldSegment> GetFoldingsFromOffset (int offset)
946
if (offset < 0 || offset >= Length)
947
return new FoldSegment[0];
948
return foldSegmentTree.GetFoldingsFromOffset (offset);
951
public IEnumerable<FoldSegment> GetFoldingContaining (int lineNumber)
953
return GetFoldingContaining (this.GetLine (lineNumber));
956
public IEnumerable<FoldSegment> GetFoldingContaining (LineSegment line)
959
return new FoldSegment[0];
960
return foldSegmentTree.GetFoldingContaining (line);
963
public IEnumerable<FoldSegment> GetStartFoldings (int lineNumber)
965
return GetStartFoldings (this.GetLine (lineNumber));
968
public IEnumerable<FoldSegment> GetStartFoldings (LineSegment line)
971
return new FoldSegment[0];
972
return foldSegmentTree.GetStartFoldings (line);
975
public IEnumerable<FoldSegment> GetEndFoldings (int lineNumber)
977
return GetStartFoldings (this.GetLine (lineNumber));
980
public IEnumerable<FoldSegment> GetEndFoldings (LineSegment line)
982
foreach (FoldSegment segment in GetFoldingContaining (line)) {
983
if (segment.EndLine.Offset == line.Offset)
984
yield return segment;
988
public int GetLineCount (FoldSegment segment)
990
return OffsetToLineNumber(segment.EndLine.Offset) - OffsetToLineNumber(segment.StartLine.Offset);
995
public void AddMarker (int lineNumber, TextMarker marker)
997
AddMarker (this.GetLine (lineNumber), marker);
999
public void AddMarker (LineSegment line, TextMarker marker)
1001
line.AddMarker (marker);
1002
this.CommitLineUpdate (line);
1005
public void RemoveMarker (int lineNumber, TextMarker marker)
1007
RemoveMarker (this.GetLine (lineNumber), marker);
1009
public void RemoveMarker (LineSegment line, TextMarker marker)
1011
line.RemoveMarker (marker);
1012
this.CommitLineUpdate (line);
1014
public void RemoveMarker (int lineNumber, Type type)
1016
RemoveMarker (this.GetLine (lineNumber), type);
1018
public void RemoveMarker (LineSegment line, Type type)
1020
line.RemoveMarker (type);
1021
this.CommitLineUpdate (line);
1024
public bool Contains (int offset)
1026
return new Segment (0, Length).Contains (offset);
1029
public bool Contains (ISegment segment)
1031
return new Segment (0, Length).Contains (segment);
1034
public int VisualToLogicalLine (int visualLineNumber)
1036
if (visualLineNumber <= 0)
1038
if (visualLineNumber >= LineCount)
1039
return visualLineNumber;
1040
return this.foldSegmentTree.VisualToLogicalLine (this, visualLineNumber);
1043
public DocumentLocation LogicalToVisualLocation (TextEditorData editor, DocumentLocation location)
1045
int line = LogicalToVisualLine (location.Line);
1046
LineSegment lineSegment = this.GetLine (location.Line);
1047
int column = lineSegment != null ? lineSegment.GetVisualColumn (editor, this, location.Column) : location.Column;
1048
return new DocumentLocation (line, column);
1051
public int LogicalToVisualLine (int logicalLineNumber)
1053
return this.foldSegmentTree.LogicalToVisualLine (this, logicalLineNumber);
1056
#region Update logic
1057
List<DocumentUpdateRequest> updateRequests = new List<DocumentUpdateRequest> ();
1059
public IEnumerable<DocumentUpdateRequest> UpdateRequests {
1061
return updateRequests;
1064
// Use CanEdit (int lineNumber) instead for getting a request
1065
// if a part of a document can be read. ReadOnly should generally not be used
1066
// for deciding, if a document is readonly or not.
1067
public bool ReadOnly {
1076
public ReadOnlyCheckDelegate ReadOnlyCheckDelegate {
1077
get { return readOnlyCheckDelegate; }
1078
set { readOnlyCheckDelegate = value; }
1082
public void RequestUpdate (DocumentUpdateRequest request)
1085
updateRequests.Add (request);
1089
public void CommitDocumentUpdate ()
1092
if (DocumentUpdated != null)
1093
DocumentUpdated (this, EventArgs.Empty);
1094
updateRequests.Clear ();
1098
public void CommitLineToEndUpdate (int line)
1100
RequestUpdate (new LineToEndUpdate (line));
1101
CommitDocumentUpdate ();
1104
public void CommitLineUpdate (int line)
1106
RequestUpdate (new LineUpdate (line));
1107
CommitDocumentUpdate ();
1110
public void CommitLineUpdate (LineSegment line)
1112
CommitLineUpdate (this.OffsetToLineNumber (line.Offset));
1115
public void CommitUpdateAll ()
1117
RequestUpdate (new UpdateAll ());
1118
CommitDocumentUpdate ();
1121
public void CommitMultipleLineUpdate (int start, int end)
1123
RequestUpdate (new MultipleLineUpdate (start, end));
1124
CommitDocumentUpdate ();
1127
public event EventHandler DocumentUpdated;
1130
#region Helper functions
1131
public const string openBrackets = "([{<";
1132
public const string closingBrackets = ")]}>";
1134
public static bool IsBracket (char ch)
1136
return (openBrackets + closingBrackets).IndexOf (ch) >= 0;
1139
public static bool IsWordSeparator (char ch)
1141
return Char.IsWhiteSpace (ch) || (Char.IsPunctuation (ch) && ch != '_');
1144
public bool IsWholeWordAt (int offset, int length)
1146
return (offset == 0 || IsWordSeparator (GetCharAt (offset - 1))) &&
1147
(offset + length == Length || IsWordSeparator (GetCharAt (offset + length)));
1150
public int GetMatchingBracketOffset (int offset)
1152
if (offset < 0 || offset >= Length)
1154
char ch = GetCharAt (offset);
1155
int bracket = openBrackets.IndexOf (ch);
1158
result = SearchMatchingBracketForward (offset + 1, bracket);
1160
bracket = closingBrackets.IndexOf (ch);
1162
result = SearchMatchingBracketBackward (offset - 1, bracket);
1170
int SearchMatchingBracketForward (int offset, int bracket)
1172
return SearchMatchingBracket (offset, closingBrackets[bracket], openBrackets[bracket], 1);
1175
int SearchMatchingBracketBackward (int offset, int bracket)
1177
return SearchMatchingBracket (offset, openBrackets[bracket], closingBrackets[bracket], -1);
1180
int SearchMatchingBracket (int offset, char openBracket, char closingBracket, int direction)
1182
bool isInString = false;
1183
bool isInChar = false;
1184
bool isInBlockComment = false;
1186
while (offset >= 0 && offset < Length) {
1187
char ch = GetCharAt (offset);
1190
if (isInBlockComment)
1191
isInBlockComment = GetCharAt (offset + direction) != '*';
1192
if (!isInString && !isInChar && offset - direction < Length)
1193
isInBlockComment = offset > 0 && GetCharAt (offset - direction) == '*';
1196
if (!isInChar && !isInBlockComment)
1197
isInString = !isInString;
1200
if (!isInString && !isInBlockComment)
1201
isInChar = !isInChar;
1204
if (ch == closingBracket) {
1205
if (!(isInString || isInChar || isInBlockComment))
1207
} else if (ch == openBracket) {
1208
if (!(isInString || isInChar || isInBlockComment)) {
1216
offset += direction;
1221
public enum CharacterClass {
1227
public static CharacterClass GetCharacterClass (char ch)
1229
if (Char.IsWhiteSpace (ch))
1230
return CharacterClass.Whitespace;
1231
if (Char.IsLetterOrDigit (ch) || ch == '_')
1232
return CharacterClass.IdentifierPart;
1233
return CharacterClass.Unknown;
1239
public delegate bool ReadOnlyCheckDelegate (int line);