From d33c19b3ac6803de4b5c8d475832efef131c1a45 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Wed, 30 Dec 2020 15:11:34 +0000 Subject: Revert "Hope it is fine" --- .../BreakPointSymbolPressedEventArgs.cs | 16 + .../Tango.Scripting.Editors/CachedAssembly.cs | 21 + .../Tango.Scripting.Editors/CachedUsing.cs | 20 + .../Document/TextDocument.cs | 1695 +++++++------- .../Tango.Scripting.Editors/Document/UndoStack.cs | 2 +- .../Editing/BreakPointMargin.cs | 285 +++ .../Tango.Scripting.Editors/Errors/ITextMarker.cs | 169 ++ .../Errors/TextMarkerService.cs | 365 +++ .../Tango.Scripting.Editors/ExtensionMethods.cs | 2 +- .../Highlighting/OffsetColorizer.cs | 2 +- .../Highlighting/Resources/CSharp-Mode.xshd | 29 +- .../Highlighting/Resources/MarkDown-Mode.xshd | 7 - .../Highlighting/Resources/XML-Mode.xshd | 14 +- .../Images/break_point_arrow.png | Bin 0 -> 453 bytes .../Tango.Scripting.Editors/Images/event.png | Bin 0 -> 210 bytes .../Tango.Scripting.Editors/Images/snippet.png | Bin 0 -> 147 bytes .../Intellisense/CompletionItem.cs | 7 +- .../Intellisense/EventCompletionItem.cs | 22 + .../Intellisense/HideIntellisenseAttribute.cs | 12 + .../Intellisense/KnownType.cs | 75 +- .../Intellisense/KnownTypeEvent.cs | 21 + .../Intellisense/KnownTypeMethod.cs | 2 + .../Intellisense/SnippetCompletionItem.cs | 26 + .../Tango.Scripting.Editors/Intellisense/Utils.cs | 14 +- .../Tango.Scripting.Editors/Rendering/TextView.cs | 2 +- .../Tango.Scripting.Editors/ScriptEditor.cs | 2099 ++++++++++++++---- .../Tango.Scripting.Editors.csproj | 33 +- .../Tango.Scripting.Editors_di35u2uj_wpftmp.csproj | 628 ++++++ .../Tango.Scripting.Editors/TextEditor.cs | 2343 ++++++++++---------- .../Tango.Scripting.Editors/Themes/Generic.xaml | 107 +- .../Tango.Scripting.Editors/XamlEditor.cs | 47 + .../Scripting/Tango.Scripting.Editors/app.config | 8 + .../Tango.Scripting.Editors/packages.config | 1 + 33 files changed, 5551 insertions(+), 2523 deletions(-) create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedAssembly.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedUsing.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/break_point_arrow.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/event.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Images/snippet.png create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/EventCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/HideIntellisenseAttribute.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/KnownTypeEvent.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Intellisense/SnippetCompletionItem.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Tango.Scripting.Editors_di35u2uj_wpftmp.csproj create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting.Editors/XamlEditor.cs (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors') diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs new file mode 100644 index 000000000..1728bb565 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/BreakPointSymbolPressedEventArgs.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Tango.Scripting.Core; + +namespace Tango.Scripting.Editors +{ + public class BreakPointSymbolPressedEventArgs : EventArgs + { + public ScriptBreakPointSymbol BreakPointSymbol { get; set; } + public Point Position { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedAssembly.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedAssembly.cs new file mode 100644 index 000000000..b0178e63e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedAssembly.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Scripting.Editors.Intellisense; + +namespace Tango.Scripting.Editors +{ + public class CachedAssembly + { + public String Name { get; set; } + public List KnownTypes { get; set; } + + public CachedAssembly() + { + KnownTypes = new List(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedUsing.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedUsing.cs new file mode 100644 index 000000000..4a663bee9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CachedUsing.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.Scripting.Editors.Intellisense; + +namespace Tango.Scripting.Editors +{ + public class CachedUsing + { + public String Namespace { get; set; } + public List KnownTypes { get; set; } + + public CachedUsing() + { + KnownTypes = new List(); + } + } +} 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 { - /// - /// This class is the main class of the text model. Basically, it is a with events. - /// - /// - /// Thread safety: - /// - /// However, there is a single method that is thread-safe: (and its overloads). - /// - public sealed class TextDocument : ITextSource, INotifyPropertyChanged - { - #region Thread ownership - readonly object lockObject = new object(); - Thread owner = Thread.CurrentThread; - - /// - /// Verifies that the current thread is the documents owner thread. - /// Throws an if the wrong thread accesses the TextDocument. - /// - /// - /// The TextDocument class is not thread-safe. A document instance expects to have a single owner thread - /// and will throw an when accessed from another thread. - /// It is possible to change the owner thread using the method. - /// - public void VerifyAccess() - { - if (Thread.CurrentThread != owner) - throw new InvalidOperationException("TextDocument can be accessed only from the thread that owns it."); - } - - /// - /// 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. - /// - /// - /// - /// - /// 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 . - /// - /// - 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 rope; - readonly DocumentLineTree lineTree; - readonly LineManager lineManager; - readonly TextAnchorTree anchorTree; - ChangeTrackingCheckpoint currentCheckpoint; - - /// - /// Create an empty text document. - /// - public TextDocument() - : this(string.Empty) - { - } - - /// - /// Create a new text document with the specified initial text. - /// - public TextDocument(IEnumerable initialText) - { - if (initialText == null) - throw new ArgumentNullException("initialText"); - rope = new Rope(initialText); - lineTree = new DocumentLineTree(this); - lineManager = new LineManager(lineTree, this); - lineTrackers.CollectionChanged += delegate { - lineManager.UpdateListOfLineTrackers(); - }; - - anchorTree = new TextAnchorTree(this); - undoStack = new UndoStack(); - FireChangeEvents(); - } - - /// - /// Create a new text document with the specified initial text. - /// - public TextDocument(ITextSource initialText) - : this(GetTextFromTextSource(initialText)) - { - } - - // gets the text from a text source, directly retrieving the underlying rope where possible - static IEnumerable 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)); - } - } - - /// - public string GetText(int offset, int length) - { - VerifyAccess(); - return rope.ToString(offset, length); - } - - /// - /// Retrieves the text for a portion of the document. - /// - 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); - } - - /// - public char GetCharAt(int offset) - { - DebugVerifyAccess(); // frequently called, so must be fast in release builds - return rope[offset]; - } - - WeakReference cachedText; - - /// - /// Gets/Sets the text of the whole document. - /// - 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); - } - } - - /// - /// - public event EventHandler TextChanged; - - /// - public int TextLength { - get { - VerifyAccess(); - return rope.Length; - } - } - - /// - /// Is raised when the TextLength property changes. - /// - /// - [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")] - public event EventHandler TextLengthChanged; - - /// - /// Is raised when one of the properties , , , - /// changes. - /// - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Is raised before the document changes. - /// - /// - /// Here is the order in which events are raised during a document update: - /// - /// BeginUpdate() - /// - /// Start of change group (on undo stack) - /// event is raised - /// - /// Insert() / Remove() / Replace() - /// - /// event is raised - /// The document is changed - /// TextAnchor.Deleted event is raised if anchors were - /// in the deleted text portion - /// event is raised - /// - /// EndUpdate() - /// - /// event is raised - /// event is raised (for the Text, TextLength, LineCount properties, in that order) - /// End of change group (on undo stack) - /// event is raised - /// - /// - /// - /// If the insert/remove/replace methods are called without a call to BeginUpdate(), - /// they will call BeginUpdate() and EndUpdate() to ensure no change happens outside of UpdateStarted/UpdateFinished. - /// - /// There can be multiple document changes between the BeginUpdate() and EndUpdate() calls. - /// In this case, the events associated with EndUpdate will be raised only once after the whole document update is done. - /// - /// The listens to the UpdateStarted and UpdateFinished events to group all changes into a single undo step. - /// - /// - public event EventHandler Changing; - - /// - /// Is raised after the document has changed. - /// - /// - public event EventHandler Changed; - - /// - /// Creates a snapshot of the current text. - /// - /// - /// 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. - /// - /// This special thread-safety guarantee is valid only for TextDocument.CreateSnapshot(), not necessarily for other - /// classes implementing ITextSource.CreateSnapshot(). - /// - /// - /// - public ITextSource CreateSnapshot() - { - lock (lockObject) { - return new RopeTextSource(rope.Clone()); - } - } - - /// - /// Creates a snapshot of the current text. - /// Additionally, creates a checkpoint that allows tracking document changes. - /// - /// - [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; - } - } - - /// - /// Creates a snapshot of a part of the current text. - /// - /// - public ITextSource CreateSnapshot(int offset, int length) - { - lock (lockObject) { - return new RopeTextSource(rope.GetRange(offset, length)); - } - } - - /// - public System.IO.TextReader CreateReader() - { - lock (lockObject) { - return new RopeTextReader(rope); - } - } - #endregion - - #region BeginUpdate / EndUpdate - int beginUpdateCount; - - /// - /// Gets if an update is running. - /// - /// - public bool IsInUpdate { - get { - VerifyAccess(); - return beginUpdateCount > 0; - } - } - - /// - /// Immediately calls , - /// and returns an IDisposable that calls . - /// - /// - public IDisposable RunUpdate() - { - BeginUpdate(); - return new CallbackOnDispose(EndUpdate); - } - - /// - /// Begins a group of document changes. - /// Some events are suspended until EndUpdate is called, and the will - /// group all changes into a single action. - /// Calling BeginUpdate several times increments a counter, only after the appropriate number - /// of EndUpdate calls the events resume their work. - /// - /// - 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); - } - } - - /// - /// Ends a group of document changes. - /// - /// - 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; - } - } - - /// - /// Occurs when a document change starts. - /// - /// - public event EventHandler UpdateStarted; - - /// - /// Occurs when a document change is finished. - /// - /// - public event EventHandler UpdateFinished; - #endregion - - #region Fire events after update - int oldTextLength; - int oldLineCount; - bool fireTextChanged; - - /// - /// Fires TextChanged, TextLengthChanged, LineCountChanged if required. - /// - 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 - /// - /// Inserts text. - /// - public void Insert(int offset, string text) - { - Replace(offset, 0, text); - } - - /// - /// Removes text. - /// - public void Remove(ISegment segment) - { - Replace(segment, string.Empty); - } - - /// - /// Removes text. - /// - public void Remove(int offset, int length) - { - Replace(offset, length, string.Empty); - } - - internal bool inDocumentChanging; - - /// - /// Replaces text. - /// - public void Replace(ISegment segment, string text) - { - if (segment == null) - throw new ArgumentNullException("segment"); - Replace(segment.Offset, segment.Length, text, null); - } - - /// - /// Replaces text. - /// - public void Replace(int offset, int length, string text) - { - Replace(offset, length, text, null); - } - - /// - /// Replaces text. - /// - /// The starting offset of the text to be replaced. - /// The length of the text to be replaced. - /// The new text. - /// 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. - 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"); - } - } - - /// - /// Replaces text. - /// - /// The starting offset of the text to be replaced. - /// The length of the text to be replaced. - /// The new text. - /// 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 . - /// Passing an OffsetChangeMap to the Replace method will automatically freeze it to ensure the thread safety of the resulting - /// DocumentChangeEventArgs instance. - /// - 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... - /// - /// Gets a read-only list of lines. - /// - /// - public IList Lines { - get { return lineTree; } - } - - /// - /// Gets a line by the line number: O(log n) - /// - 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); - } - - /// - /// Gets a document lines by offset. - /// Runtime: O(log n) - /// - [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 - - /// - /// Gets the offset from a text location. - /// - /// - public int GetOffset(TextLocation location) - { - return GetOffset(location.Line, location.Column); - } - - /// - /// Gets the offset from a text location. - /// - /// - 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; - } - - /// - /// Gets the location from an offset. - /// - /// - public TextLocation GetLocation(int offset) - { - DocumentLine line = GetLineByOffset(offset); - return new TextLocation(line.LineNumber, offset - line.Offset + 1); - } - - readonly ObservableCollection lineTrackers = new ObservableCollection(); - - /// - /// Gets the list of s attached to this document. - /// You can add custom line trackers to this list. - /// - public IList LineTrackers { - get { - VerifyAccess(); - return lineTrackers; - } - } - - UndoStack undoStack; - - /// - /// Gets the of the document. - /// - /// This property can also be used to set the undo stack, e.g. for sharing a common undo stack between multiple documents. - 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"); - } - } - } - - /// - /// Creates a new at the specified offset. - /// - /// - 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 - /// - /// Gets the total number of lines in the document. - /// Runtime: O(1). - /// - public int LineCount { - get { - VerifyAccess(); - return lineTree.LineCount; - } - } - - /// - /// Is raised when the LineCount property changes. - /// - [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(); - } - - /// - /// Gets the document lines tree in string form. - /// - [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 + /// + /// This class is the main class of the text model. Basically, it is a with events. + /// + /// + /// Thread safety: + /// + /// However, there is a single method that is thread-safe: (and its overloads). + /// + public sealed class TextDocument : ITextSource, INotifyPropertyChanged + { + #region Thread ownership + readonly object lockObject = new object(); + Thread owner = Thread.CurrentThread; + + /// + /// Verifies that the current thread is the documents owner thread. + /// Throws an if the wrong thread accesses the TextDocument. + /// + /// + /// The TextDocument class is not thread-safe. A document instance expects to have a single owner thread + /// and will throw an when accessed from another thread. + /// It is possible to change the owner thread using the method. + /// + public void VerifyAccess() + { + if (Thread.CurrentThread != owner) + throw new InvalidOperationException("TextDocument can be accessed only from the thread that owns it."); + } + + /// + /// 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. + /// + /// + /// + /// + /// 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 . + /// + /// + 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 rope; + readonly DocumentLineTree lineTree; + readonly LineManager lineManager; + readonly TextAnchorTree anchorTree; + ChangeTrackingCheckpoint currentCheckpoint; + + /// + /// Create an empty text document. + /// + public TextDocument() + : this(string.Empty) + { + } + + /// + /// Create a new text document with the specified initial text. + /// + public TextDocument(IEnumerable initialText) + { + if (initialText == null) + throw new ArgumentNullException("initialText"); + rope = new Rope(initialText); + lineTree = new DocumentLineTree(this); + lineManager = new LineManager(lineTree, this); + lineTrackers.CollectionChanged += delegate + { + lineManager.UpdateListOfLineTrackers(); + }; + + anchorTree = new TextAnchorTree(this); + undoStack = new UndoStack(); + FireChangeEvents(); + } + + /// + /// Create a new text document with the specified initial text. + /// + public TextDocument(ITextSource initialText) + : this(GetTextFromTextSource(initialText)) + { + } + + // gets the text from a text source, directly retrieving the underlying rope where possible + static IEnumerable 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)); + } + } + + /// + public string GetText(int offset, int length) + { + VerifyAccess(); + return rope.ToString(Math.Max(offset, 0), length); + } + + /// + /// Retrieves the text for a portion of the document. + /// + 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); + } + + /// + public char GetCharAt(int offset) + { + DebugVerifyAccess(); // frequently called, so must be fast in release builds + return rope[offset]; + } + + WeakReference cachedText; + + /// + /// Gets/Sets the text of the whole document. + /// + 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); + } + } + + /// + /// + public event EventHandler TextChanged; + + /// + public int TextLength + { + get + { + VerifyAccess(); + return rope.Length; + } + } + + /// + /// Is raised when the TextLength property changes. + /// + /// + [Obsolete("This event will be removed in a future version; use the PropertyChanged event instead")] + public event EventHandler TextLengthChanged; + + /// + /// Is raised when one of the properties , , , + /// changes. + /// + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Is raised before the document changes. + /// + /// + /// Here is the order in which events are raised during a document update: + /// + /// BeginUpdate() + /// + /// Start of change group (on undo stack) + /// event is raised + /// + /// Insert() / Remove() / Replace() + /// + /// event is raised + /// The document is changed + /// TextAnchor.Deleted event is raised if anchors were + /// in the deleted text portion + /// event is raised + /// + /// EndUpdate() + /// + /// event is raised + /// event is raised (for the Text, TextLength, LineCount properties, in that order) + /// End of change group (on undo stack) + /// event is raised + /// + /// + /// + /// If the insert/remove/replace methods are called without a call to BeginUpdate(), + /// they will call BeginUpdate() and EndUpdate() to ensure no change happens outside of UpdateStarted/UpdateFinished. + /// + /// There can be multiple document changes between the BeginUpdate() and EndUpdate() calls. + /// In this case, the events associated with EndUpdate will be raised only once after the whole document update is done. + /// + /// The listens to the UpdateStarted and UpdateFinished events to group all changes into a single undo step. + /// + /// + public event EventHandler Changing; + + /// + /// Is raised after the document has changed. + /// + /// + public event EventHandler Changed; + + /// + /// Creates a snapshot of the current text. + /// + /// + /// 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. + /// + /// This special thread-safety guarantee is valid only for TextDocument.CreateSnapshot(), not necessarily for other + /// classes implementing ITextSource.CreateSnapshot(). + /// + /// + /// + public ITextSource CreateSnapshot() + { + lock (lockObject) + { + return new RopeTextSource(rope.Clone()); + } + } + + /// + /// Creates a snapshot of the current text. + /// Additionally, creates a checkpoint that allows tracking document changes. + /// + /// + [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; + } + } + + /// + /// Creates a snapshot of a part of the current text. + /// + /// + public ITextSource CreateSnapshot(int offset, int length) + { + lock (lockObject) + { + return new RopeTextSource(rope.GetRange(offset, length)); + } + } + + /// + public System.IO.TextReader CreateReader() + { + lock (lockObject) + { + return new RopeTextReader(rope); + } + } + #endregion + + #region BeginUpdate / EndUpdate + int beginUpdateCount; + + /// + /// Gets if an update is running. + /// + /// + public bool IsInUpdate + { + get + { + VerifyAccess(); + return beginUpdateCount > 0; + } + } + + /// + /// Immediately calls , + /// and returns an IDisposable that calls . + /// + /// + public IDisposable RunUpdate() + { + BeginUpdate(); + return new CallbackOnDispose(EndUpdate); + } + + /// + /// Begins a group of document changes. + /// Some events are suspended until EndUpdate is called, and the will + /// group all changes into a single action. + /// Calling BeginUpdate several times increments a counter, only after the appropriate number + /// of EndUpdate calls the events resume their work. + /// + /// + 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); + } + } + + /// + /// Ends a group of document changes. + /// + /// + 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; + } + } + + /// + /// Occurs when a document change starts. + /// + /// + public event EventHandler UpdateStarted; + + /// + /// Occurs when a document change is finished. + /// + /// + public event EventHandler UpdateFinished; + #endregion + + #region Fire events after update + int oldTextLength; + int oldLineCount; + bool fireTextChanged; + + /// + /// Fires TextChanged, TextLengthChanged, LineCountChanged if required. + /// + 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 + /// + /// Inserts text. + /// + public void Insert(int offset, string text) + { + Replace(offset, 0, text); + } + + /// + /// Removes text. + /// + public void Remove(ISegment segment) + { + Replace(segment, string.Empty); + } + + /// + /// Removes text. + /// + public void Remove(int offset, int length) + { + Replace(offset, length, string.Empty); + } + + internal bool inDocumentChanging; + + /// + /// Replaces text. + /// + public void Replace(ISegment segment, string text) + { + if (segment == null) + throw new ArgumentNullException("segment"); + Replace(segment.Offset, segment.Length, text, null); + } + + /// + /// Replaces text. + /// + public void Replace(int offset, int length, string text) + { + Replace(offset, length, text, null); + } + + /// + /// Replaces text. + /// + /// The starting offset of the text to be replaced. + /// The length of the text to be replaced. + /// The new text. + /// 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. + 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"); + } + } + + /// + /// Replaces text. + /// + /// The starting offset of the text to be replaced. + /// The length of the text to be replaced. + /// The new text. + /// 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 . + /// Passing an OffsetChangeMap to the Replace method will automatically freeze it to ensure the thread safety of the resulting + /// DocumentChangeEventArgs instance. + /// + 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... + /// + /// Gets a read-only list of lines. + /// + /// + public IList Lines + { + get { return lineTree; } + } + + /// + /// Gets a line by the line number: O(log n) + /// + 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); + } + + /// + /// Gets a document lines by offset. + /// Runtime: O(log n) + /// + [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 + + /// + /// Gets the offset from a text location. + /// + /// + public int GetOffset(TextLocation location) + { + return GetOffset(location.Line, location.Column); + } + + /// + /// Gets the offset from a text location. + /// + /// + 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; + } + + /// + /// Gets the location from an offset. + /// + /// + public TextLocation GetLocation(int offset) + { + DocumentLine line = GetLineByOffset(offset); + return new TextLocation(line.LineNumber, offset - line.Offset + 1); + } + + readonly ObservableCollection lineTrackers = new ObservableCollection(); + + /// + /// Gets the list of s attached to this document. + /// You can add custom line trackers to this list. + /// + public IList LineTrackers + { + get + { + VerifyAccess(); + return lineTrackers; + } + } + + UndoStack undoStack; + + /// + /// Gets the of the document. + /// + /// This property can also be used to set the undo stack, e.g. for sharing a common undo stack between multiple documents. + 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"); + } + } + } + + /// + /// Creates a new at the specified offset. + /// + /// + 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 + /// + /// Gets the total number of lines in the document. + /// Runtime: O(1). + /// + public int LineCount + { + get + { + VerifyAccess(); + return lineTree.LineCount; + } + } + + /// + /// Is raised when the LineCount property changes. + /// + [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(); + } + + /// + /// Gets the document lines tree in string form. + /// + [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 - } - - /// - /// Gets the text anchor tree in string form. - /// - [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 + } + + /// + /// Gets the text anchor tree in string form. + /// + [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/Document/UndoStack.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs index f0a759b23..86e1fa33e 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Document/UndoStack.cs @@ -414,7 +414,7 @@ namespace Tango.Scripting.Editors.Document /// public void ClearAll() { - ThrowIfUndoGroupOpen(); + //ThrowIfUndoGroupOpen(); actionCountInUndoGroup = 0; optionalActionCount = 0; if (undostack.Count != 0) { diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs new file mode 100644 index 000000000..e566e6aa9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/BreakPointMargin.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Media.TextFormatting; +using Tango.Scripting.Core; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + public class BreakPointMargin : AbstractMargin, IWeakEventListener + { + private TextArea textArea; + private int maxLineNumberLength = 1; + private BitmapSource _arrowBitmap; + private ScriptEditor _editor; + + public ObservableCollection BreakPoints { get; set; } + + public Brush Background + { + get { return (Brush)GetValue(BackgroundProperty); } + set { SetValue(BackgroundProperty, value); } + } + public static readonly DependencyProperty BackgroundProperty = + DependencyProperty.Register("Background", typeof(Brush), typeof(BreakPointMargin), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(50, 50, 50)))); + + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + public static readonly DependencyProperty ForegroundProperty = + DependencyProperty.Register("Foreground", typeof(Brush), typeof(BreakPointMargin), new PropertyMetadata(Brushes.Red)); + + static BreakPointMargin() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BreakPointMargin), + new FrameworkPropertyMetadata(typeof(BreakPointMargin))); + } + + public BreakPointMargin(ScriptEditor editor) + { + _editor = editor; + BreakPoints = new ObservableCollection(); + BreakPoints.CollectionChanged += BreakPoints_CollectionChanged; + RenderOptions.SetEdgeMode(this, EdgeMode.Unspecified); + + _arrowBitmap = new BitmapImage(new Uri($"pack://application:,,,/Tango.Scripting.Editors;component/Images/break_point_arrow.png", UriKind.Absolute)); + } + + private void BreakPoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + InvalidateVisual(); + } + + protected override Size MeasureOverride(Size availableSize) + { + return new Size(20, 0); + } + + protected override void OnRender(DrawingContext drawingContext) + { + TextView textView = this.TextView; + Size renderSize = this.RenderSize; + if (textView != null && textView.VisualLinesValid) + { + drawingContext.DrawRectangle(Background, new Pen(Background, 1), new Rect(0, 0, ActualWidth, ActualHeight)); + + var foreground = Foreground; + foreach (VisualLine line in textView.VisualLines) + { + int lineNumber = line.FirstDocumentLine.LineNumber; + + BreakPoint b = BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber); + + if (b != null) + { + double y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop); + drawingContext.DrawEllipse(Foreground, new Pen(Brushes.Gainsboro, 1), new Point(10, y - textView.VerticalOffset + 8), 6, 6); + + if (b.IsActive) + { + drawingContext.DrawImage(_arrowBitmap, new Rect(6, y - textView.VerticalOffset + 2.5, 8.5, 10)); + } + } + } + } + } + + protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) + { + if (oldTextView != null) + { + oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged; + } + base.OnTextViewChanged(oldTextView, newTextView); + if (newTextView != null) + { + newTextView.VisualLinesChanged += TextViewVisualLinesChanged; + + // find the text area belonging to the new text view + textArea = newTextView.Services.GetService(typeof(TextArea)) as TextArea; + } + else + { + textArea = null; + } + InvalidateVisual(); + } + + protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument) + { + if (oldDocument != null) + { + PropertyChangedEventManager.RemoveListener(oldDocument, this, "LineCount"); + } + base.OnDocumentChanged(oldDocument, newDocument); + if (newDocument != null) + { + PropertyChangedEventManager.AddListener(newDocument, this, "LineCount"); + } + OnDocumentLineCountChanged(); + } + + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (managerType == typeof(PropertyChangedEventManager)) + { + OnDocumentLineCountChanged(); + return true; + } + return false; + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + return ReceiveWeakEvent(managerType, sender, e); + } + + private void OnDocumentLineCountChanged() + { + int documentLineCount = Document != null ? Document.LineCount : 1; + int newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length; + + foreach (var breakPoint in BreakPoints.ToList()) + { + if (breakPoint.LineNumber > documentLineCount) + { + BreakPoints.Remove(breakPoint); + } + else + { + try + { + var line = Document.GetLineByNumber(breakPoint.LineNumber); + if (line != null) + { + String lineText = Document.GetText(line.Offset, line.Length); + if (!IsBreakPointValid(lineText)) + { + BreakPoints.Remove(breakPoint); + } + } + } + catch { } + } + } + + // The margin looks too small when there is only one digit, so always reserve space for + // at least two digits + if (newLength < 2) + newLength = 2; + + if (newLength != maxLineNumberLength) + { + maxLineNumberLength = newLength; + InvalidateMeasure(); + } + } + + private void TextViewVisualLinesChanged(object sender, EventArgs e) + { + InvalidateVisual(); + } + + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) + { + // accept clicks even when clicking on the background + return new PointHitTestResult(this, hitTestParameters.HitPoint); + } + + private VisualLine GetLineNumberByMousePosition(MouseEventArgs e) + { + Point pos = e.GetPosition(TextView); + pos.X = 0; + pos.Y += TextView.VerticalOffset; + VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y); + return vl; + } + + private bool IsBreakPointValid(String lineText) + { + if (lineText.EndsWith(";") && !lineText.StartsWith("using")) + { + return true; + } + + return false; + } + + protected override void OnPreviewMouseMove(MouseEventArgs e) + { + base.OnPreviewMouseMove(e); + + if (_editor.DisableBreakPoints) + { + Cursor = Cursors.No; + } + else + { + Cursor = Cursors.Arrow; + } + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + + if (_editor.DisableBreakPoints) + { + return; + } + + try + { + if (!e.Handled && TextView != null && textArea != null) + { + e.Handled = true; + textArea.Focus(); + + var visualLine = GetLineNumberByMousePosition(e); + + int? lineNumber = visualLine != null ? (int?)visualLine.FirstDocumentLine.LineNumber : null; + + if (lineNumber != null) + { + var breakPoint = BreakPoints.FirstOrDefault(x => x.LineNumber == lineNumber.Value); + if (breakPoint != null) + { + BreakPoints.Remove(breakPoint); + } + else + { + var lineText = Document.GetText(visualLine.FirstDocumentLine.Offset, visualLine.FirstDocumentLine.Length).Trim(); + + if (IsBreakPointValid(lineText)) + { + BreakPoint newBreakPoint = new BreakPoint(); + newBreakPoint.LineNumber = lineNumber.Value; + BreakPoints.Add(newBreakPoint); + } + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs new file mode 100644 index 000000000..dcbf8388a --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/ITextMarker.cs @@ -0,0 +1,169 @@ +// 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.Windows; +using System.Windows.Media; + +namespace Tango.Scripting.Editors +{ + /// + /// Represents a text marker. + /// + public interface ITextMarker + { + /// + /// Gets the start offset of the marked text region. + /// + int StartOffset { get; } + + /// + /// Gets the end offset of the marked text region. + /// + int EndOffset { get; } + + /// + /// Gets the length of the marked region. + /// + int Length { get; } + + /// + /// Deletes the text marker. + /// + void Delete(); + + /// + /// Gets whether the text marker was deleted. + /// + bool IsDeleted { get; } + + /// + /// Event that occurs when the text marker is deleted. + /// + event EventHandler Deleted; + + /// + /// Gets/Sets the background color. + /// + Color? BackgroundColor { get; set; } + + /// + /// Gets/Sets the foreground color. + /// + Color? ForegroundColor { get; set; } + + /// + /// Gets/Sets the font weight. + /// + FontWeight? FontWeight { get; set; } + + /// + /// Gets/Sets the font style. + /// + FontStyle? FontStyle { get; set; } + + /// + /// Gets/Sets the type of the marker. Use TextMarkerType.None for normal markers. + /// + TextMarkerTypes MarkerTypes { get; set; } + + /// + /// Gets/Sets the color of the marker. + /// + Color MarkerColor { get; set; } + + /// + /// Gets/Sets an object with additional data for this text marker. + /// + object Tag { get; set; } + + /// + /// Gets/Sets an object that will be displayed as tooltip in the text editor. + /// + /// Not supported in this sample! + object ToolTip { get; set; } + } + + [Flags] + public enum TextMarkerTypes + { + /// + /// Use no marker + /// + None = 0x0000, + /// + /// Use squiggly underline marker + /// + SquigglyUnderline = 0x001, + /// + /// Normal underline. + /// + NormalUnderline = 0x002, + /// + /// Dotted underline. + /// + DottedUnderline = 0x004, + + /// + /// Horizontal line in the scroll bar. + /// + LineInScrollBar = 0x0100, + /// + /// Small triangle in the scroll bar, pointing to the right. + /// + ScrollBarRightTriangle = 0x0400, + /// + /// Small triangle in the scroll bar, pointing to the left. + /// + ScrollBarLeftTriangle = 0x0800, + /// + /// Small circle in the scroll bar. + /// + CircleInScrollBar = 0x1000 + } + + public interface ITextMarkerService + { + /// + /// Creates a new text marker. The text marker will be invisible at first, + /// you need to set one of the Color properties to make it visible. + /// + ITextMarker Create(int startOffset, int length); + + /// + /// Gets the list of text markers. + /// + IEnumerable TextMarkers { get; } + + /// + /// Removes the specified text marker. + /// + void Remove(ITextMarker marker); + + /// + /// Removes all text markers that match the condition. + /// + void RemoveAll(Predicate predicate); + + /// + /// Finds all text markers at the specified offset. + /// + IEnumerable GetMarkersAtOffset(int offset); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs new file mode 100644 index 000000000..2bb3d8e03 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Errors/TextMarkerService.cs @@ -0,0 +1,365 @@ +// 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 System.Windows; +using System.Windows.Media; +using System.Windows.Threading; +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; + +namespace Tango.Scripting.Editors +{ + /// + /// Handles the text markers for a code editor. + /// + public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService, ITextViewConnect + { + TextSegmentCollection markers; + TextDocument document; + + public TextMarkerService(TextDocument document) + { + if (document == null) + throw new ArgumentNullException("document"); + this.document = document; + this.markers = new TextSegmentCollection(document); + } + + #region ITextMarkerService + public ITextMarker Create(int startOffset, int length) + { + if (markers == null) + throw new InvalidOperationException("Cannot create a marker when not attached to a document"); + + int textLength = document.TextLength; + if (startOffset < 0 || startOffset > textLength) + throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between 0 and " + textLength); + if (length < 0 || startOffset + length > textLength) + throw new ArgumentOutOfRangeException("length", length, "length must not be negative and startOffset+length must not be after the end of the document"); + + TextMarker m = new TextMarker(this, startOffset, length); + markers.Add(m); + // no need to mark segment for redraw: the text marker is invisible until a property is set + return m; + } + + public IEnumerable GetMarkersAtOffset(int offset) + { + if (markers == null) + return Enumerable.Empty(); + else + return markers.FindSegmentsContaining(offset); + } + + public IEnumerable TextMarkers { + get { return markers ?? Enumerable.Empty(); } + } + + public void RemoveAll(Predicate predicate) + { + if (predicate == null) + throw new ArgumentNullException("predicate"); + if (markers != null) { + foreach (TextMarker m in markers.ToArray()) { + if (predicate(m)) + Remove(m); + } + } + } + + public void Remove(ITextMarker marker) + { + if (marker == null) + throw new ArgumentNullException("marker"); + TextMarker m = marker as TextMarker; + if (markers != null && markers.Remove(m)) { + Redraw(m); + m.OnDeleted(); + } + } + + /// + /// Redraws the specified text segment. + /// + internal void Redraw(ISegment segment) + { + foreach (var view in textViews) { + view.Redraw(segment, DispatcherPriority.Normal); + } + if (RedrawRequested != null) + RedrawRequested(this, EventArgs.Empty); + } + + public event EventHandler RedrawRequested; + #endregion + + #region DocumentColorizingTransformer + protected override void ColorizeLine(DocumentLine line) + { + if (markers == null) + return; + int lineStart = line.Offset; + int lineEnd = lineStart + line.Length; + foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length)) { + Brush foregroundBrush = null; + if (marker.ForegroundColor != null) { + foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value); + foregroundBrush.Freeze(); + } + ChangeLinePart( + Math.Max(marker.StartOffset, lineStart), + Math.Min(marker.EndOffset, lineEnd), + element => { + if (foregroundBrush != null) { + element.TextRunProperties.SetForegroundBrush(foregroundBrush); + } + Typeface tf = element.TextRunProperties.Typeface; + element.TextRunProperties.SetTypeface(new Typeface( + tf.FontFamily, + marker.FontStyle ?? tf.Style, + marker.FontWeight ?? tf.Weight, + tf.Stretch + )); + } + ); + } + } + #endregion + + #region IBackgroundRenderer + public KnownLayer Layer { + get { + // draw behind selection + return KnownLayer.Selection; + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (drawingContext == null) + throw new ArgumentNullException("drawingContext"); + if (markers == null || !textView.VisualLinesValid) + return; + var visualLines = textView.VisualLines; + if (visualLines.Count == 0) + return; + int viewStart = visualLines.First().FirstDocumentLine.Offset; + int viewEnd = visualLines.Last().LastDocumentLine.EndOffset; + foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) { + if (marker.BackgroundColor != null) { + BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); + geoBuilder.AlignToWholePixels = true; + geoBuilder.CornerRadius = 3; + geoBuilder.AddSegment(textView, marker); + Geometry geometry = geoBuilder.CreateGeometry(); + if (geometry != null) { + Color color = marker.BackgroundColor.Value; + SolidColorBrush brush = new SolidColorBrush(color); + brush.Freeze(); + drawingContext.DrawGeometry(brush, null, geometry); + } + } + var underlineMarkerTypes = TextMarkerTypes.SquigglyUnderline | TextMarkerTypes.NormalUnderline | TextMarkerTypes.DottedUnderline; + if ((marker.MarkerTypes & underlineMarkerTypes) != 0) { + foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) { + Point startPoint = r.BottomLeft; + Point endPoint = r.BottomRight; + + Brush usedBrush = new SolidColorBrush(marker.MarkerColor); + usedBrush.Freeze(); + if ((marker.MarkerTypes & TextMarkerTypes.SquigglyUnderline) != 0) { + double offset = 2.5; + + int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4); + + StreamGeometry geometry = new StreamGeometry(); + + using (StreamGeometryContext ctx = geometry.Open()) { + ctx.BeginFigure(startPoint, false, false); + ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false); + } + + geometry.Freeze(); + + Pen usedPen = new Pen(usedBrush, 1); + usedPen.Freeze(); + drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry); + } + if ((marker.MarkerTypes & TextMarkerTypes.NormalUnderline) != 0) { + Pen usedPen = new Pen(usedBrush, 1); + usedPen.Freeze(); + drawingContext.DrawLine(usedPen, startPoint, endPoint); + } + if ((marker.MarkerTypes & TextMarkerTypes.DottedUnderline) != 0) { + Pen usedPen = new Pen(usedBrush, 1); + usedPen.DashStyle = DashStyles.Dot; + usedPen.Freeze(); + drawingContext.DrawLine(usedPen, startPoint, endPoint); + } + } + } + } + } + + IEnumerable CreatePoints(Point start, Point end, double offset, int count) + { + for (int i = 0; i < count; i++) + yield return new Point(start.X + i * offset, start.Y - ((i + 1) % 2 == 0 ? offset : 0)); + } + #endregion + + #region ITextViewConnect + readonly List textViews = new List(); + + void ITextViewConnect.AddToTextView(TextView textView) + { + if (textView != null && !textViews.Contains(textView)) { + Debug.Assert(textView.Document == document); + textViews.Add(textView); + } + } + + void ITextViewConnect.RemoveFromTextView(TextView textView) + { + if (textView != null) { + Debug.Assert(textView.Document == document); + textViews.Remove(textView); + } + } + #endregion + } + + public sealed class TextMarker : TextSegment, ITextMarker + { + readonly TextMarkerService service; + + public TextMarker(TextMarkerService service, int startOffset, int length) + { + if (service == null) + throw new ArgumentNullException("service"); + this.service = service; + this.StartOffset = startOffset; + this.Length = length; + this.markerTypes = TextMarkerTypes.None; + } + + public event EventHandler Deleted; + + public bool IsDeleted { + get { return !this.IsConnectedToCollection; } + } + + public void Delete() + { + service.Remove(this); + } + + internal void OnDeleted() + { + if (Deleted != null) + Deleted(this, EventArgs.Empty); + } + + void Redraw() + { + service.Redraw(this); + } + + Color? backgroundColor; + + public Color? BackgroundColor { + get { return backgroundColor; } + set { + if (backgroundColor != value) { + backgroundColor = value; + Redraw(); + } + } + } + + Color? foregroundColor; + + public Color? ForegroundColor { + get { return foregroundColor; } + set { + if (foregroundColor != value) { + foregroundColor = value; + Redraw(); + } + } + } + + FontWeight? fontWeight; + + public FontWeight? FontWeight { + get { return fontWeight; } + set { + if (fontWeight != value) { + fontWeight = value; + Redraw(); + } + } + } + + FontStyle? fontStyle; + + public FontStyle? FontStyle { + get { return fontStyle; } + set { + if (fontStyle != value) { + fontStyle = value; + Redraw(); + } + } + } + + public object Tag { get; set; } + + TextMarkerTypes markerTypes; + + public TextMarkerTypes MarkerTypes { + get { return markerTypes; } + set { + if (markerTypes != value) { + markerTypes = value; + Redraw(); + } + } + } + + Color markerColor; + + public Color MarkerColor { + get { return markerColor; } + set { + if (markerColor != value) { + markerColor = value; + Redraw(); + } + } + } + + public object ToolTip { get; set; } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs index 1605ff281..d112c6141 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/ExtensionMethods.cs @@ -22,7 +22,7 @@ namespace Tango.Scripting.Editors { List args = new List(); - foreach (var lGenericArgument in type.GetGenericTypeDefinition().GetGenericArguments()) + foreach (var lGenericArgument in type.GetGenericArguments()) { args.Add(lGenericArgument.Name); } diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs index a05d1fc75..72c27f9a9 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/OffsetColorizer.cs @@ -30,7 +30,7 @@ namespace Tango.Scripting.Editors.Highlighting { try { - ChangeLinePart(StartOffset, EndOffset, element => element.TextRunProperties.SetForegroundBrush(Brush)); + ChangeLinePart(StartOffset, EndOffset, element => element.TextRunProperties.SetBackgroundBrush(Brush)); } catch { } } diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd index 40f362e08..1f6139ff6 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Highlighting/Resources/CSharp-Mode.xshd @@ -11,7 +11,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -47,26 +47,7 @@ - - \# - - - - (define|undef|if|elif|else|endif|line)\b - - - - // - - - - - - (region|endregion|error|warning|pragma)\b - - - - + + @@ -83,95 +90,6 @@ - - @@ -223,6 +141,15 @@ + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/XamlEditor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/XamlEditor.cs new file mode 100644 index 000000000..22b425ba2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/XamlEditor.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace Tango.Scripting.Editors +{ + public class XamlEditor : TextEditor + { + private bool preventCodeUpdate; + + public String Xaml + { + get { return (String)GetValue(XamlProperty); } + set { SetValue(XamlProperty, value); } + } + public static readonly DependencyProperty XamlProperty = + DependencyProperty.Register("Xaml", typeof(String), typeof(XamlEditor), new PropertyMetadata(null, (d, e) => (d as XamlEditor).OnXamlChanged())); + + public XamlEditor() + { + TextChanged += XamlEditor_TextChanged; + } + + private void XamlEditor_TextChanged(object sender, EventArgs e) + { + if (!preventCodeUpdate) + { + preventCodeUpdate = true; + Xaml = Text; + preventCodeUpdate = false; + } + } + + private void OnXamlChanged() + { + if (!preventCodeUpdate) + { + preventCodeUpdate = true; + Text = Xaml; + preventCodeUpdate = false; + } + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config index d3a17b4de..16d75cf59 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/app.config @@ -77,6 +77,14 @@ + + + + + + + + diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config index 00eef19db..a0f62a1d4 100644 --- a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/packages.config @@ -4,6 +4,7 @@ + -- cgit v1.3.1