@ -0,0 +1,7 @@ |
|||||||
|
# ignore all obj and bin folders (even in subdirectories) |
||||||
|
obj/ |
||||||
|
bin/ |
||||||
|
/Documentation/Help |
||||||
|
/packages/AvalonEdit |
||||||
|
/packages/AvalonEdit.Sample |
||||||
|
/packages/NUnit.2.6.3 |
@ -0,0 +1,343 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Document |
||||||
|
{ |
||||||
|
#if !NREFACTORY
|
||||||
|
/// <summary>
|
||||||
|
/// A document representing a source code file for refactoring.
|
||||||
|
/// Line and column counting starts at 1.
|
||||||
|
/// Offset counting starts at 0.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDocument : ITextSource, IServiceProvider |
||||||
|
{ |
||||||
|
#if NREFACTORY
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an immutable snapshot of this document.
|
||||||
|
/// </summary>
|
||||||
|
IDocument CreateDocumentSnapshot(); |
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the text of the whole document..
|
||||||
|
/// </summary>
|
||||||
|
new string Text { get; set; } // hides ITextSource.Text to add the setter
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is called directly before a change is applied to the document.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It is invalid to modify the document within this event handler.
|
||||||
|
/// Aborting the change (by throwing an exception) is likely to cause corruption of data structures
|
||||||
|
/// that listen to the Changing and Changed events.
|
||||||
|
/// </remarks>
|
||||||
|
event EventHandler<TextChangeEventArgs> TextChanging; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is called directly after a change is applied to the document.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It is invalid to modify the document within this event handler.
|
||||||
|
/// Aborting the event handler (by throwing an exception) is likely to cause corruption of data structures
|
||||||
|
/// that listen to the Changing and Changed events.
|
||||||
|
/// </remarks>
|
||||||
|
event EventHandler<TextChangeEventArgs> TextChanged; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is called after a group of changes is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="EndUndoableAction"/>
|
||||||
|
event EventHandler ChangeCompleted; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of lines in the document.
|
||||||
|
/// </summary>
|
||||||
|
int LineCount { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the document line with the specified number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lineNumber">The number of the line to retrieve. The first line has number 1.</param>
|
||||||
|
IDocumentLine GetLineByNumber(int lineNumber); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the document line that contains the specified offset.
|
||||||
|
/// </summary>
|
||||||
|
IDocumentLine GetLineByOffset(int offset); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the offset from a text location.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="GetLocation"/>
|
||||||
|
int GetOffset(int line, int column); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the offset from a text location.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="GetLocation"/>
|
||||||
|
int GetOffset(TextLocation location); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the location from an offset.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="GetOffset(TextLocation)"/>
|
||||||
|
TextLocation GetLocation(int offset); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||||
|
/// <param name="text">The new text.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Anchors positioned exactly at the insertion offset will move according to their movement type.
|
||||||
|
/// For AnchorMovementType.Default, they will move behind the inserted text.
|
||||||
|
/// The caret will also move behind the inserted text.
|
||||||
|
/// </remarks>
|
||||||
|
void Insert(int offset, string text); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||||
|
/// <param name="text">The new text.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Anchors positioned exactly at the insertion offset will move according to their movement type.
|
||||||
|
/// For AnchorMovementType.Default, they will move behind the inserted text.
|
||||||
|
/// The caret will also move behind the inserted text.
|
||||||
|
/// </remarks>
|
||||||
|
void Insert(int offset, ITextSource text); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||||
|
/// <param name="text">The new text.</param>
|
||||||
|
/// <param name="defaultAnchorMovementType">
|
||||||
|
/// Anchors positioned exactly at the insertion offset will move according to the anchor's movement type.
|
||||||
|
/// For AnchorMovementType.Default, they will move according to the movement type specified by this parameter.
|
||||||
|
/// The caret will also move according to the <paramref name="defaultAnchorMovementType"/> parameter.
|
||||||
|
/// </param>
|
||||||
|
void Insert(int offset, string text, AnchorMovementType defaultAnchorMovementType); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||||
|
/// <param name="text">The new text.</param>
|
||||||
|
/// <param name="defaultAnchorMovementType">
|
||||||
|
/// Anchors positioned exactly at the insertion offset will move according to the anchor's movement type.
|
||||||
|
/// For AnchorMovementType.Default, they will move according to the movement type specified by this parameter.
|
||||||
|
/// The caret will also move according to the <paramref name="defaultAnchorMovementType"/> parameter.
|
||||||
|
/// </param>
|
||||||
|
void Insert(int offset, ITextSource text, AnchorMovementType defaultAnchorMovementType); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Starting offset of the text to be removed.</param>
|
||||||
|
/// <param name="length">Length of the text to be removed.</param>
|
||||||
|
void Remove(int offset, int length); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The starting offset of the text to be replaced.</param>
|
||||||
|
/// <param name="length">The length of the text to be replaced.</param>
|
||||||
|
/// <param name="newText">The new text.</param>
|
||||||
|
void Replace(int offset, int length, string newText); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The starting offset of the text to be replaced.</param>
|
||||||
|
/// <param name="length">The length of the text to be replaced.</param>
|
||||||
|
/// <param name="newText">The new text.</param>
|
||||||
|
void Replace(int offset, int length, ITextSource newText); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make the document combine the following actions into a single
|
||||||
|
/// action for undo purposes.
|
||||||
|
/// </summary>
|
||||||
|
void StartUndoableAction(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the undoable action started with <see cref="StartUndoableAction"/>.
|
||||||
|
/// </summary>
|
||||||
|
void EndUndoableAction(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an undo group. Dispose the returned value to close the undo group.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An object that closes the undo group when Dispose() is called.</returns>
|
||||||
|
IDisposable OpenUndoGroup(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ITextAnchor"/> at the specified offset.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="ITextAnchor" select="remarks|example"/>
|
||||||
|
ITextAnchor CreateAnchor(int offset); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the file the document is stored in.
|
||||||
|
/// Could also be a non-existent dummy file name or null if no name has been set.
|
||||||
|
/// </summary>
|
||||||
|
string FileName { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when the file name of the document changes.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler FileNameChanged; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A line inside a <see cref="IDocument"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDocumentLine : ISegment |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the length of this line, including the line delimiter.
|
||||||
|
/// </summary>
|
||||||
|
int TotalLength { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length of the line terminator.
|
||||||
|
/// Returns 1 or 2; or 0 at the end of the document.
|
||||||
|
/// </summary>
|
||||||
|
int DelimiterLength { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of this line.
|
||||||
|
/// The first line has the number 1.
|
||||||
|
/// </summary>
|
||||||
|
int LineNumber { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the previous line. Returns null if this is the first line in the document.
|
||||||
|
/// </summary>
|
||||||
|
IDocumentLine PreviousLine { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next line. Returns null if this is the last line in the document.
|
||||||
|
/// </summary>
|
||||||
|
IDocumentLine NextLine { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the line was deleted.
|
||||||
|
/// </summary>
|
||||||
|
bool IsDeleted { get; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a change of the document text.
|
||||||
|
/// This class is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public class TextChangeEventArgs : EventArgs |
||||||
|
{ |
||||||
|
readonly int offset; |
||||||
|
readonly ITextSource removedText; |
||||||
|
readonly ITextSource insertedText; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset at which the change occurs.
|
||||||
|
/// </summary>
|
||||||
|
public int Offset { |
||||||
|
get { return offset; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text that was removed.
|
||||||
|
/// </summary>
|
||||||
|
public ITextSource RemovedText { |
||||||
|
get { return removedText; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of characters removed.
|
||||||
|
/// </summary>
|
||||||
|
public int RemovalLength { |
||||||
|
get { return removedText.TextLength; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text that was inserted.
|
||||||
|
/// </summary>
|
||||||
|
public ITextSource InsertedText { |
||||||
|
get { return insertedText; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of characters inserted.
|
||||||
|
/// </summary>
|
||||||
|
public int InsertionLength { |
||||||
|
get { return insertedText.TextLength; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TextChangeEventArgs object.
|
||||||
|
/// </summary>
|
||||||
|
public TextChangeEventArgs(int offset, string removedText, string insertedText) |
||||||
|
{ |
||||||
|
if (offset < 0) |
||||||
|
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative"); |
||||||
|
this.offset = offset; |
||||||
|
this.removedText = removedText != null ? new StringTextSource(removedText) : StringTextSource.Empty; |
||||||
|
this.insertedText = insertedText != null ? new StringTextSource(insertedText) : StringTextSource.Empty; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TextChangeEventArgs object.
|
||||||
|
/// </summary>
|
||||||
|
public TextChangeEventArgs(int offset, ITextSource removedText, ITextSource insertedText) |
||||||
|
{ |
||||||
|
if (offset < 0) |
||||||
|
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative"); |
||||||
|
this.offset = offset; |
||||||
|
this.removedText = removedText ?? StringTextSource.Empty; |
||||||
|
this.insertedText = insertedText ?? StringTextSource.Empty; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the new offset where the specified offset moves after this document change.
|
||||||
|
/// </summary>
|
||||||
|
public virtual int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default) |
||||||
|
{ |
||||||
|
if (offset >= this.Offset && offset <= this.Offset + this.RemovalLength) { |
||||||
|
if (movementType == AnchorMovementType.BeforeInsertion) |
||||||
|
return this.Offset; |
||||||
|
else |
||||||
|
return this.Offset + this.InsertionLength; |
||||||
|
} else if (offset > this.Offset) { |
||||||
|
return offset + this.InsertionLength - this.RemovalLength; |
||||||
|
} else { |
||||||
|
return offset; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates TextChangeEventArgs for the reverse change.
|
||||||
|
/// </summary>
|
||||||
|
public virtual TextChangeEventArgs Invert() |
||||||
|
{ |
||||||
|
return new TextChangeEventArgs(offset, insertedText, removedText); |
||||||
|
} |
||||||
|
} |
||||||
|
#endif
|
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Document |
||||||
|
{ |
||||||
|
#if !NREFACTORY
|
||||||
|
/// <summary>
|
||||||
|
/// The TextAnchor class references an offset (a position between two characters).
|
||||||
|
/// It automatically updates the offset when text is inserted/removed in front of the anchor.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>Use the <see cref="ITextAnchor.Offset"/> property to get the offset from a text anchor.
|
||||||
|
/// Use the <see cref="IDocument.CreateAnchor"/> method to create an anchor from an offset.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The document will automatically update all text anchors; and because it uses weak references to do so,
|
||||||
|
/// the garbage collector can simply collect the anchor object when you don't need it anymore.
|
||||||
|
/// </para>
|
||||||
|
/// <para>Moreover, the document is able to efficiently update a large number of anchors without having to look
|
||||||
|
/// at each anchor object individually. Updating the offsets of all anchors usually only takes time logarithmic
|
||||||
|
/// to the number of anchors. Retrieving the <see cref="ITextAnchor.Offset"/> property also runs in O(lg N).</para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// Usage:
|
||||||
|
/// <code>TextAnchor anchor = document.CreateAnchor(offset);
|
||||||
|
/// ChangeMyDocument();
|
||||||
|
/// int newOffset = anchor.Offset;
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public interface ITextAnchor |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the text location of this anchor.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||||
|
TextLocation Location { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the offset of the text anchor.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||||
|
int Offset { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls how the anchor moves.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Anchor movement is ambiguous if text is inserted exactly at the anchor's location.
|
||||||
|
/// Does the anchor stay before the inserted text, or does it move after it?
|
||||||
|
/// The property <see cref="MovementType"/> will be used to determine which of these two options the anchor will choose.
|
||||||
|
/// The default value is <see cref="AnchorMovementType.Default"/>.</remarks>
|
||||||
|
AnchorMovementType MovementType { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// Specifies whether the anchor survives deletion of the text containing it.
|
||||||
|
/// </para><para>
|
||||||
|
/// <c>false</c>: The anchor is deleted when the a selection that includes the anchor is deleted.
|
||||||
|
/// <c>true</c>: The anchor is not deleted.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks><inheritdoc cref="IsDeleted" /></remarks>
|
||||||
|
bool SurviveDeletion { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the anchor was deleted.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>When a piece of text containing an anchor is removed, then that anchor will be deleted.
|
||||||
|
/// First, the <see cref="IsDeleted"/> property is set to true on all deleted anchors,
|
||||||
|
/// then the <see cref="Deleted"/> events are raised.
|
||||||
|
/// You cannot retrieve the offset from an anchor that has been deleted.</para>
|
||||||
|
/// <para>This deletion behavior might be useful when using anchors for building a bookmark feature,
|
||||||
|
/// but in other cases you want to still be able to use the anchor. For those cases, set <c><see cref="SurviveDeletion"/> = true</c>.</para>
|
||||||
|
/// </remarks>
|
||||||
|
bool IsDeleted { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs after the anchor was deleted.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <inheritdoc cref="IsDeleted" />
|
||||||
|
/// <para>Due to the 'weak reference' nature of text anchors, you will receive
|
||||||
|
/// the Deleted event only while your code holds a reference to the TextAnchor object.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
event EventHandler Deleted; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the line number of the anchor.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||||
|
int Line { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the column number of this anchor.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||||
|
int Column { get; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines how a text anchor moves.
|
||||||
|
/// </summary>
|
||||||
|
public enum AnchorMovementType |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// When text is inserted at the anchor position, the type of the insertion
|
||||||
|
/// determines where the caret moves to. For normal insertions, the anchor will move
|
||||||
|
/// after the inserted text.
|
||||||
|
/// </summary>
|
||||||
|
Default, |
||||||
|
/// <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 |
||||||
|
} |
||||||
|
#endif
|
||||||
|
} |
@ -0,0 +1,357 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Collections.Generic; |
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Document |
||||||
|
{ |
||||||
|
#if !NREFACTORY
|
||||||
|
/// <summary>
|
||||||
|
/// A read-only view on a (potentially mutable) text source.
|
||||||
|
/// The IDocument interface derives from this interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITextSource |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets a version identifier for this text source.
|
||||||
|
/// Returns null for unversioned text sources.
|
||||||
|
/// </summary>
|
||||||
|
ITextSourceVersion Version { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an immutable snapshot of this text source.
|
||||||
|
/// Unlike all other methods in this interface, this method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
ITextSource CreateSnapshot(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an immutable snapshot of a part of this text source.
|
||||||
|
/// Unlike all other methods in this interface, this method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
ITextSource CreateSnapshot(int offset, int length); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TextReader to read from this text source.
|
||||||
|
/// </summary>
|
||||||
|
TextReader CreateReader(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TextReader to read from this text source.
|
||||||
|
/// </summary>
|
||||||
|
TextReader CreateReader(int offset, int length); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the total text length.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The length of the text, in characters.</returns>
|
||||||
|
/// <remarks>This is the same as Text.Length, but is more efficient because
|
||||||
|
/// it doesn't require creating a String object.</remarks>
|
||||||
|
int TextLength { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the whole text as string.
|
||||||
|
/// </summary>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] |
||||||
|
string Text { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a character at the specified position in the document.
|
||||||
|
/// </summary>
|
||||||
|
/// <paramref name="offset">The index of the character to get.</paramref>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to TextLength-1).</exception>
|
||||||
|
/// <returns>The character at the specified position.</returns>
|
||||||
|
/// <remarks>This is the same as Text[offset], but is more efficient because
|
||||||
|
/// it doesn't require creating a String object.</remarks>
|
||||||
|
char GetCharAt(int offset); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the text for a portion of the document.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
|
||||||
|
/// <remarks>This is the same as Text.Substring, but is more efficient because
|
||||||
|
/// it doesn't require creating a String object for the whole document.</remarks>
|
||||||
|
string GetText(int offset, int length); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the text for a portion of the document.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
|
||||||
|
string GetText(ISegment segment); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the text from this document into the TextWriter.
|
||||||
|
/// </summary>
|
||||||
|
void WriteTextTo(TextWriter writer); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the text from this document into the TextWriter.
|
||||||
|
/// </summary>
|
||||||
|
void WriteTextTo(TextWriter writer, int offset, int length); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the first occurrence of the character in the specified array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">Character to search for</param>
|
||||||
|
/// <param name="startIndex">Start index of the area to search.</param>
|
||||||
|
/// <param name="count">Length of the area to search.</param>
|
||||||
|
/// <returns>The first index where the character was found; or -1 if no occurrence was found.</returns>
|
||||||
|
int IndexOf(char c, int startIndex, int count); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the first occurrence of any character in the specified array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anyOf">Characters to search for</param>
|
||||||
|
/// <param name="startIndex">Start index of the area to search.</param>
|
||||||
|
/// <param name="count">Length of the area to search.</param>
|
||||||
|
/// <returns>The first index where any character was found; or -1 if no occurrence was found.</returns>
|
||||||
|
int IndexOfAny(char[] anyOf, int startIndex, int count); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the first occurrence of the specified search text in this text source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="searchText">The search text</param>
|
||||||
|
/// <param name="startIndex">Start index of the area to search.</param>
|
||||||
|
/// <param name="count">Length of the area to search.</param>
|
||||||
|
/// <param name="comparisonType">String comparison to use.</param>
|
||||||
|
/// <returns>The first index where the search term was found; or -1 if no occurrence was found.</returns>
|
||||||
|
int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the last occurrence of the specified character in this text source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">The search character</param>
|
||||||
|
/// <param name="startIndex">Start index of the area to search.</param>
|
||||||
|
/// <param name="count">Length of the area to search.</param>
|
||||||
|
/// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns>
|
||||||
|
/// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
|
||||||
|
/// This is different than the meaning of the parameters on string.LastIndexOf!</remarks>
|
||||||
|
int LastIndexOf(char c, int startIndex, int count); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the last occurrence of the specified search text in this text source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="searchText">The search text</param>
|
||||||
|
/// <param name="startIndex">Start index of the area to search.</param>
|
||||||
|
/// <param name="count">Length of the area to search.</param>
|
||||||
|
/// <param name="comparisonType">String comparison to use.</param>
|
||||||
|
/// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns>
|
||||||
|
/// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
|
||||||
|
/// This is different than the meaning of the parameters on string.LastIndexOf!</remarks>
|
||||||
|
int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType); |
||||||
|
|
||||||
|
/* What about: |
||||||
|
void Insert (int offset, string value); |
||||||
|
void Remove (int offset, int count); |
||||||
|
void Remove (ISegment segment); |
||||||
|
|
||||||
|
void Replace (int offset, int count, string value); |
||||||
|
|
||||||
|
Or more search operations: |
||||||
|
|
||||||
|
IEnumerable<int> SearchForward (string pattern, int startIndex); |
||||||
|
IEnumerable<int> SearchForwardIgnoreCase (string pattern, int startIndex); |
||||||
|
|
||||||
|
IEnumerable<int> SearchBackward (string pattern, int startIndex); |
||||||
|
IEnumerable<int> SearchBackwardIgnoreCase (string pattern, int startIndex); |
||||||
|
*/ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a version identifier for a text source.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Verions can be used to efficiently detect whether a document has changed and needs reparsing;
|
||||||
|
/// or even to implement incremental parsers.
|
||||||
|
/// It is a separate class from ITextSource to allow the GC to collect the text source while
|
||||||
|
/// the version checkpoint is still in use.
|
||||||
|
/// </remarks>
|
||||||
|
public interface ITextSourceVersion |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this checkpoint belongs to the same document as the other checkpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Returns false when given <c>null</c>.
|
||||||
|
/// </remarks>
|
||||||
|
bool BelongsToSameDocumentAs(ITextSourceVersion other); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the age of this checkpoint to the other checkpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method is thread-safe.</remarks>
|
||||||
|
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this version.</exception>
|
||||||
|
/// <returns>-1 if this version is older than <paramref name="other"/>.
|
||||||
|
/// 0 if <c>this</c> version instance represents the same version as <paramref name="other"/>.
|
||||||
|
/// 1 if this version is newer than <paramref name="other"/>.</returns>
|
||||||
|
int CompareAge(ITextSourceVersion other); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the changes from this checkpoint to the other checkpoint.
|
||||||
|
/// If 'other' is older than this checkpoint, reverse changes are calculated.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method is thread-safe.</remarks>
|
||||||
|
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
|
||||||
|
IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates where the offset has moved in the other buffer version.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
|
||||||
|
int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement = AnchorMovementType.Default); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the ITextSource interface using a string.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable] |
||||||
|
public class StringTextSource : ITextSource |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets a text source containing the empty string.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StringTextSource Empty = new StringTextSource(string.Empty); |
||||||
|
|
||||||
|
readonly string text; |
||||||
|
readonly ITextSourceVersion version; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new StringTextSource with the given text.
|
||||||
|
/// </summary>
|
||||||
|
public StringTextSource(string text) |
||||||
|
{ |
||||||
|
if (text == null) |
||||||
|
throw new ArgumentNullException("text"); |
||||||
|
this.text = text; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new StringTextSource with the given text.
|
||||||
|
/// </summary>
|
||||||
|
public StringTextSource(string text, ITextSourceVersion version) |
||||||
|
{ |
||||||
|
if (text == null) |
||||||
|
throw new ArgumentNullException("text"); |
||||||
|
this.text = text; |
||||||
|
this.version = version; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ITextSourceVersion Version { |
||||||
|
get { return version; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int TextLength { |
||||||
|
get { return text.Length; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Text { |
||||||
|
get { return text; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ITextSource CreateSnapshot() |
||||||
|
{ |
||||||
|
return this; // StringTextSource is immutable
|
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ITextSource CreateSnapshot(int offset, int length) |
||||||
|
{ |
||||||
|
return new StringTextSource(text.Substring(offset, length)); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TextReader CreateReader() |
||||||
|
{ |
||||||
|
return new StringReader(text); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TextReader CreateReader(int offset, int length) |
||||||
|
{ |
||||||
|
return new StringReader(text.Substring(offset, length)); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void WriteTextTo(TextWriter writer) |
||||||
|
{ |
||||||
|
writer.Write(text); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void WriteTextTo(TextWriter writer, int offset, int length) |
||||||
|
{ |
||||||
|
writer.Write(text.Substring(offset, length)); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public char GetCharAt(int offset) |
||||||
|
{ |
||||||
|
return text[offset]; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string GetText(int offset, int length) |
||||||
|
{ |
||||||
|
return text.Substring(offset, length); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string GetText(ISegment segment) |
||||||
|
{ |
||||||
|
if (segment == null) |
||||||
|
throw new ArgumentNullException("segment"); |
||||||
|
return text.Substring(segment.Offset, segment.Length); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int IndexOf(char c, int startIndex, int count) |
||||||
|
{ |
||||||
|
return text.IndexOf(c, startIndex, count); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int IndexOfAny(char[] anyOf, int startIndex, int count) |
||||||
|
{ |
||||||
|
return text.IndexOfAny(anyOf, startIndex, count); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) |
||||||
|
{ |
||||||
|
return text.IndexOf(searchText, startIndex, count, comparisonType); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int LastIndexOf(char c, int startIndex, int count) |
||||||
|
{ |
||||||
|
return text.LastIndexOf(c, startIndex + count - 1, count); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) |
||||||
|
{ |
||||||
|
return text.LastIndexOf(searchText, startIndex + count - 1, count, comparisonType); |
||||||
|
} |
||||||
|
} |
||||||
|
#endif
|
||||||
|
} |
@ -0,0 +1,271 @@ |
|||||||
|
// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.ComponentModel; |
||||||
|
using System.Globalization; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Document |
||||||
|
{ |
||||||
|
#if !NREFACTORY
|
||||||
|
/// <summary>
|
||||||
|
/// A line/column position.
|
||||||
|
/// Text editor lines/columns are counted started from one.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The document provides the methods <see cref="IDocument.GetLocation"/> and
|
||||||
|
/// <see cref="IDocument.GetOffset(TextLocation)"/> to convert between offsets and TextLocations.
|
||||||
|
/// </remarks>
|
||||||
|
[Serializable] |
||||||
|
[TypeConverter(typeof(TextLocationConverter))] |
||||||
|
public struct TextLocation : IComparable<TextLocation>, IEquatable<TextLocation> |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents no text location (0, 0).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly TextLocation Empty = new TextLocation(0, 0); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a TextLocation instance.
|
||||||
|
/// </summary>
|
||||||
|
public TextLocation(int line, int column) |
||||||
|
{ |
||||||
|
this.line = line; |
||||||
|
this.column = column; |
||||||
|
} |
||||||
|
|
||||||
|
readonly int column, line; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the line number.
|
||||||
|
/// </summary>
|
||||||
|
public int Line { |
||||||
|
get { return line; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the column number.
|
||||||
|
/// </summary>
|
||||||
|
public int Column { |
||||||
|
get { return column; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the TextLocation instance is empty.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEmpty { |
||||||
|
get { |
||||||
|
return column <= 0 && line <= 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a string representation for debugging purposes.
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() |
||||||
|
{ |
||||||
|
return string.Format(CultureInfo.InvariantCulture, "(Line {1}, Col {0})", this.column, this.line); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a hash code.
|
||||||
|
/// </summary>
|
||||||
|
public override int GetHashCode() |
||||||
|
{ |
||||||
|
return unchecked (191 * column.GetHashCode() ^ line.GetHashCode()); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equality test.
|
||||||
|
/// </summary>
|
||||||
|
public override bool Equals(object obj) |
||||||
|
{ |
||||||
|
if (!(obj is TextLocation)) return false; |
||||||
|
return (TextLocation)obj == this; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equality test.
|
||||||
|
/// </summary>
|
||||||
|
public bool Equals(TextLocation other) |
||||||
|
{ |
||||||
|
return this == other; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equality test.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator ==(TextLocation left, TextLocation right) |
||||||
|
{ |
||||||
|
return left.column == right.column && left.line == right.line; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inequality test.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator !=(TextLocation left, TextLocation right) |
||||||
|
{ |
||||||
|
return left.column != right.column || left.line != right.line; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two text locations.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator <(TextLocation left, TextLocation right) |
||||||
|
{ |
||||||
|
if (left.line < right.line) |
||||||
|
return true; |
||||||
|
else if (left.line == right.line) |
||||||
|
return left.column < right.column; |
||||||
|
else |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two text locations.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator >(TextLocation left, TextLocation right) |
||||||
|
{ |
||||||
|
if (left.line > right.line) |
||||||
|
return true; |
||||||
|
else if (left.line == right.line) |
||||||
|
return left.column > right.column; |
||||||
|
else |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two text locations.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator <=(TextLocation left, TextLocation right) |
||||||
|
{ |
||||||
|
return !(left > right); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two text locations.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator >=(TextLocation left, TextLocation right) |
||||||
|
{ |
||||||
|
return !(left < right); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two text locations.
|
||||||
|
/// </summary>
|
||||||
|
public int CompareTo(TextLocation other) |
||||||
|
{ |
||||||
|
if (this == other) |
||||||
|
return 0; |
||||||
|
if (this < other) |
||||||
|
return -1; |
||||||
|
else |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts strings of the form '0+[;,]0+' to a <see cref="TextLocation"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class TextLocationConverter : TypeConverter |
||||||
|
{ |
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
||||||
|
{ |
||||||
|
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) |
||||||
|
{ |
||||||
|
return destinationType == typeof(TextLocation) || base.CanConvertTo(context, destinationType); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
||||||
|
{ |
||||||
|
if (value is string) { |
||||||
|
string[] parts = ((string)value).Split(';', ','); |
||||||
|
if (parts.Length == 2) { |
||||||
|
return new TextLocation(int.Parse(parts[0], culture), int.Parse(parts[1], culture)); |
||||||
|
} |
||||||
|
} |
||||||
|
return base.ConvertFrom(context, culture, value); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) |
||||||
|
{ |
||||||
|
if (value is TextLocation && destinationType == typeof(string)) { |
||||||
|
var loc = (TextLocation)value; |
||||||
|
return loc.Line.ToString(culture) + ";" + loc.Column.ToString(culture); |
||||||
|
} |
||||||
|
return base.ConvertTo(context, culture, value, destinationType); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An (Offset,Length)-pair.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISegment |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the start offset of the segment.
|
||||||
|
/// </summary>
|
||||||
|
int Offset { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length of the segment.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>For line segments (IDocumentLine), the length does not include the line delimeter.</remarks>
|
||||||
|
int Length { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the end offset of the segment.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>EndOffset = Offset + Length;</remarks>
|
||||||
|
int EndOffset { get; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="ISegment"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ISegmentExtensions |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets whether <paramref name="segment"/> fully contains the specified segment.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use <c>segment.Contains(offset, 0)</c> to detect whether a segment (end inclusive) contains offset;
|
||||||
|
/// use <c>segment.Contains(offset, 1)</c> to detect whether a segment (end exclusive) contains offset.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool Contains (this ISegment segment, int offset, int length) |
||||||
|
{ |
||||||
|
return segment.Offset <= offset && offset + length <= segment.EndOffset; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether <paramref name="thisSegment"/> fully contains the specified segment.
|
||||||
|
/// </summary>
|
||||||
|
public static bool Contains (this ISegment thisSegment, ISegment segment) |
||||||
|
{ |
||||||
|
return segment != null && thisSegment.Offset <= segment.Offset && segment.EndOffset <= thisSegment.EndOffset; |
||||||
|
} |
||||||
|
} |
||||||
|
#endif
|
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Linq; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Document |
||||||
|
{ |
||||||
|
#if !NREFACTORY
|
||||||
|
/// <summary>
|
||||||
|
/// Provides ITextSourceVersion instances.
|
||||||
|
/// </summary>
|
||||||
|
public class TextSourceVersionProvider |
||||||
|
{ |
||||||
|
Version currentVersion; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new TextSourceVersionProvider instance.
|
||||||
|
/// </summary>
|
||||||
|
public TextSourceVersionProvider() |
||||||
|
{ |
||||||
|
this.currentVersion = new Version(this); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current version.
|
||||||
|
/// </summary>
|
||||||
|
public ITextSourceVersion CurrentVersion { |
||||||
|
get { return currentVersion; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the current version with a new version.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="change">Change from current version to new version</param>
|
||||||
|
public void AppendChange(TextChangeEventArgs change) |
||||||
|
{ |
||||||
|
if (change == null) |
||||||
|
throw new ArgumentNullException("change"); |
||||||
|
currentVersion.change = change; |
||||||
|
currentVersion.next = new Version(currentVersion); |
||||||
|
currentVersion = currentVersion.next; |
||||||
|
} |
||||||
|
|
||||||
|
[DebuggerDisplay("Version #{id}")] |
||||||
|
sealed class Version : ITextSourceVersion |
||||||
|
{ |
||||||
|
// Reference back to the provider.
|
||||||
|
// Used to determine if two checkpoints belong to the same document.
|
||||||
|
readonly TextSourceVersionProvider provider; |
||||||
|
// ID used for CompareAge()
|
||||||
|
readonly int id; |
||||||
|
|
||||||
|
// the change from this version to the next version
|
||||||
|
internal TextChangeEventArgs change; |
||||||
|
internal Version next; |
||||||
|
|
||||||
|
internal Version(TextSourceVersionProvider provider) |
||||||
|
{ |
||||||
|
this.provider = provider; |
||||||
|
} |
||||||
|
|
||||||
|
internal Version(Version prev) |
||||||
|
{ |
||||||
|
this.provider = prev.provider; |
||||||
|
this.id = unchecked( prev.id + 1 ); |
||||||
|
} |
||||||
|
|
||||||
|
public bool BelongsToSameDocumentAs(ITextSourceVersion other) |
||||||
|
{ |
||||||
|
Version o = other as Version; |
||||||
|
return o != null && provider == o.provider; |
||||||
|
} |
||||||
|
|
||||||
|
public int CompareAge(ITextSourceVersion other) |
||||||
|
{ |
||||||
|
if (other == null) |
||||||
|
throw new ArgumentNullException("other"); |
||||||
|
Version o = other as Version; |
||||||
|
if (o == null || provider != o.provider) |
||||||
|
throw new ArgumentException("Versions do not belong to the same document."); |
||||||
|
// We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1.
|
||||||
|
// This is guaranteed on x86 because so many checkpoints don't fit into memory.
|
||||||
|
return Math.Sign(unchecked( this.id - o.id )); |
||||||
|
} |
||||||
|
|
||||||
|
public IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other) |
||||||
|
{ |
||||||
|
int result = CompareAge(other); |
||||||
|
Version o = (Version)other; |
||||||
|
if (result < 0) |
||||||
|
return GetForwardChanges(o); |
||||||
|
else if (result > 0) |
||||||
|
return o.GetForwardChanges(this).Reverse().Select(change => change.Invert()); |
||||||
|
else |
||||||
|
return Empty<TextChangeEventArgs>.Array; |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerable<TextChangeEventArgs> GetForwardChanges(Version other) |
||||||
|
{ |
||||||
|
// Return changes from this(inclusive) to other(exclusive).
|
||||||
|
for (Version node = this; node != other; node = node.next) { |
||||||
|
yield return node.change; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement) |
||||||
|
{ |
||||||
|
int offset = oldOffset; |
||||||
|
foreach (var e in GetChangesTo(other)) { |
||||||
|
offset = e.GetNewOffset(offset, movement); |
||||||
|
} |
||||||
|
return offset; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif
|
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Threading; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Utils |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Invokes an action when it is disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This class ensures the callback is invoked at most once,
|
||||||
|
/// even when Dispose is called on multiple threads.
|
||||||
|
/// </remarks>
|
||||||
|
sealed class CallbackOnDispose : IDisposable |
||||||
|
{ |
||||||
|
Action action; |
||||||
|
|
||||||
|
public CallbackOnDispose(Action action) |
||||||
|
{ |
||||||
|
if (action == null) |
||||||
|
throw new ArgumentNullException("action"); |
||||||
|
this.action = action; |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
Action a = Interlocked.Exchange(ref action, null); |
||||||
|
if (a != null) { |
||||||
|
a(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is used to prevent stack overflows by representing a 'busy' flag
|
||||||
|
/// that prevents reentrance when another call is running.
|
||||||
|
/// However, using a simple 'bool busy' is not thread-safe, so we use a
|
||||||
|
/// thread-static BusyManager.
|
||||||
|
/// </summary>
|
||||||
|
static class BusyManager |
||||||
|
{ |
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] |
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", |
||||||
|
Justification = "Should always be used with 'var'")] |
||||||
|
public struct BusyLock : IDisposable |
||||||
|
{ |
||||||
|
public static readonly BusyLock Failed = new BusyLock(null); |
||||||
|
|
||||||
|
readonly List<object> objectList; |
||||||
|
|
||||||
|
internal BusyLock(List<object> objectList) |
||||||
|
{ |
||||||
|
this.objectList = objectList; |
||||||
|
} |
||||||
|
|
||||||
|
public bool Success { |
||||||
|
get { return objectList != null; } |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
if (objectList != null) { |
||||||
|
objectList.RemoveAt(objectList.Count - 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[ThreadStatic] static List<object> _activeObjects; |
||||||
|
|
||||||
|
public static BusyLock Enter(object obj) |
||||||
|
{ |
||||||
|
List<object> activeObjects = _activeObjects; |
||||||
|
if (activeObjects == null) |
||||||
|
activeObjects = _activeObjects = new List<object>(); |
||||||
|
for (int i = 0; i < activeObjects.Count; i++) { |
||||||
|
if (activeObjects[i] == obj) |
||||||
|
return BusyLock.Failed; |
||||||
|
} |
||||||
|
activeObjects.Add(obj); |
||||||
|
return new BusyLock(activeObjects); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,123 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Utils |
||||||
|
{ |
||||||
|
interface IFreezable |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
bool IsFrozen { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes this instance.
|
||||||
|
/// </summary>
|
||||||
|
void Freeze(); |
||||||
|
} |
||||||
|
|
||||||
|
static class FreezableHelper |
||||||
|
{ |
||||||
|
public static void ThrowIfFrozen(IFreezable freezable) |
||||||
|
{ |
||||||
|
if (freezable.IsFrozen) |
||||||
|
throw new InvalidOperationException("Cannot mutate frozen " + freezable.GetType().Name); |
||||||
|
} |
||||||
|
|
||||||
|
public static IList<T> FreezeListAndElements<T>(IList<T> list) |
||||||
|
{ |
||||||
|
if (list != null) { |
||||||
|
foreach (T item in list) |
||||||
|
Freeze(item); |
||||||
|
} |
||||||
|
return FreezeList(list); |
||||||
|
} |
||||||
|
|
||||||
|
public static IList<T> FreezeList<T>(IList<T> list) |
||||||
|
{ |
||||||
|
if (list == null || list.Count == 0) |
||||||
|
return Empty<T>.Array; |
||||||
|
if (list.IsReadOnly) { |
||||||
|
// If the list is already read-only, return it directly.
|
||||||
|
// This is important, otherwise we might undo the effects of interning.
|
||||||
|
return list; |
||||||
|
} else { |
||||||
|
return new ReadOnlyCollection<T>(list.ToArray()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void Freeze(object item) |
||||||
|
{ |
||||||
|
IFreezable f = item as IFreezable; |
||||||
|
if (f != null) |
||||||
|
f.Freeze(); |
||||||
|
} |
||||||
|
|
||||||
|
public static T FreezeAndReturn<T>(T item) where T : IFreezable |
||||||
|
{ |
||||||
|
item.Freeze(); |
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the item is not frozen, this method creates and returns a frozen clone.
|
||||||
|
/// If the item is already frozen, it is returned without creating a clone.
|
||||||
|
/// </summary>
|
||||||
|
public static T GetFrozenClone<T>(T item) where T : IFreezable, ICloneable |
||||||
|
{ |
||||||
|
if (!item.IsFrozen) { |
||||||
|
item = (T)item.Clone(); |
||||||
|
item.Freeze(); |
||||||
|
} |
||||||
|
return item; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[Serializable] |
||||||
|
abstract class AbstractFreezable : IFreezable |
||||||
|
{ |
||||||
|
bool isFrozen; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFrozen { |
||||||
|
get { return isFrozen; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes this instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Freeze() |
||||||
|
{ |
||||||
|
if (!isFrozen) { |
||||||
|
FreezeInternal(); |
||||||
|
isFrozen = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual void FreezeInternal() |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Text; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Utils |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// An immutable stack.
|
||||||
|
///
|
||||||
|
/// Using 'foreach' on the stack will return the items from top to bottom (in the order they would be popped).
|
||||||
|
/// </summary>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] |
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] |
||||||
|
[Serializable] |
||||||
|
public sealed class ImmutableStack<T> : IEnumerable<T> |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the empty stack instance.
|
||||||
|
/// </summary>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "ImmutableStack is immutable")] |
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] |
||||||
|
public static readonly ImmutableStack<T> Empty = new ImmutableStack<T>(); |
||||||
|
|
||||||
|
readonly T value; |
||||||
|
readonly ImmutableStack<T> next; |
||||||
|
|
||||||
|
private ImmutableStack() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
private ImmutableStack(T value, ImmutableStack<T> next) |
||||||
|
{ |
||||||
|
this.value = value; |
||||||
|
this.next = next; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes an item on the stack. This does not modify the stack itself, but returns a new
|
||||||
|
/// one with the value pushed.
|
||||||
|
/// </summary>
|
||||||
|
public ImmutableStack<T> Push(T item) |
||||||
|
{ |
||||||
|
return new ImmutableStack<T>(item, this); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the item on the top of the stack.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">The stack is empty.</exception>
|
||||||
|
public T Peek() |
||||||
|
{ |
||||||
|
if (IsEmpty) |
||||||
|
throw new InvalidOperationException("Operation not valid on empty stack."); |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the item on the top of the stack.
|
||||||
|
/// Returns <c>default(T)</c> if the stack is empty.
|
||||||
|
/// </summary>
|
||||||
|
public T PeekOrDefault() |
||||||
|
{ |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the stack with the top item removed.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">The stack is empty.</exception>
|
||||||
|
public ImmutableStack<T> Pop() |
||||||
|
{ |
||||||
|
if (IsEmpty) |
||||||
|
throw new InvalidOperationException("Operation not valid on empty stack."); |
||||||
|
return next; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if this stack is empty.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEmpty { |
||||||
|
get { return next == null; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an enumerator that iterates through the stack top-to-bottom.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerator<T> GetEnumerator() |
||||||
|
{ |
||||||
|
ImmutableStack<T> t = this; |
||||||
|
while (!t.IsEmpty) { |
||||||
|
yield return t.value; |
||||||
|
t = t.next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return this.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString() |
||||||
|
{ |
||||||
|
StringBuilder b = new StringBuilder("[Stack"); |
||||||
|
foreach (T val in this) { |
||||||
|
b.Append(' '); |
||||||
|
b.Append(val); |
||||||
|
} |
||||||
|
b.Append(']'); |
||||||
|
return b.ToString(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 468 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 567 B |