aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2020-01-29 01:53:41 +0200
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2020-01-29 01:53:41 +0200
commit8473fdd9207a14cf26c200f0289ae9ceca771e6a (patch)
treeb30523af284e29949597ac3e3a02d7cb4b310270 /Software/Visual_Studio/Scripting
parent4b70130484df80f00f28b5295ca0143f9b19c34a (diff)
downloadTango-8473fdd9207a14cf26c200f0289ae9ceca771e6a.tar.gz
Tango-8473fdd9207a14cf26c200f0289ae9ceca771e6a.zip
Fixed some issues with ScriptEditor.
Diffstat (limited to 'Software/Visual_Studio/Scripting')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs1695
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs729
2 files changed, 1246 insertions, 1178 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs
index 84fc86f44..a95d07fcf 100644
--- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/TextDocument.cs
@@ -14,823 +14,884 @@ using Tango.Scripting.Editors.Utils;
namespace Tango.Scripting.Editors.Document
{
- /// <summary>
- /// This class is the main class of the text model. Basically, it is a <see cref="System.Text.StringBuilder"/> with events.
- /// </summary>
- /// <remarks>
- /// <b>Thread safety:</b>
- /// <inheritdoc cref="VerifyAccess"/>
- /// <para>However, there is a single method that is thread-safe: <see cref="CreateSnapshot()"/> (and its overloads).</para>
- /// </remarks>
- public sealed class TextDocument : ITextSource, INotifyPropertyChanged
- {
- #region Thread ownership
- readonly object lockObject = new object();
- Thread owner = Thread.CurrentThread;
-
- /// <summary>
- /// Verifies that the current thread is the documents owner thread.
- /// Throws an <see cref="InvalidOperationException"/> if the wrong thread accesses the TextDocument.
- /// </summary>
- /// <remarks>
- /// <para>The TextDocument class is not thread-safe. A document instance expects to have a single owner thread
- /// and will throw an <see cref="InvalidOperationException"/> when accessed from another thread.
- /// It is possible to change the owner thread using the <see cref="SetOwnerThread"/> method.</para>
- /// </remarks>
- public void VerifyAccess()
- {
- if (Thread.CurrentThread != owner)
- throw new InvalidOperationException("TextDocument can be accessed only from the thread that owns it.");
- }
-
- /// <summary>
- /// Transfers ownership of the document to another thread. This method can be used to load
- /// a file into a TextDocument on a background thread and then transfer ownership to the UI thread
- /// for displaying the document.
- /// </summary>
- /// <remarks>
- /// <inheritdoc cref="VerifyAccess"/>
- /// <para>
- /// The owner can be set to null, which means that no thread can access the document. But, if the document
- /// has no owner thread, any thread may take ownership by calling <see cref="SetOwnerThread"/>.
- /// </para>
- /// </remarks>
- public void SetOwnerThread(Thread newOwner)
- {
- // We need to lock here to ensure that in the null owner case,
- // only one thread succeeds in taking ownership.
- lock (lockObject) {
- if (owner != null) {
- VerifyAccess();
- }
- owner = newOwner;
- }
- }
- #endregion
-
- #region Fields + Constructor
- readonly Rope<char> rope;
- readonly DocumentLineTree lineTree;
- readonly LineManager lineManager;
- readonly TextAnchorTree anchorTree;
- ChangeTrackingCheckpoint currentCheckpoint;
-
- /// <summary>
- /// Create an empty text document.
- /// </summary>
- public TextDocument()
- : this(string.Empty)
- {
- }
-
- /// <summary>
- /// Create a new text document with the specified initial text.
- /// </summary>
- public TextDocument(IEnumerable<char> initialText)
- {
- if (initialText == null)
- throw new ArgumentNullException("initialText");
- rope = new Rope<char>(initialText);
- lineTree = new DocumentLineTree(this);
- lineManager = new LineManager(lineTree, this);
- lineTrackers.CollectionChanged += delegate {
- lineManager.UpdateListOfLineTrackers();
- };
-
- anchorTree = new TextAnchorTree(this);
- undoStack = new UndoStack();
- FireChangeEvents();
- }
-
- /// <summary>
- /// Create a new text document with the specified initial text.
- /// </summary>
- public TextDocument(ITextSource initialText)
- : this(GetTextFromTextSource(initialText))
- {
- }
-
- // gets the text from a text source, directly retrieving the underlying rope where possible
- static IEnumerable<char> GetTextFromTextSource(ITextSource textSource)
- {
- if (textSource == null)
- throw new ArgumentNullException("textSource");
-
- RopeTextSource rts = textSource as RopeTextSource;
- if (rts != null)
- return rts.GetRope();
-
- TextDocument doc = textSource as TextDocument;
- if (doc != null)
- return doc.rope;
-
- return textSource.Text;
- }
- #endregion
-
- #region Text
- void ThrowIfRangeInvalid(int offset, int length)
- {
- if (offset < 0 || offset > rope.Length) {
- throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
- }
- if (length < 0 || offset + length > rope.Length) {
- throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
- }
- }
-
- /// <inheritdoc/>
- public string GetText(int offset, int length)
- {
- VerifyAccess();
- return rope.ToString(offset, length);
- }
-
- /// <summary>
- /// Retrieves the text for a portion of the document.
- /// </summary>
- public string GetText(ISegment segment)
- {
- if (segment == null)
- throw new ArgumentNullException("segment");
- return GetText(segment.Offset, segment.Length);
- }
-
- int ITextSource.IndexOfAny(char[] anyOf, int startIndex, int count)
- {
- DebugVerifyAccess(); // frequently called (NewLineFinder), so must be fast in release builds
- return rope.IndexOfAny(anyOf, startIndex, count);
- }
-
- /// <inheritdoc/>
- public char GetCharAt(int offset)
- {
- DebugVerifyAccess(); // frequently called, so must be fast in release builds
- return rope[offset];
- }
-
- WeakReference cachedText;
-
- /// <summary>
- /// Gets/Sets the text of the whole document.
- /// </summary>
- public string Text {
- get {
- VerifyAccess();
- string completeText = cachedText != null ? (cachedText.Target as string) : null;
- if (completeText == null) {
- completeText = rope.ToString();
- cachedText = new WeakReference(completeText);
- }
- return completeText;
- }
- set {
- VerifyAccess();
- if (value == null)
- throw new ArgumentNullException("value");
- Replace(0, rope.Length, value);
- }
- }
-
- /// <inheritdoc/>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public event EventHandler TextChanged;
-
- /// <inheritdoc/>
- public int TextLength {
- get {
- VerifyAccess();
- return rope.Length;
- }
- }
-
- /// <summary>
- /// Is raised when the TextLength property changes.
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")]
- public event EventHandler TextLengthChanged;
-
- /// <summary>
- /// Is raised when one of the properties <see cref="Text"/>, <see cref="TextLength"/>, <see cref="LineCount"/>,
- /// <see cref="UndoStack"/> changes.
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
- /// Is raised before the document changes.
- /// </summary>
- /// <remarks>
- /// <para>Here is the order in which events are raised during a document update:</para>
- /// <list type="bullet">
- /// <item><description><b><see cref="BeginUpdate">BeginUpdate()</see></b></description>
- /// <list type="bullet">
- /// <item><description>Start of change group (on undo stack)</description></item>
- /// <item><description><see cref="UpdateStarted"/> event is raised</description></item>
- /// </list></item>
- /// <item><description><b><see cref="Insert(int,string)">Insert()</see> / <see cref="Remove(int,int)">Remove()</see> / <see cref="Replace(int,int,string)">Replace()</see></b></description>
- /// <list type="bullet">
- /// <item><description><see cref="Changing"/> event is raised</description></item>
- /// <item><description>The document is changed</description></item>
- /// <item><description><see cref="TextAnchor.Deleted">TextAnchor.Deleted</see> event is raised if anchors were
- /// in the deleted text portion</description></item>
- /// <item><description><see cref="Changed"/> event is raised</description></item>
- /// </list></item>
- /// <item><description><b><see cref="EndUpdate">EndUpdate()</see></b></description>
- /// <list type="bullet">
- /// <item><description><see cref="TextChanged"/> event is raised</description></item>
- /// <item><description><see cref="PropertyChanged"/> event is raised (for the Text, TextLength, LineCount properties, in that order)</description></item>
- /// <item><description>End of change group (on undo stack)</description></item>
- /// <item><description><see cref="UpdateFinished"/> event is raised</description></item>
- /// </list></item>
- /// </list>
- /// <para>
- /// If the insert/remove/replace methods are called without a call to <c>BeginUpdate()</c>,
- /// they will call <c>BeginUpdate()</c> and <c>EndUpdate()</c> to ensure no change happens outside of <c>UpdateStarted</c>/<c>UpdateFinished</c>.
- /// </para><para>
- /// There can be multiple document changes between the <c>BeginUpdate()</c> and <c>EndUpdate()</c> calls.
- /// In this case, the events associated with EndUpdate will be raised only once after the whole document update is done.
- /// </para><para>
- /// The <see cref="UndoStack"/> listens to the <c>UpdateStarted</c> and <c>UpdateFinished</c> events to group all changes into a single undo step.
- /// </para>
- /// </remarks>
- public event EventHandler<DocumentChangeEventArgs> Changing;
-
- /// <summary>
- /// Is raised after the document has changed.
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public event EventHandler<DocumentChangeEventArgs> Changed;
-
- /// <summary>
- /// Creates a snapshot of the current text.
- /// </summary>
- /// <remarks>
- /// <para>This method returns an immutable snapshot of the document, and may be safely called even when
- /// the document's owner thread is concurrently modifying the document.
- /// </para><para>
- /// This special thread-safety guarantee is valid only for TextDocument.CreateSnapshot(), not necessarily for other
- /// classes implementing ITextSource.CreateSnapshot().
- /// </para><para>
- /// </para>
- /// </remarks>
- public ITextSource CreateSnapshot()
- {
- lock (lockObject) {
- return new RopeTextSource(rope.Clone());
- }
- }
-
- /// <summary>
- /// Creates a snapshot of the current text.
- /// Additionally, creates a checkpoint that allows tracking document changes.
- /// </summary>
- /// <remarks><inheritdoc cref="CreateSnapshot()"/><inheritdoc cref="ChangeTrackingCheckpoint"/></remarks>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Need to return snapshot and checkpoint together to ensure thread-safety")]
- public ITextSource CreateSnapshot(out ChangeTrackingCheckpoint checkpoint)
- {
- lock (lockObject) {
- if (currentCheckpoint == null)
- currentCheckpoint = new ChangeTrackingCheckpoint(lockObject);
- checkpoint = currentCheckpoint;
- return new RopeTextSource(rope.Clone());
- }
- }
-
- internal ChangeTrackingCheckpoint CreateChangeTrackingCheckpoint()
- {
- lock (lockObject) {
- if (currentCheckpoint == null)
- currentCheckpoint = new ChangeTrackingCheckpoint(lockObject);
- return currentCheckpoint;
- }
- }
-
- /// <summary>
- /// Creates a snapshot of a part of the current text.
- /// </summary>
- /// <remarks><inheritdoc cref="CreateSnapshot()"/></remarks>
- public ITextSource CreateSnapshot(int offset, int length)
- {
- lock (lockObject) {
- return new RopeTextSource(rope.GetRange(offset, length));
- }
- }
-
- /// <inheritdoc/>
- public System.IO.TextReader CreateReader()
- {
- lock (lockObject) {
- return new RopeTextReader(rope);
- }
- }
- #endregion
-
- #region BeginUpdate / EndUpdate
- int beginUpdateCount;
-
- /// <summary>
- /// Gets if an update is running.
- /// </summary>
- /// <remarks><inheritdoc cref="BeginUpdate"/></remarks>
- public bool IsInUpdate {
- get {
- VerifyAccess();
- return beginUpdateCount > 0;
- }
- }
-
- /// <summary>
- /// Immediately calls <see cref="BeginUpdate()"/>,
- /// and returns an IDisposable that calls <see cref="EndUpdate()"/>.
- /// </summary>
- /// <remarks><inheritdoc cref="BeginUpdate"/></remarks>
- public IDisposable RunUpdate()
- {
- BeginUpdate();
- return new CallbackOnDispose(EndUpdate);
- }
-
- /// <summary>
- /// <para>Begins a group of document changes.</para>
- /// <para>Some events are suspended until EndUpdate is called, and the <see cref="UndoStack"/> will
- /// group all changes into a single action.</para>
- /// <para>Calling BeginUpdate several times increments a counter, only after the appropriate number
- /// of EndUpdate calls the events resume their work.</para>
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public void BeginUpdate()
- {
- VerifyAccess();
- if (inDocumentChanging)
- throw new InvalidOperationException("Cannot change document within another document change.");
- beginUpdateCount++;
- if (beginUpdateCount == 1) {
- undoStack.StartUndoGroup();
- if (UpdateStarted != null)
- UpdateStarted(this, EventArgs.Empty);
- }
- }
-
- /// <summary>
- /// Ends a group of document changes.
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public void EndUpdate()
- {
- VerifyAccess();
- if (inDocumentChanging)
- throw new InvalidOperationException("Cannot end update within document change.");
- if (beginUpdateCount == 0)
- throw new InvalidOperationException("No update is active.");
- if (beginUpdateCount == 1) {
- // fire change events inside the change group - event handlers might add additional
- // document changes to the change group
- FireChangeEvents();
- undoStack.EndUndoGroup();
- beginUpdateCount = 0;
- if (UpdateFinished != null)
- UpdateFinished(this, EventArgs.Empty);
- } else {
- beginUpdateCount -= 1;
- }
- }
-
- /// <summary>
- /// Occurs when a document change starts.
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public event EventHandler UpdateStarted;
-
- /// <summary>
- /// Occurs when a document change is finished.
- /// </summary>
- /// <remarks><inheritdoc cref="Changing"/></remarks>
- public event EventHandler UpdateFinished;
- #endregion
-
- #region Fire events after update
- int oldTextLength;
- int oldLineCount;
- bool fireTextChanged;
-
- /// <summary>
- /// Fires TextChanged, TextLengthChanged, LineCountChanged if required.
- /// </summary>
- internal void FireChangeEvents()
- {
- // it may be necessary to fire the event multiple times if the document is changed
- // from inside the event handlers
- while (fireTextChanged) {
- fireTextChanged = false;
- if (TextChanged != null)
- TextChanged(this, EventArgs.Empty);
- OnPropertyChanged("Text");
-
- int textLength = rope.Length;
- if (textLength != oldTextLength) {
- oldTextLength = textLength;
- if (TextLengthChanged != null)
- TextLengthChanged(this, EventArgs.Empty);
- OnPropertyChanged("TextLength");
- }
- int lineCount = lineTree.LineCount;
- if (lineCount != oldLineCount) {
- oldLineCount = lineCount;
- if (LineCountChanged != null)
- LineCountChanged(this, EventArgs.Empty);
- OnPropertyChanged("LineCount");
- }
- }
- }
-
- void OnPropertyChanged(string propertyName)
- {
- if (PropertyChanged != null)
- PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
- #endregion
-
- #region Insert / Remove / Replace
- /// <summary>
- /// Inserts text.
- /// </summary>
- public void Insert(int offset, string text)
- {
- Replace(offset, 0, text);
- }
-
- /// <summary>
- /// Removes text.
- /// </summary>
- public void Remove(ISegment segment)
- {
- Replace(segment, string.Empty);
- }
-
- /// <summary>
- /// Removes text.
- /// </summary>
- public void Remove(int offset, int length)
- {
- Replace(offset, length, string.Empty);
- }
-
- internal bool inDocumentChanging;
-
- /// <summary>
- /// Replaces text.
- /// </summary>
- public void Replace(ISegment segment, string text)
- {
- if (segment == null)
- throw new ArgumentNullException("segment");
- Replace(segment.Offset, segment.Length, text, null);
- }
-
- /// <summary>
- /// Replaces text.
- /// </summary>
- public void Replace(int offset, int length, string text)
- {
- Replace(offset, length, text, null);
- }
-
- /// <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="text">The new text.</param>
- /// <param name="offsetChangeMappingType">The offsetChangeMappingType determines how offsets inside the old text are mapped to the new text.
- /// This affects how the anchors and segments inside the replaced region behave.</param>
- public void Replace(int offset, int length, string text, OffsetChangeMappingType offsetChangeMappingType)
- {
- if (text == null)
- throw new ArgumentNullException("text");
- // Please see OffsetChangeMappingType XML comments for details on how these modes work.
- switch (offsetChangeMappingType) {
- case OffsetChangeMappingType.Normal:
- Replace(offset, length, text, null);
- break;
- case OffsetChangeMappingType.KeepAnchorBeforeInsertion:
- Replace(offset, length, text, OffsetChangeMap.FromSingleElement(
- new OffsetChangeMapEntry(offset, length, text.Length, false, true)));
- break;
- case OffsetChangeMappingType.RemoveAndInsert:
- if (length == 0 || text.Length == 0) {
- // only insertion or only removal?
- // OffsetChangeMappingType doesn't matter, just use Normal.
- Replace(offset, length, text, null);
- } else {
- OffsetChangeMap map = new OffsetChangeMap(2);
- map.Add(new OffsetChangeMapEntry(offset, length, 0));
- map.Add(new OffsetChangeMapEntry(offset, 0, text.Length));
- map.Freeze();
- Replace(offset, length, text, map);
- }
- break;
- case OffsetChangeMappingType.CharacterReplace:
- if (length == 0 || text.Length == 0) {
- // only insertion or only removal?
- // OffsetChangeMappingType doesn't matter, just use Normal.
- Replace(offset, length, text, null);
- } else if (text.Length > length) {
- // look at OffsetChangeMappingType.CharacterReplace XML comments on why we need to replace
- // the last character
- OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + length - 1, 1, 1 + text.Length - length);
- Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry));
- } else if (text.Length < length) {
- OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + text.Length, length - text.Length, 0, true, false);
- Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry));
- } else {
- Replace(offset, length, text, OffsetChangeMap.Empty);
- }
- break;
- default:
- throw new ArgumentOutOfRangeException("offsetChangeMappingType", offsetChangeMappingType, "Invalid enum value");
- }
- }
-
- /// <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="text">The new text.</param>
- /// <param name="offsetChangeMap">The offsetChangeMap determines how offsets inside the old text are mapped to the new text.
- /// This affects how the anchors and segments inside the replaced region behave.
- /// If you pass null (the default when using one of the other overloads), the offsets are changed as
- /// in OffsetChangeMappingType.Normal mode.
- /// If you pass OffsetChangeMap.Empty, then everything will stay in its old place (OffsetChangeMappingType.CharacterReplace mode).
- /// The offsetChangeMap must be a valid 'explanation' for the document change. See <see cref="OffsetChangeMap.IsValidForDocumentChange"/>.
- /// Passing an OffsetChangeMap to the Replace method will automatically freeze it to ensure the thread safety of the resulting
- /// DocumentChangeEventArgs instance.
- /// </param>
- public void Replace(int offset, int length, string text, OffsetChangeMap offsetChangeMap)
- {
- if (text == null)
- throw new ArgumentNullException("text");
-
- if (offsetChangeMap != null)
- offsetChangeMap.Freeze();
-
- // Ensure that all changes take place inside an update group.
- // Will also take care of throwing an exception if inDocumentChanging is set.
- BeginUpdate();
- try {
- // protect document change against corruption by other changes inside the event handlers
- inDocumentChanging = true;
- try {
- // The range verification must wait until after the BeginUpdate() call because the document
- // might be modified inside the UpdateStarted event.
- ThrowIfRangeInvalid(offset, length);
-
- DoReplace(offset, length, text, offsetChangeMap);
- } finally {
- inDocumentChanging = false;
- }
- } finally {
- EndUpdate();
- }
- }
-
- void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap)
- {
- if (length == 0 && newText.Length == 0)
- return;
-
- // trying to replace a single character in 'Normal' mode?
- // for single characters, 'CharacterReplace' mode is equivalent, but more performant
- // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode)
- if (length == 1 && newText.Length == 1 && offsetChangeMap == null)
- offsetChangeMap = OffsetChangeMap.Empty;
-
- string removedText = rope.ToString(offset, length);
- DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap);
-
- // fire DocumentChanging event
- if (Changing != null)
- Changing(this, args);
-
- undoStack.Push(this, args);
-
- cachedText = null; // reset cache of complete document text
- fireTextChanged = true;
- DelayedEvents delayedEvents = new DelayedEvents();
-
- lock (lockObject) {
- // create linked list of checkpoints, if required
- if (currentCheckpoint != null) {
- currentCheckpoint = currentCheckpoint.Append(args);
- }
-
- // now update the textBuffer and lineTree
- if (offset == 0 && length == rope.Length) {
- // optimize replacing the whole document
- rope.Clear();
- rope.InsertText(0, newText);
- lineManager.Rebuild();
- } else {
- rope.RemoveRange(offset, length);
- lineManager.Remove(offset, length);
- #if DEBUG
- lineTree.CheckProperties();
- #endif
- rope.InsertText(offset, newText);
- lineManager.Insert(offset, newText);
- #if DEBUG
- lineTree.CheckProperties();
- #endif
- }
- }
-
- // update text anchors
- if (offsetChangeMap == null) {
- anchorTree.HandleTextChange(args.CreateSingleChangeMapEntry(), delayedEvents);
- } else {
- foreach (OffsetChangeMapEntry entry in offsetChangeMap) {
- anchorTree.HandleTextChange(entry, delayedEvents);
- }
- }
-
- // raise delayed events after our data structures are consistent again
- delayedEvents.RaiseEvents();
-
- // fire DocumentChanged event
- if (Changed != null)
- Changed(this, args);
- }
- #endregion
-
- #region GetLineBy...
- /// <summary>
- /// Gets a read-only list of lines.
- /// </summary>
- /// <remarks><inheritdoc cref="DocumentLine"/></remarks>
- public IList<DocumentLine> Lines {
- get { return lineTree; }
- }
-
- /// <summary>
- /// Gets a line by the line number: O(log n)
- /// </summary>
- public DocumentLine GetLineByNumber(int number)
- {
- VerifyAccess();
- if (number < 1 || number > lineTree.LineCount)
- throw new ArgumentOutOfRangeException("number", number, "Value must be between 1 and " + lineTree.LineCount);
- return lineTree.GetByNumber(number);
- }
-
- /// <summary>
- /// Gets a document lines by offset.
- /// Runtime: O(log n)
- /// </summary>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
- public DocumentLine GetLineByOffset(int offset)
- {
- VerifyAccess();
- if (offset < 0 || offset > rope.Length) {
- throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString());
- }
- return lineTree.GetByOffset(offset);
- }
- #endregion
-
- /// <summary>
- /// Gets the offset from a text location.
- /// </summary>
- /// <seealso cref="GetLocation"/>
- public int GetOffset(TextLocation location)
- {
- return GetOffset(location.Line, location.Column);
- }
-
- /// <summary>
- /// Gets the offset from a text location.
- /// </summary>
- /// <seealso cref="GetLocation"/>
- public int GetOffset(int line, int column)
- {
- DocumentLine docLine = GetLineByNumber(line);
- if (column <= 0)
- return docLine.Offset;
- if (column > docLine.Length)
- return docLine.EndOffset;
- return docLine.Offset + column - 1;
- }
-
- /// <summary>
- /// Gets the location from an offset.
- /// </summary>
- /// <seealso cref="GetOffset(TextLocation)"/>
- public TextLocation GetLocation(int offset)
- {
- DocumentLine line = GetLineByOffset(offset);
- return new TextLocation(line.LineNumber, offset - line.Offset + 1);
- }
-
- readonly ObservableCollection<ILineTracker> lineTrackers = new ObservableCollection<ILineTracker>();
-
- /// <summary>
- /// Gets the list of <see cref="ILineTracker"/>s attached to this document.
- /// You can add custom line trackers to this list.
- /// </summary>
- public IList<ILineTracker> LineTrackers {
- get {
- VerifyAccess();
- return lineTrackers;
- }
- }
-
- UndoStack undoStack;
-
- /// <summary>
- /// Gets the <see cref="UndoStack"/> of the document.
- /// </summary>
- /// <remarks>This property can also be used to set the undo stack, e.g. for sharing a common undo stack between multiple documents.</remarks>
- public UndoStack UndoStack {
- get { return undoStack; }
- set {
- if (value == null)
- throw new ArgumentNullException();
- if (value != undoStack) {
- undoStack.ClearAll(); // first clear old undo stack, so that it can't be used to perform unexpected changes on this document
- // ClearAll() will also throw an exception when it's not safe to replace the undo stack (e.g. update is currently in progress)
- undoStack = value;
- OnPropertyChanged("UndoStack");
- }
- }
- }
-
- /// <summary>
- /// Creates a new <see cref="TextAnchor"/> at the specified offset.
- /// </summary>
- /// <inheritdoc cref="TextAnchor" select="remarks|example"/>
- public TextAnchor CreateAnchor(int offset)
- {
- VerifyAccess();
- if (offset < 0 || offset > rope.Length) {
- throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
- }
- return anchorTree.CreateAnchor(offset);
- }
-
- #region LineCount
- /// <summary>
- /// Gets the total number of lines in the document.
- /// Runtime: O(1).
- /// </summary>
- public int LineCount {
- get {
- VerifyAccess();
- return lineTree.LineCount;
- }
- }
-
- /// <summary>
- /// Is raised when the LineCount property changes.
- /// </summary>
- [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")]
- public event EventHandler LineCountChanged;
- #endregion
-
- #region Debugging
- [Conditional("DEBUG")]
- internal void DebugVerifyAccess()
- {
- VerifyAccess();
- }
-
- /// <summary>
- /// Gets the document lines tree in string form.
- /// </summary>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
- internal string GetLineTreeAsString()
- {
- #if DEBUG
- return lineTree.GetTreeAsString();
- #else
+ /// <summary>
+ /// This class is the main class of the text model. Basically, it is a <see cref="System.Text.StringBuilder"/> with events.
+ /// </summary>
+ /// <remarks>
+ /// <b>Thread safety:</b>
+ /// <inheritdoc cref="VerifyAccess"/>
+ /// <para>However, there is a single method that is thread-safe: <see cref="CreateSnapshot()"/> (and its overloads).</para>
+ /// </remarks>
+ public sealed class TextDocument : ITextSource, INotifyPropertyChanged
+ {
+ #region Thread ownership
+ readonly object lockObject = new object();
+ Thread owner = Thread.CurrentThread;
+
+ /// <summary>
+ /// Verifies that the current thread is the documents owner thread.
+ /// Throws an <see cref="InvalidOperationException"/> if the wrong thread accesses the TextDocument.
+ /// </summary>
+ /// <remarks>
+ /// <para>The TextDocument class is not thread-safe. A document instance expects to have a single owner thread
+ /// and will throw an <see cref="InvalidOperationException"/> when accessed from another thread.
+ /// It is possible to change the owner thread using the <see cref="SetOwnerThread"/> method.</para>
+ /// </remarks>
+ public void VerifyAccess()
+ {
+ if (Thread.CurrentThread != owner)
+ throw new InvalidOperationException("TextDocument can be accessed only from the thread that owns it.");
+ }
+
+ /// <summary>
+ /// Transfers ownership of the document to another thread. This method can be used to load
+ /// a file into a TextDocument on a background thread and then transfer ownership to the UI thread
+ /// for displaying the document.
+ /// </summary>
+ /// <remarks>
+ /// <inheritdoc cref="VerifyAccess"/>
+ /// <para>
+ /// The owner can be set to null, which means that no thread can access the document. But, if the document
+ /// has no owner thread, any thread may take ownership by calling <see cref="SetOwnerThread"/>.
+ /// </para>
+ /// </remarks>
+ public void SetOwnerThread(Thread newOwner)
+ {
+ // We need to lock here to ensure that in the null owner case,
+ // only one thread succeeds in taking ownership.
+ lock (lockObject)
+ {
+ if (owner != null)
+ {
+ VerifyAccess();
+ }
+ owner = newOwner;
+ }
+ }
+ #endregion
+
+ #region Fields + Constructor
+ readonly Rope<char> rope;
+ readonly DocumentLineTree lineTree;
+ readonly LineManager lineManager;
+ readonly TextAnchorTree anchorTree;
+ ChangeTrackingCheckpoint currentCheckpoint;
+
+ /// <summary>
+ /// Create an empty text document.
+ /// </summary>
+ public TextDocument()
+ : this(string.Empty)
+ {
+ }
+
+ /// <summary>
+ /// Create a new text document with the specified initial text.
+ /// </summary>
+ public TextDocument(IEnumerable<char> initialText)
+ {
+ if (initialText == null)
+ throw new ArgumentNullException("initialText");
+ rope = new Rope<char>(initialText);
+ lineTree = new DocumentLineTree(this);
+ lineManager = new LineManager(lineTree, this);
+ lineTrackers.CollectionChanged += delegate
+ {
+ lineManager.UpdateListOfLineTrackers();
+ };
+
+ anchorTree = new TextAnchorTree(this);
+ undoStack = new UndoStack();
+ FireChangeEvents();
+ }
+
+ /// <summary>
+ /// Create a new text document with the specified initial text.
+ /// </summary>
+ public TextDocument(ITextSource initialText)
+ : this(GetTextFromTextSource(initialText))
+ {
+ }
+
+ // gets the text from a text source, directly retrieving the underlying rope where possible
+ static IEnumerable<char> GetTextFromTextSource(ITextSource textSource)
+ {
+ if (textSource == null)
+ throw new ArgumentNullException("textSource");
+
+ RopeTextSource rts = textSource as RopeTextSource;
+ if (rts != null)
+ return rts.GetRope();
+
+ TextDocument doc = textSource as TextDocument;
+ if (doc != null)
+ return doc.rope;
+
+ return textSource.Text;
+ }
+ #endregion
+
+ #region Text
+ void ThrowIfRangeInvalid(int offset, int length)
+ {
+ if (offset < 0 || offset > rope.Length)
+ {
+ throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
+ }
+ if (length < 0 || offset + length > rope.Length)
+ {
+ throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
+ }
+ }
+
+ /// <inheritdoc/>
+ public string GetText(int offset, int length)
+ {
+ VerifyAccess();
+ return rope.ToString(Math.Max(offset, 0), length);
+ }
+
+ /// <summary>
+ /// Retrieves the text for a portion of the document.
+ /// </summary>
+ public string GetText(ISegment segment)
+ {
+ if (segment == null)
+ throw new ArgumentNullException("segment");
+ return GetText(segment.Offset, segment.Length);
+ }
+
+ int ITextSource.IndexOfAny(char[] anyOf, int startIndex, int count)
+ {
+ DebugVerifyAccess(); // frequently called (NewLineFinder), so must be fast in release builds
+ return rope.IndexOfAny(anyOf, startIndex, count);
+ }
+
+ /// <inheritdoc/>
+ public char GetCharAt(int offset)
+ {
+ DebugVerifyAccess(); // frequently called, so must be fast in release builds
+ return rope[offset];
+ }
+
+ WeakReference cachedText;
+
+ /// <summary>
+ /// Gets/Sets the text of the whole document.
+ /// </summary>
+ public string Text
+ {
+ get
+ {
+ VerifyAccess();
+ string completeText = cachedText != null ? (cachedText.Target as string) : null;
+ if (completeText == null)
+ {
+ completeText = rope.ToString();
+ cachedText = new WeakReference(completeText);
+ }
+ return completeText;
+ }
+ set
+ {
+ VerifyAccess();
+ if (value == null)
+ throw new ArgumentNullException("value");
+ Replace(0, rope.Length, value);
+ }
+ }
+
+ /// <inheritdoc/>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public event EventHandler TextChanged;
+
+ /// <inheritdoc/>
+ public int TextLength
+ {
+ get
+ {
+ VerifyAccess();
+ return rope.Length;
+ }
+ }
+
+ /// <summary>
+ /// Is raised when the TextLength property changes.
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")]
+ public event EventHandler TextLengthChanged;
+
+ /// <summary>
+ /// Is raised when one of the properties <see cref="Text"/>, <see cref="TextLength"/>, <see cref="LineCount"/>,
+ /// <see cref="UndoStack"/> changes.
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// Is raised before the document changes.
+ /// </summary>
+ /// <remarks>
+ /// <para>Here is the order in which events are raised during a document update:</para>
+ /// <list type="bullet">
+ /// <item><description><b><see cref="BeginUpdate">BeginUpdate()</see></b></description>
+ /// <list type="bullet">
+ /// <item><description>Start of change group (on undo stack)</description></item>
+ /// <item><description><see cref="UpdateStarted"/> event is raised</description></item>
+ /// </list></item>
+ /// <item><description><b><see cref="Insert(int,string)">Insert()</see> / <see cref="Remove(int,int)">Remove()</see> / <see cref="Replace(int,int,string)">Replace()</see></b></description>
+ /// <list type="bullet">
+ /// <item><description><see cref="Changing"/> event is raised</description></item>
+ /// <item><description>The document is changed</description></item>
+ /// <item><description><see cref="TextAnchor.Deleted">TextAnchor.Deleted</see> event is raised if anchors were
+ /// in the deleted text portion</description></item>
+ /// <item><description><see cref="Changed"/> event is raised</description></item>
+ /// </list></item>
+ /// <item><description><b><see cref="EndUpdate">EndUpdate()</see></b></description>
+ /// <list type="bullet">
+ /// <item><description><see cref="TextChanged"/> event is raised</description></item>
+ /// <item><description><see cref="PropertyChanged"/> event is raised (for the Text, TextLength, LineCount properties, in that order)</description></item>
+ /// <item><description>End of change group (on undo stack)</description></item>
+ /// <item><description><see cref="UpdateFinished"/> event is raised</description></item>
+ /// </list></item>
+ /// </list>
+ /// <para>
+ /// If the insert/remove/replace methods are called without a call to <c>BeginUpdate()</c>,
+ /// they will call <c>BeginUpdate()</c> and <c>EndUpdate()</c> to ensure no change happens outside of <c>UpdateStarted</c>/<c>UpdateFinished</c>.
+ /// </para><para>
+ /// There can be multiple document changes between the <c>BeginUpdate()</c> and <c>EndUpdate()</c> calls.
+ /// In this case, the events associated with EndUpdate will be raised only once after the whole document update is done.
+ /// </para><para>
+ /// The <see cref="UndoStack"/> listens to the <c>UpdateStarted</c> and <c>UpdateFinished</c> events to group all changes into a single undo step.
+ /// </para>
+ /// </remarks>
+ public event EventHandler<DocumentChangeEventArgs> Changing;
+
+ /// <summary>
+ /// Is raised after the document has changed.
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public event EventHandler<DocumentChangeEventArgs> Changed;
+
+ /// <summary>
+ /// Creates a snapshot of the current text.
+ /// </summary>
+ /// <remarks>
+ /// <para>This method returns an immutable snapshot of the document, and may be safely called even when
+ /// the document's owner thread is concurrently modifying the document.
+ /// </para><para>
+ /// This special thread-safety guarantee is valid only for TextDocument.CreateSnapshot(), not necessarily for other
+ /// classes implementing ITextSource.CreateSnapshot().
+ /// </para><para>
+ /// </para>
+ /// </remarks>
+ public ITextSource CreateSnapshot()
+ {
+ lock (lockObject)
+ {
+ return new RopeTextSource(rope.Clone());
+ }
+ }
+
+ /// <summary>
+ /// Creates a snapshot of the current text.
+ /// Additionally, creates a checkpoint that allows tracking document changes.
+ /// </summary>
+ /// <remarks><inheritdoc cref="CreateSnapshot()"/><inheritdoc cref="ChangeTrackingCheckpoint"/></remarks>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Need to return snapshot and checkpoint together to ensure thread-safety")]
+ public ITextSource CreateSnapshot(out ChangeTrackingCheckpoint checkpoint)
+ {
+ lock (lockObject)
+ {
+ if (currentCheckpoint == null)
+ currentCheckpoint = new ChangeTrackingCheckpoint(lockObject);
+ checkpoint = currentCheckpoint;
+ return new RopeTextSource(rope.Clone());
+ }
+ }
+
+ internal ChangeTrackingCheckpoint CreateChangeTrackingCheckpoint()
+ {
+ lock (lockObject)
+ {
+ if (currentCheckpoint == null)
+ currentCheckpoint = new ChangeTrackingCheckpoint(lockObject);
+ return currentCheckpoint;
+ }
+ }
+
+ /// <summary>
+ /// Creates a snapshot of a part of the current text.
+ /// </summary>
+ /// <remarks><inheritdoc cref="CreateSnapshot()"/></remarks>
+ public ITextSource CreateSnapshot(int offset, int length)
+ {
+ lock (lockObject)
+ {
+ return new RopeTextSource(rope.GetRange(offset, length));
+ }
+ }
+
+ /// <inheritdoc/>
+ public System.IO.TextReader CreateReader()
+ {
+ lock (lockObject)
+ {
+ return new RopeTextReader(rope);
+ }
+ }
+ #endregion
+
+ #region BeginUpdate / EndUpdate
+ int beginUpdateCount;
+
+ /// <summary>
+ /// Gets if an update is running.
+ /// </summary>
+ /// <remarks><inheritdoc cref="BeginUpdate"/></remarks>
+ public bool IsInUpdate
+ {
+ get
+ {
+ VerifyAccess();
+ return beginUpdateCount > 0;
+ }
+ }
+
+ /// <summary>
+ /// Immediately calls <see cref="BeginUpdate()"/>,
+ /// and returns an IDisposable that calls <see cref="EndUpdate()"/>.
+ /// </summary>
+ /// <remarks><inheritdoc cref="BeginUpdate"/></remarks>
+ public IDisposable RunUpdate()
+ {
+ BeginUpdate();
+ return new CallbackOnDispose(EndUpdate);
+ }
+
+ /// <summary>
+ /// <para>Begins a group of document changes.</para>
+ /// <para>Some events are suspended until EndUpdate is called, and the <see cref="UndoStack"/> will
+ /// group all changes into a single action.</para>
+ /// <para>Calling BeginUpdate several times increments a counter, only after the appropriate number
+ /// of EndUpdate calls the events resume their work.</para>
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public void BeginUpdate()
+ {
+ VerifyAccess();
+ if (inDocumentChanging)
+ throw new InvalidOperationException("Cannot change document within another document change.");
+ beginUpdateCount++;
+ if (beginUpdateCount == 1)
+ {
+ undoStack.StartUndoGroup();
+ if (UpdateStarted != null)
+ UpdateStarted(this, EventArgs.Empty);
+ }
+ }
+
+ /// <summary>
+ /// Ends a group of document changes.
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public void EndUpdate()
+ {
+ VerifyAccess();
+ if (inDocumentChanging)
+ throw new InvalidOperationException("Cannot end update within document change.");
+ if (beginUpdateCount == 0)
+ throw new InvalidOperationException("No update is active.");
+ if (beginUpdateCount == 1)
+ {
+ // fire change events inside the change group - event handlers might add additional
+ // document changes to the change group
+ FireChangeEvents();
+ undoStack.EndUndoGroup();
+ beginUpdateCount = 0;
+ if (UpdateFinished != null)
+ UpdateFinished(this, EventArgs.Empty);
+ }
+ else
+ {
+ beginUpdateCount -= 1;
+ }
+ }
+
+ /// <summary>
+ /// Occurs when a document change starts.
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public event EventHandler UpdateStarted;
+
+ /// <summary>
+ /// Occurs when a document change is finished.
+ /// </summary>
+ /// <remarks><inheritdoc cref="Changing"/></remarks>
+ public event EventHandler UpdateFinished;
+ #endregion
+
+ #region Fire events after update
+ int oldTextLength;
+ int oldLineCount;
+ bool fireTextChanged;
+
+ /// <summary>
+ /// Fires TextChanged, TextLengthChanged, LineCountChanged if required.
+ /// </summary>
+ internal void FireChangeEvents()
+ {
+ // it may be necessary to fire the event multiple times if the document is changed
+ // from inside the event handlers
+ while (fireTextChanged)
+ {
+ fireTextChanged = false;
+ if (TextChanged != null)
+ TextChanged(this, EventArgs.Empty);
+ OnPropertyChanged("Text");
+
+ int textLength = rope.Length;
+ if (textLength != oldTextLength)
+ {
+ oldTextLength = textLength;
+ if (TextLengthChanged != null)
+ TextLengthChanged(this, EventArgs.Empty);
+ OnPropertyChanged("TextLength");
+ }
+ int lineCount = lineTree.LineCount;
+ if (lineCount != oldLineCount)
+ {
+ oldLineCount = lineCount;
+ if (LineCountChanged != null)
+ LineCountChanged(this, EventArgs.Empty);
+ OnPropertyChanged("LineCount");
+ }
+ }
+ }
+
+ void OnPropertyChanged(string propertyName)
+ {
+ if (PropertyChanged != null)
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+ #endregion
+
+ #region Insert / Remove / Replace
+ /// <summary>
+ /// Inserts text.
+ /// </summary>
+ public void Insert(int offset, string text)
+ {
+ Replace(offset, 0, text);
+ }
+
+ /// <summary>
+ /// Removes text.
+ /// </summary>
+ public void Remove(ISegment segment)
+ {
+ Replace(segment, string.Empty);
+ }
+
+ /// <summary>
+ /// Removes text.
+ /// </summary>
+ public void Remove(int offset, int length)
+ {
+ Replace(offset, length, string.Empty);
+ }
+
+ internal bool inDocumentChanging;
+
+ /// <summary>
+ /// Replaces text.
+ /// </summary>
+ public void Replace(ISegment segment, string text)
+ {
+ if (segment == null)
+ throw new ArgumentNullException("segment");
+ Replace(segment.Offset, segment.Length, text, null);
+ }
+
+ /// <summary>
+ /// Replaces text.
+ /// </summary>
+ public void Replace(int offset, int length, string text)
+ {
+ Replace(offset, length, text, null);
+ }
+
+ /// <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="text">The new text.</param>
+ /// <param name="offsetChangeMappingType">The offsetChangeMappingType determines how offsets inside the old text are mapped to the new text.
+ /// This affects how the anchors and segments inside the replaced region behave.</param>
+ public void Replace(int offset, int length, string text, OffsetChangeMappingType offsetChangeMappingType)
+ {
+ if (text == null)
+ throw new ArgumentNullException("text");
+ // Please see OffsetChangeMappingType XML comments for details on how these modes work.
+ switch (offsetChangeMappingType)
+ {
+ case OffsetChangeMappingType.Normal:
+ Replace(offset, length, text, null);
+ break;
+ case OffsetChangeMappingType.KeepAnchorBeforeInsertion:
+ Replace(offset, length, text, OffsetChangeMap.FromSingleElement(
+ new OffsetChangeMapEntry(offset, length, text.Length, false, true)));
+ break;
+ case OffsetChangeMappingType.RemoveAndInsert:
+ if (length == 0 || text.Length == 0)
+ {
+ // only insertion or only removal?
+ // OffsetChangeMappingType doesn't matter, just use Normal.
+ Replace(offset, length, text, null);
+ }
+ else
+ {
+ OffsetChangeMap map = new OffsetChangeMap(2);
+ map.Add(new OffsetChangeMapEntry(offset, length, 0));
+ map.Add(new OffsetChangeMapEntry(offset, 0, text.Length));
+ map.Freeze();
+ Replace(offset, length, text, map);
+ }
+ break;
+ case OffsetChangeMappingType.CharacterReplace:
+ if (length == 0 || text.Length == 0)
+ {
+ // only insertion or only removal?
+ // OffsetChangeMappingType doesn't matter, just use Normal.
+ Replace(offset, length, text, null);
+ }
+ else if (text.Length > length)
+ {
+ // look at OffsetChangeMappingType.CharacterReplace XML comments on why we need to replace
+ // the last character
+ OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + length - 1, 1, 1 + text.Length - length);
+ Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry));
+ }
+ else if (text.Length < length)
+ {
+ OffsetChangeMapEntry entry = new OffsetChangeMapEntry(offset + text.Length, length - text.Length, 0, true, false);
+ Replace(offset, length, text, OffsetChangeMap.FromSingleElement(entry));
+ }
+ else
+ {
+ Replace(offset, length, text, OffsetChangeMap.Empty);
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("offsetChangeMappingType", offsetChangeMappingType, "Invalid enum value");
+ }
+ }
+
+ /// <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="text">The new text.</param>
+ /// <param name="offsetChangeMap">The offsetChangeMap determines how offsets inside the old text are mapped to the new text.
+ /// This affects how the anchors and segments inside the replaced region behave.
+ /// If you pass null (the default when using one of the other overloads), the offsets are changed as
+ /// in OffsetChangeMappingType.Normal mode.
+ /// If you pass OffsetChangeMap.Empty, then everything will stay in its old place (OffsetChangeMappingType.CharacterReplace mode).
+ /// The offsetChangeMap must be a valid 'explanation' for the document change. See <see cref="OffsetChangeMap.IsValidForDocumentChange"/>.
+ /// Passing an OffsetChangeMap to the Replace method will automatically freeze it to ensure the thread safety of the resulting
+ /// DocumentChangeEventArgs instance.
+ /// </param>
+ public void Replace(int offset, int length, string text, OffsetChangeMap offsetChangeMap)
+ {
+ if (text == null)
+ throw new ArgumentNullException("text");
+
+ if (offsetChangeMap != null)
+ offsetChangeMap.Freeze();
+
+ // Ensure that all changes take place inside an update group.
+ // Will also take care of throwing an exception if inDocumentChanging is set.
+ BeginUpdate();
+ try
+ {
+ // protect document change against corruption by other changes inside the event handlers
+ inDocumentChanging = true;
+ try
+ {
+ // The range verification must wait until after the BeginUpdate() call because the document
+ // might be modified inside the UpdateStarted event.
+ ThrowIfRangeInvalid(offset, length);
+
+ DoReplace(offset, length, text, offsetChangeMap);
+ }
+ finally
+ {
+ inDocumentChanging = false;
+ }
+ }
+ finally
+ {
+ EndUpdate();
+ }
+ }
+
+ void DoReplace(int offset, int length, string newText, OffsetChangeMap offsetChangeMap)
+ {
+ if (length == 0 && newText.Length == 0)
+ return;
+
+ // trying to replace a single character in 'Normal' mode?
+ // for single characters, 'CharacterReplace' mode is equivalent, but more performant
+ // (we don't have to touch the anchorTree at all in 'CharacterReplace' mode)
+ if (length == 1 && newText.Length == 1 && offsetChangeMap == null)
+ offsetChangeMap = OffsetChangeMap.Empty;
+
+ string removedText = rope.ToString(offset, length);
+ DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, removedText, newText, offsetChangeMap);
+
+ // fire DocumentChanging event
+ if (Changing != null)
+ Changing(this, args);
+
+ undoStack.Push(this, args);
+
+ cachedText = null; // reset cache of complete document text
+ fireTextChanged = true;
+ DelayedEvents delayedEvents = new DelayedEvents();
+
+ lock (lockObject)
+ {
+ // create linked list of checkpoints, if required
+ if (currentCheckpoint != null)
+ {
+ currentCheckpoint = currentCheckpoint.Append(args);
+ }
+
+ // now update the textBuffer and lineTree
+ if (offset == 0 && length == rope.Length)
+ {
+ // optimize replacing the whole document
+ rope.Clear();
+ rope.InsertText(0, newText);
+ lineManager.Rebuild();
+ }
+ else
+ {
+ rope.RemoveRange(offset, length);
+ lineManager.Remove(offset, length);
+#if DEBUG
+ lineTree.CheckProperties();
+#endif
+ rope.InsertText(offset, newText);
+ lineManager.Insert(offset, newText);
+#if DEBUG
+ lineTree.CheckProperties();
+#endif
+ }
+ }
+
+ // update text anchors
+ if (offsetChangeMap == null)
+ {
+ anchorTree.HandleTextChange(args.CreateSingleChangeMapEntry(), delayedEvents);
+ }
+ else
+ {
+ foreach (OffsetChangeMapEntry entry in offsetChangeMap)
+ {
+ anchorTree.HandleTextChange(entry, delayedEvents);
+ }
+ }
+
+ // raise delayed events after our data structures are consistent again
+ delayedEvents.RaiseEvents();
+
+ // fire DocumentChanged event
+ if (Changed != null)
+ Changed(this, args);
+ }
+ #endregion
+
+ #region GetLineBy...
+ /// <summary>
+ /// Gets a read-only list of lines.
+ /// </summary>
+ /// <remarks><inheritdoc cref="DocumentLine"/></remarks>
+ public IList<DocumentLine> Lines
+ {
+ get { return lineTree; }
+ }
+
+ /// <summary>
+ /// Gets a line by the line number: O(log n)
+ /// </summary>
+ public DocumentLine GetLineByNumber(int number)
+ {
+ VerifyAccess();
+ if (number < 1 || number > lineTree.LineCount)
+ throw new ArgumentOutOfRangeException("number", number, "Value must be between 1 and " + lineTree.LineCount);
+ return lineTree.GetByNumber(number);
+ }
+
+ /// <summary>
+ /// Gets a document lines by offset.
+ /// Runtime: O(log n)
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
+ public DocumentLine GetLineByOffset(int offset)
+ {
+ VerifyAccess();
+ if (offset < 0 || offset > rope.Length)
+ {
+ throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString());
+ }
+ return lineTree.GetByOffset(offset);
+ }
+ #endregion
+
+ /// <summary>
+ /// Gets the offset from a text location.
+ /// </summary>
+ /// <seealso cref="GetLocation"/>
+ public int GetOffset(TextLocation location)
+ {
+ return GetOffset(location.Line, location.Column);
+ }
+
+ /// <summary>
+ /// Gets the offset from a text location.
+ /// </summary>
+ /// <seealso cref="GetLocation"/>
+ public int GetOffset(int line, int column)
+ {
+ DocumentLine docLine = GetLineByNumber(line);
+ if (column <= 0)
+ return docLine.Offset;
+ if (column > docLine.Length)
+ return docLine.EndOffset;
+ return docLine.Offset + column - 1;
+ }
+
+ /// <summary>
+ /// Gets the location from an offset.
+ /// </summary>
+ /// <seealso cref="GetOffset(TextLocation)"/>
+ public TextLocation GetLocation(int offset)
+ {
+ DocumentLine line = GetLineByOffset(offset);
+ return new TextLocation(line.LineNumber, offset - line.Offset + 1);
+ }
+
+ readonly ObservableCollection<ILineTracker> lineTrackers = new ObservableCollection<ILineTracker>();
+
+ /// <summary>
+ /// Gets the list of <see cref="ILineTracker"/>s attached to this document.
+ /// You can add custom line trackers to this list.
+ /// </summary>
+ public IList<ILineTracker> LineTrackers
+ {
+ get
+ {
+ VerifyAccess();
+ return lineTrackers;
+ }
+ }
+
+ UndoStack undoStack;
+
+ /// <summary>
+ /// Gets the <see cref="UndoStack"/> of the document.
+ /// </summary>
+ /// <remarks>This property can also be used to set the undo stack, e.g. for sharing a common undo stack between multiple documents.</remarks>
+ public UndoStack UndoStack
+ {
+ get { return undoStack; }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException();
+ if (value != undoStack)
+ {
+ undoStack.ClearAll(); // first clear old undo stack, so that it can't be used to perform unexpected changes on this document
+ // ClearAll() will also throw an exception when it's not safe to replace the undo stack (e.g. update is currently in progress)
+ undoStack = value;
+ OnPropertyChanged("UndoStack");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="TextAnchor"/> at the specified offset.
+ /// </summary>
+ /// <inheritdoc cref="TextAnchor" select="remarks|example"/>
+ public TextAnchor CreateAnchor(int offset)
+ {
+ VerifyAccess();
+ if (offset < 0 || offset > rope.Length)
+ {
+ throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + rope.Length.ToString(CultureInfo.InvariantCulture));
+ }
+ return anchorTree.CreateAnchor(offset);
+ }
+
+ #region LineCount
+ /// <summary>
+ /// Gets the total number of lines in the document.
+ /// Runtime: O(1).
+ /// </summary>
+ public int LineCount
+ {
+ get
+ {
+ VerifyAccess();
+ return lineTree.LineCount;
+ }
+ }
+
+ /// <summary>
+ /// Is raised when the LineCount property changes.
+ /// </summary>
+ [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")]
+ public event EventHandler LineCountChanged;
+ #endregion
+
+ #region Debugging
+ [Conditional("DEBUG")]
+ internal void DebugVerifyAccess()
+ {
+ VerifyAccess();
+ }
+
+ /// <summary>
+ /// Gets the document lines tree in string form.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ internal string GetLineTreeAsString()
+ {
+#if DEBUG
+ return lineTree.GetTreeAsString();
+#else
return "Not available in release build.";
- #endif
- }
-
- /// <summary>
- /// Gets the text anchor tree in string form.
- /// </summary>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
- internal string GetTextAnchorTreeAsString()
- {
- #if DEBUG
- return anchorTree.GetTreeAsString();
- #else
+#endif
+ }
+
+ /// <summary>
+ /// Gets the text anchor tree in string form.
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ internal string GetTextAnchorTreeAsString()
+ {
+#if DEBUG
+ return anchorTree.GetTreeAsString();
+#else
return "Not available in release build.";
- #endif
- }
- #endregion
- }
+#endif
+ }
+ #endregion
+ }
}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs
index efa1b087a..d06862cef 100644
--- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ScriptEditor.cs
@@ -279,18 +279,18 @@ namespace Tango.Scripting.Editors
}
else if (e.Key == Key.Oem2)
{
- int offset = CaretOffset;
- var line = Document.GetLineByOffset(offset);
+ //int offset = CaretOffset;
+ //var line = Document.GetLineByOffset(offset);
- String text = GetCurrentLineText();
- if (text.TrimStart('\t', ' ').StartsWith("//"))
- {
- Document.BeginUpdate();
- Document.Replace(line, "/// <summary>\n/// \n/// </summary>");
- Document.EndUpdate();
- e.Handled = true;
- CaretOffset = Document.GetLineByNumber(line.LineNumber + 1).EndOffset;
- }
+ //String text = GetCurrentLineText();
+ //if (text.TrimStart('\t', ' ').StartsWith("//"))
+ //{
+ // Document.BeginUpdate();
+ // Document.Replace(line, "/// <summary>\n/// \n/// </summary>");
+ // Document.EndUpdate();
+ // e.Handled = true;
+ // CaretOffset = Document.GetLineByNumber(line.LineNumber + 1).EndOffset;
+ //}
}
else if (e.Key == Key.End || e.Key == Key.Home)
{
@@ -304,474 +304,481 @@ namespace Tango.Scripting.Editors
private void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
- List<Object> items = new List<object>();
-
- HidePopup();
-
- var lineText = GetCurrentLineText();
- var previousWords = GetPreviousWords();
- var previousWordsLast = previousWords.LastOrDefault();
- String currentWord = previousWordsLast != null ? previousWordsLast.Replace("\t", "") : String.Empty;
- String currentWordIncludingParenthesis = currentWord.Split('(').LastOrDefault();
-
- if (previousWords.Count > 0 && previousWords.First().Trim().StartsWith("//")) return;
-
- if (e.Text == " " && previousWords.Count > 2 && previousWords[previousWords.Count - 2] == "=")
+ try
{
- var expression = previousWords.First();
- var knownType = GetKnownTypeFromExpression(expression + ".");
+ List<Object> items = new List<object>();
- if (knownType != null && knownType.Type.IsEnum)
- {
- completionWindow.HideCompletion();
- IList<ICompletionData> data = new List<ICompletionData>();
+ HidePopup();
- foreach (var field in knownType.Fields)
- {
- data.Add(new FieldCompletionItem()
- {
- Class = knownType.FriendlyName,
- Name = knownType.FriendlyName + "." + field.Name,
- Type = field.ReturnTypeFriendlyName,
- Description = field.Summary,
- });
- }
+ var lineText = GetCurrentLineText();
+ var previousWords = GetPreviousWords();
+ var previousWordsLast = previousWords.LastOrDefault();
+ String currentWord = previousWordsLast != null ? previousWordsLast.Replace("\t", "") : String.Empty;
+ String currentWordIncludingParenthesis = currentWord.Split('(').LastOrDefault();
- ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord());
- }
-
- }
- else if (e.Text == " " && GetPreviousWord() == "new")
- {
- var s = _parser.GetExpressionFirst<FieldDeclarationSyntax>(GetCurrentLineText());
-
- if (s != null)
- {
- String type = s.Declaration.Type.ToString();
-
- IList<ICompletionData> data = new List<ICompletionData>();
-
- data.Add(new ClassCompletionItem()
- {
- Name = type,
- Description = "Auto generate assignment...",
- });
+ if (previousWords.Count > 0 && previousWords.First().Trim().StartsWith("//")) return;
- ShowCompletionWindow(data, type);
- }
- }
- else if (e.Text == ";" || e.Text == " ")
- {
- HideCompletionWindow();
- }
- else if (e.Text == ".")
- {
- var knownType = GetCurrentKnownType();
-
- if (knownType != null)
+ if (e.Text == " " && previousWords.Count > 2 && previousWords[previousWords.Count - 2] == "=")
{
- completionWindow.HideCompletion();
- IList<ICompletionData> data = new List<ICompletionData>();
+ var expression = previousWords.First();
+ var knownType = GetKnownTypeFromExpression(expression + ".");
- if (!knownType.Type.IsEnum)
+ if (knownType != null && knownType.Type.IsEnum)
{
- var typeMembers = knownType.Members.ToList();
+ completionWindow.HideCompletion();
+ IList<ICompletionData> data = new List<ICompletionData>();
- foreach (var methodGroup in typeMembers.OfType<KnownTypeMethod>().GroupBy(x => x.NameWithTypeArguments))
+ foreach (var field in knownType.Fields)
{
- var method = methodGroup.First();
-
- data.Add(new MethodCompletionItem()
+ data.Add(new FieldCompletionItem()
{
Class = knownType.FriendlyName,
- Name = method.NameWithTypeArguments,
- ReturnType = method.ReturnTypeFriendlyName,
- Description = method.Summary,
- Parameters = method.Parameters,
- Overloads = methodGroup.Count() - 1,
+ Name = knownType.FriendlyName + "." + field.Name,
+ Type = field.ReturnTypeFriendlyName,
+ Description = field.Summary,
});
}
- foreach (var methodGroup in typeMembers.Where(x => x.GetType() != typeof(KnownTypeMethod)).GroupBy(x => x.Name))
- {
- var member = methodGroup.First();
+ ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord());
+ }
- data.Add(new PropertyCompletionItem()
- {
- Class = knownType.FriendlyName,
- Name = member.Name,
- Type = member.ReturnTypeFriendlyName,
- Description = member.Summary,
- });
+ }
+ else if (e.Text == " " && GetPreviousWord() == "new")
+ {
+ var s = _parser.GetExpressionFirst<FieldDeclarationSyntax>(GetCurrentLineText());
- }
- }
- else
+ if (s != null)
{
- foreach (var field in knownType.Fields)
+ String type = s.Declaration.Type.ToString();
+
+ IList<ICompletionData> data = new List<ICompletionData>();
+
+ data.Add(new ClassCompletionItem()
{
- data.Add(new FieldCompletionItem()
- {
- Class = knownType.FriendlyName,
- Name = field.Name,
- Type = field.ReturnTypeFriendlyName,
- Description = field.Summary,
- });
+ Name = type,
+ Description = "Auto generate assignment...",
+ });
- }
+ ShowCompletionWindow(data, type);
}
-
- ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord());
}
- else
+ else if (e.Text == ";" || e.Text == " ")
+ {
+ HideCompletionWindow();
+ }
+ else if (e.Text == ".")
{
- var declaredType = GetCurrentDeclaredType();
+ var knownType = GetCurrentKnownType();
- if (declaredType != null)
+ if (knownType != null)
{
completionWindow.HideCompletion();
IList<ICompletionData> data = new List<ICompletionData>();
- var typeMembers = declaredType.Symbols.ToList();
-
- foreach (var methodGroup in typeMembers.GroupBy(x => x.Name))
+ if (!knownType.Type.IsEnum)
{
- var member = methodGroup.First();
+ var typeMembers = knownType.Members.ToList();
- if (member.Kind == SymbolKind.Method)
+ foreach (var methodGroup in typeMembers.OfType<KnownTypeMethod>().GroupBy(x => x.NameWithTypeArguments))
{
- var methodCompletion = new MethodCompletionItem()
- {
- Class = declaredType.Name,
- Name = member.Name,
- ReturnType = member.Type,
- Description = member.Summary,
- Overloads = methodGroup.Count() - 1,
- };
+ var method = methodGroup.First();
-
- for (int i = 0; i < member.Parameters.Count; i++)
+ data.Add(new MethodCompletionItem()
{
- var pair = member.Parameters[i];
-
- methodCompletion.Parameters.Add(new KnownTypeMethodParameter()
- {
- Type = pair.Key,
- Name = pair.Value,
- IsLast = (i == member.Parameters.Count - 1)
- });
- }
-
- data.Add(methodCompletion);
-
+ Class = knownType.FriendlyName,
+ Name = method.NameWithTypeArguments,
+ ReturnType = method.ReturnTypeFriendlyName,
+ Description = method.Summary,
+ Parameters = method.Parameters,
+ Overloads = methodGroup.Count() - 1,
+ });
}
- else if (member.Kind == SymbolKind.Property)
+
+ foreach (var methodGroup in typeMembers.Where(x => x.GetType() != typeof(KnownTypeMethod)).GroupBy(x => x.Name))
{
+ var member = methodGroup.First();
+
data.Add(new PropertyCompletionItem()
{
- Class = declaredType.Name,
+ Class = knownType.FriendlyName,
Name = member.Name,
- Type = member.Type,
+ Type = member.ReturnTypeFriendlyName,
Description = member.Summary,
});
+
}
- else if (member.Kind == SymbolKind.Field)
+ }
+ else
+ {
+ foreach (var field in knownType.Fields)
{
data.Add(new FieldCompletionItem()
{
- Class = declaredType.Name,
- Name = member.Name,
- Type = member.Type,
- Description = member.Summary,
+ Class = knownType.FriendlyName,
+ Name = field.Name,
+ Type = field.ReturnTypeFriendlyName,
+ Description = field.Summary,
});
+
}
}
- ShowCompletionWindow(data, GetCurrentWord());
+ ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord());
}
- }
- }
- else if (e.Text == "(" || e.Text == ",")
- {
- completionWindow.HideCompletion();
+ else
+ {
+ var declaredType = GetCurrentDeclaredType();
- try
+ if (declaredType != null)
+ {
+ completionWindow.HideCompletion();
+ IList<ICompletionData> data = new List<ICompletionData>();
+
+ var typeMembers = declaredType.Symbols.ToList();
+
+ foreach (var methodGroup in typeMembers.GroupBy(x => x.Name))
+ {
+ var member = methodGroup.First();
+
+ if (member.Kind == SymbolKind.Method)
+ {
+ var methodCompletion = new MethodCompletionItem()
+ {
+ Class = declaredType.Name,
+ Name = member.Name,
+ ReturnType = member.Type,
+ Description = member.Summary,
+ Overloads = methodGroup.Count() - 1,
+ };
+
+
+ for (int i = 0; i < member.Parameters.Count; i++)
+ {
+ var pair = member.Parameters[i];
+
+ methodCompletion.Parameters.Add(new KnownTypeMethodParameter()
+ {
+ Type = pair.Key,
+ Name = pair.Value,
+ IsLast = (i == member.Parameters.Count - 1)
+ });
+ }
+
+ data.Add(methodCompletion);
+
+ }
+ else if (member.Kind == SymbolKind.Property)
+ {
+ data.Add(new PropertyCompletionItem()
+ {
+ Class = declaredType.Name,
+ Name = member.Name,
+ Type = member.Type,
+ Description = member.Summary,
+ });
+ }
+ else if (member.Kind == SymbolKind.Field)
+ {
+ data.Add(new FieldCompletionItem()
+ {
+ Class = declaredType.Name,
+ Name = member.Name,
+ Type = member.Type,
+ Description = member.Summary,
+ });
+ }
+ }
+
+ ShowCompletionWindow(data, GetCurrentWord());
+ }
+ }
+ }
+ else if (e.Text == "(" || e.Text == ",")
{
- var session = GetConstructionSession();
+ completionWindow.HideCompletion();
- if (session != null)
+ try
{
- var content = CreateConstructionSessionPopupContent(session);
- if (content.Methods.Count > 0)
+ var session = GetConstructionSession();
+
+ if (session != null)
{
- ShowPopup(content);
- return;
+ var content = CreateConstructionSessionPopupContent(session);
+ if (content.Methods.Count > 0)
+ {
+ ShowPopup(content);
+ return;
+ }
}
- }
- var methodSession = GetMethodSession();
+ var methodSession = GetMethodSession();
- if (methodSession != null)
- {
- var content = CreateMethodSessionPopupContent(methodSession);
- if (content.Methods.Count > 0)
+ if (methodSession != null)
{
- ShowPopup(content);
- return;
+ var content = CreateMethodSessionPopupContent(methodSession);
+ if (content.Methods.Count > 0)
+ {
+ ShowPopup(content);
+ return;
+ }
}
- }
- var declaredMethodSession = GetDeclaredMethodSession();
+ var declaredMethodSession = GetDeclaredMethodSession();
- if (declaredMethodSession != null)
- {
- var content = CreateDeclaredMethodSessionPopupContent(declaredMethodSession);
- if (content.Methods.Count > 0)
+ if (declaredMethodSession != null)
{
- ShowPopup(content);
- return;
+ var content = CreateDeclaredMethodSessionPopupContent(declaredMethodSession);
+ if (content.Methods.Count > 0)
+ {
+ ShowPopup(content);
+ return;
+ }
}
}
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex);
+ }
}
- catch (Exception ex)
- {
- Debug.WriteLine(ex);
- }
- }
- else if (lineText.StartsWith("using"))
- {
- if (completionWindow.IsVisible)
+ else if (lineText.StartsWith("using") && e.Text != "\n")
{
- completionWindow.UpdatePositionFix();
- return;
- }
+ if (completionWindow.IsVisible)
+ {
+ completionWindow.UpdatePositionFix();
+ return;
+ }
- IList<ICompletionData> data = new List<ICompletionData>();
+ IList<ICompletionData> data = new List<ICompletionData>();
- foreach (var asm in ReferenceAssemblies)
- {
- foreach (var ns in asm.Assembly.GetTypes().Select(x => x.Namespace).Distinct().Where(x => x != null))
+ foreach (var asm in ReferenceAssemblies)
{
- data.Add(new NamespaceCompletionItem()
+ foreach (var ns in asm.Assembly.GetTypes().Select(x => x.Namespace).Distinct().Where(x => x != null))
{
- Name = ns,
- Assembly = asm.Assembly.GetName().Name,
- });
+ data.Add(new NamespaceCompletionItem()
+ {
+ Name = ns,
+ Assembly = asm.Assembly.GetName().Name,
+ });
+ }
}
- }
- data = data.DistinctBy(x => x.Text).ToList();
+ data = data.DistinctBy(x => x.Text).ToList();
- ShowCompletionWindow(data, GetCurrentWord());
- }
- else if (e.Text == "{")
- {
- int parentesisCount = lineText.TakeWhile(x => x != '{').Count(x => x == '\"');
-
- if (parentesisCount % 2 == 0)
- {
- Document.Insert(CaretOffset, "}");
- CaretOffset--;
+ ShowCompletionWindow(data, GetCurrentWord());
}
- }
- else if (e.Text == "}")
- {
- if (Document.GetText(CaretOffset - 2, 1) == "{" && Document.GetText(CaretOffset, 1) == "}")
- {
- Document.Replace(CaretOffset, 1, "");
- }
- }
- else if (e.Text == "\n")
- {
- if (Document.GetText(CaretOffset - 3, 1) == "{" && Document.GetText(CaretOffset, 1) == "}")
+ else if (e.Text == "{")
{
- CaretOffset--;
- Document.Insert(CaretOffset, "\n\t");
+ int parentesisCount = lineText.TakeWhile(x => x != '{').Count(x => x == '\"');
+
+ if (parentesisCount % 2 == 0)
+ {
+ Document.Insert(CaretOffset, "}");
+ CaretOffset--;
+ }
}
- }
- else if (!currentWordIncludingParenthesis.Contains(".") || currentWord[currentWord.Length - 2] == '<')
- {
- if (completionWindow.IsVisible)
+ else if (e.Text == "}")
{
- completionWindow.UpdatePositionFix();
- return;
+ if (Document.GetText(CaretOffset - 2, 1) == "{" && Document.GetText(CaretOffset, 1) == "}")
+ {
+ Document.Replace(CaretOffset, 1, "");
+ }
}
-
- var previous_word = GetPreviousWord();
- var word = GetCurrentWord();
-
- if (word.Contains("<"))
+ else if (e.Text == "\n")
{
- word = word.Last(x => x != '<').ToString();
+ if (Document.GetText(CaretOffset - 3, 1) == "{" && Document.GetText(CaretOffset, 1) == "}")
+ {
+ CaretOffset--;
+ Document.Insert(CaretOffset, "\n\t");
+ }
}
-
- if (previous_word != word)
+ else if (!currentWordIncludingParenthesis.Contains(".") || currentWord[currentWord.Length - 2] == '<')
{
- if (_knownTypes.Exists(x => x.Name == previous_word))
+ if (completionWindow.IsVisible)
{
+ completionWindow.UpdatePositionFix();
return;
}
- if (_blocking_type_words.Contains(previous_word))
+ var previous_word = GetPreviousWord();
+ var word = GetCurrentWord();
+
+ if (word.Contains("<"))
{
- return;
+ word = word.Last(x => x != '<').ToString();
}
- }
-
- if (!String.IsNullOrWhiteSpace(word))
- {
- IList<ICompletionData> data = new List<ICompletionData>();
- foreach (var type in _declaredTypes.Where(x => x.Name.StartsWith(word)))
+ if (previous_word != word)
{
- if (type.Kind == TypeKind.Struct)
+ if (_knownTypes.Exists(x => x.Name == previous_word))
{
- data.Add(new StructCompletionItem()
- {
- Name = type.Name,
- Description = type.Summary,
- Namespace = type.ContainingNamespace,
- Priority = 1,
- });
- }
- else if (type.Kind == TypeKind.Enum)
- {
- data.Add(new EnumCompletionItem()
- {
- Name = type.Name,
- Description = type.Summary,
- Namespace = type.ContainingNamespace,
- Priority = 1,
- });
- }
- else if (type.Kind == TypeKind.Interface)
- {
- data.Add(new InterfaceCompletionItem()
- {
- Name = type.Name,
- Description = type.Summary,
- Namespace = type.ContainingNamespace,
- Priority = 1,
- });
- }
- else if (type.Kind == TypeKind.Class)
- {
- data.Add(new ClassCompletionItem()
- {
- Name = type.Name,
- Description = type.Summary,
- Namespace = type.ContainingNamespace,
- Priority = 1,
- });
+ return;
}
- else
+
+ if (_blocking_type_words.Contains(previous_word))
{
- throw new NotImplementedException("Implement generic item here!");
+ return;
}
}
- foreach (var type in _knownTypes.ToList().Where(x => x.Name.StartsWith(word)))
+ if (!String.IsNullOrWhiteSpace(word))
{
- if (type.Type.IsEnum)
+ IList<ICompletionData> data = new List<ICompletionData>();
+
+ foreach (var type in _declaredTypes.Where(x => x.Name.StartsWith(word)))
{
- data.Add(new EnumCompletionItem()
+ if (type.Kind == TypeKind.Struct)
{
- Namespace = type.Type.Namespace,
- Description = type.Summary,
- Name = type.FriendlyName,
- Priority = 0,
- });
- }
- else if (type.Type.IsInterface)
- {
- data.Add(new InterfaceCompletionItem()
+ data.Add(new StructCompletionItem()
+ {
+ Name = type.Name,
+ Description = type.Summary,
+ Namespace = type.ContainingNamespace,
+ Priority = 1,
+ });
+ }
+ else if (type.Kind == TypeKind.Enum)
{
- Name = type.FriendlyName,
- Description = type.Summary,
- Namespace = type.Type.Namespace,
- Priority = 0,
- });
- }
- else if (type.Type.IsValueType)
- {
- data.Add(new StructCompletionItem()
+ data.Add(new EnumCompletionItem()
+ {
+ Name = type.Name,
+ Description = type.Summary,
+ Namespace = type.ContainingNamespace,
+ Priority = 1,
+ });
+ }
+ else if (type.Kind == TypeKind.Interface)
{
- Name = type.FriendlyName,
- Description = type.Summary,
- Namespace = type.Type.Namespace,
- Priority = 0,
- });
- }
- else if (type.Type.IsClass)
- {
- data.Add(new ClassCompletionItem()
+ data.Add(new InterfaceCompletionItem()
+ {
+ Name = type.Name,
+ Description = type.Summary,
+ Namespace = type.ContainingNamespace,
+ Priority = 1,
+ });
+ }
+ else if (type.Kind == TypeKind.Class)
{
- Name = type.FriendlyName,
- Description = type.Summary,
- Namespace = type.Type.Namespace,
- Priority = 0,
- });
- }
- else
- {
- throw new NotImplementedException("Implement generic item here.");
+ data.Add(new ClassCompletionItem()
+ {
+ Name = type.Name,
+ Description = type.Summary,
+ Namespace = type.ContainingNamespace,
+ Priority = 1,
+ });
+ }
+ else
+ {
+ throw new NotImplementedException("Implement generic item here!");
+ }
}
- }
- foreach (var symbol in _parser.GetContextSymbols(Document.Text, CaretOffset).Where(x => x.Name.StartsWith(GetCurrentWord())))
- {
- if (symbol.Kind == SymbolKind.Property)
+ foreach (var type in _knownTypes.ToList().Where(x => x.Name.StartsWith(word)))
{
- data.Add(new PropertyCompletionItem()
+ if (type.Type.IsEnum)
{
- Class = symbol.Class,
- Description = symbol.Summary,
- Name = symbol.Name,
- Type = symbol.Type,
- Priority = 2,
- });
- }
- else if (symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter)
- {
- data.Add(new FieldCompletionItem()
+ data.Add(new EnumCompletionItem()
+ {
+ Namespace = type.Type.Namespace,
+ Description = type.Summary,
+ Name = type.FriendlyName,
+ Priority = 0,
+ });
+ }
+ else if (type.Type.IsInterface)
{
- Class = symbol.Class,
- Description = symbol.Summary,
- Name = symbol.Name,
- Type = symbol.Type,
- Priority = 2,
- });
+ data.Add(new InterfaceCompletionItem()
+ {
+ Name = type.FriendlyName,
+ Description = type.Summary,
+ Namespace = type.Type.Namespace,
+ Priority = 0,
+ });
+ }
+ else if (type.Type.IsValueType)
+ {
+ data.Add(new StructCompletionItem()
+ {
+ Name = type.FriendlyName,
+ Description = type.Summary,
+ Namespace = type.Type.Namespace,
+ Priority = 0,
+ });
+ }
+ else if (type.Type.IsClass)
+ {
+ data.Add(new ClassCompletionItem()
+ {
+ Name = type.FriendlyName,
+ Description = type.Summary,
+ Namespace = type.Type.Namespace,
+ Priority = 0,
+ });
+ }
+ else
+ {
+ throw new NotImplementedException("Implement generic item here.");
+ }
}
- else if (symbol.Kind == SymbolKind.Method)
+
+ foreach (var symbol in _parser.GetContextSymbols(Document.Text, CaretOffset).Where(x => x.Name.StartsWith(GetCurrentWord())))
{
- var methodCompletion = new MethodCompletionItem()
+ if (symbol.Kind == SymbolKind.Property)
{
- Class = symbol.Class,
- Description = symbol.Summary,
- Name = symbol.Name,
- ReturnType = symbol.Type,
- Priority = 2,
- };
-
- for (int i = 0; i < symbol.Parameters.Count; i++)
+ data.Add(new PropertyCompletionItem()
+ {
+ Class = symbol.Class,
+ Description = symbol.Summary,
+ Name = symbol.Name,
+ Type = symbol.Type,
+ Priority = 2,
+ });
+ }
+ else if (symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter)
{
- var pair = symbol.Parameters[i];
-
- methodCompletion.Parameters.Add(new KnownTypeMethodParameter()
+ data.Add(new FieldCompletionItem()
{
- Type = pair.Key,
- Name = pair.Value,
- IsLast = (i == symbol.Parameters.Count - 1)
+ Class = symbol.Class,
+ Description = symbol.Summary,
+ Name = symbol.Name,
+ Type = symbol.Type,
+ Priority = 2,
});
}
+ else if (symbol.Kind == SymbolKind.Method)
+ {
+ var methodCompletion = new MethodCompletionItem()
+ {
+ Class = symbol.Class,
+ Description = symbol.Summary,
+ Name = symbol.Name,
+ ReturnType = symbol.Type,
+ Priority = 2,
+ };
+
+ for (int i = 0; i < symbol.Parameters.Count; i++)
+ {
+ var pair = symbol.Parameters[i];
+
+ methodCompletion.Parameters.Add(new KnownTypeMethodParameter()
+ {
+ Type = pair.Key,
+ Name = pair.Value,
+ IsLast = (i == symbol.Parameters.Count - 1)
+ });
+ }
- data.Add(methodCompletion);
+ data.Add(methodCompletion);
+ }
}
- }
- ShowCompletionWindow(data, word);
+ ShowCompletionWindow(data, word);
+ }
}
}
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex.ToString());
+ }
}
#endregion