// // ITextEditor.cs // // Author: // Mike Krüger // // Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) // // 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 MonoDevelop.Core.Text; using System.Collections.Generic; using System.Text; using MonoDevelop.Ide.Gui; using MonoDevelop.Ide.Editor.Extension; using System.IO; using MonoDevelop.Ide.Editor.Highlighting; using Mono.Addins; using MonoDevelop.Core; using MonoDevelop.Ide.Extensions; using System.Linq; using MonoDevelop.Components; using System.ComponentModel; using MonoDevelop.Ide.TypeSystem; using System.Threading; using MonoDevelop.Ide.Editor.Projection; namespace MonoDevelop.Ide.Editor { public sealed class TextEditor : Control, ITextDocument, IDisposable { readonly ITextEditorImpl textEditorImpl; IReadonlyTextDocument ReadOnlyTextDocument { get { return textEditorImpl.Document; } } ITextDocument ReadWriteTextDocument { get { return (ITextDocument)textEditorImpl.Document; } } public ITextSourceVersion Version { get { return ReadOnlyTextDocument.Version; } } FileTypeCondition fileTypeCondition = new FileTypeCondition (); List allProviders = new List (); void OnTooltipProviderChanged (object s, ExtensionNodeEventArgs a) { TooltipProvider provider; try { var extensionNode = a.ExtensionNode as TooltipExtensionNode; allProviders.Add (extensionNode); if (extensionNode.IsValidFor (MimeType)) return; provider = (TooltipProvider) extensionNode.CreateInstance (); } catch (Exception e) { LoggingService.LogError ("Can't create tooltip provider:"+ a.ExtensionNode, e); return; } if (a.Change == ExtensionChange.Add) { textEditorImpl.AddTooltipProvider (provider); } else { textEditorImpl.RemoveTooltipProvider (provider); } } public event EventHandler SelectionChanged { add { textEditorImpl.SelectionChanged += value; } remove { textEditorImpl.SelectionChanged -= value; } } public event EventHandler CaretPositionChanged { add { textEditorImpl.CaretPositionChanged += value; } remove { textEditorImpl.CaretPositionChanged -= value; } } public event EventHandler BeginAtomicUndoOperation { add { textEditorImpl.BeginAtomicUndoOperation += value; } remove { textEditorImpl.BeginAtomicUndoOperation -= value; } } public event EventHandler EndAtomicUndoOperation { add { textEditorImpl.EndAtomicUndoOperation += value; } remove { textEditorImpl.EndAtomicUndoOperation -= value; } } public event EventHandler TextChanging { add { ReadWriteTextDocument.TextChanging += value; } remove { ReadWriteTextDocument.TextChanging -= value; } } public event EventHandler TextChanged { add { ReadWriteTextDocument.TextChanged += value; } remove { ReadWriteTextDocument.TextChanged -= value; } } public event EventHandler BeginMouseHover { add { textEditorImpl.BeginMouseHover += value; } remove { textEditorImpl.BeginMouseHover -= value; } } public event EventHandler VAdjustmentChanged { add { textEditorImpl.VAdjustmentChanged += value; } remove { textEditorImpl.VAdjustmentChanged -= value; } } public event EventHandler HAdjustmentChanged { add { textEditorImpl.HAdjustmentChanged += value; } remove { textEditorImpl.HAdjustmentChanged -= value; } } public char this[int offset] { get { return ReadOnlyTextDocument [offset]; } set { ReadWriteTextDocument [offset] = value; } } // public event EventHandler LineChanged { // add { textEditorImpl.LineChanged += value; } // remove { textEditorImpl.LineChanged -= value; } // } // // public event EventHandler LineInserted { // add { textEditorImpl.LineInserted += value; } // remove { textEditorImpl.LineInserted -= value; } // } // // public event EventHandler LineRemoved { // add { textEditorImpl.LineRemoved += value; } // remove { textEditorImpl.LineRemoved -= value; } // } public ITextEditorOptions Options { get { return textEditorImpl.Options; } set { textEditorImpl.Options = value; OnOptionsChanged (EventArgs.Empty); } } public event EventHandler OptionsChanged; void OnOptionsChanged (EventArgs e) { var handler = OptionsChanged; if (handler != null) handler (this, e); } public EditMode EditMode { get { return textEditorImpl.EditMode; } } public DocumentLocation CaretLocation { get { return textEditorImpl.CaretLocation; } set { textEditorImpl.CaretLocation = value; } } public SemanticHighlighting SemanticHighlighting { get { return textEditorImpl.SemanticHighlighting; } set { textEditorImpl.SemanticHighlighting = value; } } public int CaretLine { get { return CaretLocation.Line; } set { CaretLocation = new DocumentLocation (value, CaretColumn); } } public int CaretColumn { get { return CaretLocation.Column; } set { CaretLocation = new DocumentLocation (CaretLine, value); } } public int CaretOffset { get { return textEditorImpl.CaretOffset; } set { textEditorImpl.CaretOffset = value; } } public bool IsReadOnly { get { return ReadOnlyTextDocument.IsReadOnly; } set { ReadWriteTextDocument.IsReadOnly = value; } } public bool IsSomethingSelected { get { return textEditorImpl.IsSomethingSelected; } } public SelectionMode SelectionMode { get { return textEditorImpl.SelectionMode; } } public ISegment SelectionRange { get { return textEditorImpl.SelectionRange; } set { textEditorImpl.SelectionRange = value; } } public DocumentRegion SelectionRegion { get { return textEditorImpl.SelectionRegion; } set { textEditorImpl.SelectionRegion = value; } } public int SelectionAnchorOffset { get { return textEditorImpl.SelectionAnchorOffset; } set { textEditorImpl.SelectionAnchorOffset = value; } } public int SelectionLeadOffset { get { return textEditorImpl.SelectionLeadOffset; } set { textEditorImpl.SelectionLeadOffset = value; } } public string SelectedText { get { return IsSomethingSelected ? ReadOnlyTextDocument.GetTextAt (SelectionRange) : null; } set { var selection = SelectionRange; ReplaceText (selection, value); SelectionRange = new TextSegment (selection.Offset, value.Length); } } public bool IsInAtomicUndo { get { return ReadWriteTextDocument.IsInAtomicUndo; } } public double LineHeight { get { return textEditorImpl.LineHeight; } } /// /// Gets or sets the type of the MIME. /// /// The type of the MIME. public string MimeType { get { return ReadOnlyTextDocument.MimeType; } set { ReadWriteTextDocument.MimeType = value; } } public event EventHandler MimeTypeChanged { add { ReadWriteTextDocument.MimeTypeChanged += value; } remove { ReadWriteTextDocument.MimeTypeChanged -= value; } } public string Text { get { return ReadOnlyTextDocument.Text; } set { ReadWriteTextDocument.Text = value; } } /// /// Gets the eol marker. On a text editor always use that and not GetEolMarker. /// The EOL marker of the document may get overwritten my the one from the options. /// public string EolMarker { get { if (Options.OverrideDocumentEolMarker) return Options.DefaultEolMarker; return ReadOnlyTextDocument.GetEolMarker (); } } public bool UseBOM { get { return ReadOnlyTextDocument.UseBOM; } set { ReadWriteTextDocument.UseBOM = value; } } public Encoding Encoding { get { return ReadOnlyTextDocument.Encoding; } set { ReadWriteTextDocument.Encoding = value; } } public int LineCount { get { return ReadOnlyTextDocument.LineCount; } } /// /// Gets the name of the file the document is stored in. /// Could also be a non-existent dummy file name or null if no name has been set. /// public FilePath FileName { get { return ReadOnlyTextDocument.FileName; } set { ReadWriteTextDocument.FileName = value; } } public event EventHandler FileNameChanged { add { ReadWriteTextDocument.FileNameChanged += value; } remove { ReadWriteTextDocument.FileNameChanged -= value; } } public int Length { get { return ReadOnlyTextDocument.Length; } } public double ZoomLevel { get { return textEditorImpl.ZoomLevel; } set { textEditorImpl.ZoomLevel = value; } } public event EventHandler ZoomLevelChanged { add { textEditorImpl.ZoomLevelChanged += value; } remove { textEditorImpl.ZoomLevelChanged -= value; } } public IDisposable OpenUndoGroup () { return ReadWriteTextDocument.OpenUndoGroup (); } public void SetSelection (int anchorOffset, int leadOffset) { textEditorImpl.SetSelection (anchorOffset, leadOffset); } public void SetSelection (DocumentLocation anchor, DocumentLocation lead) { SetSelection (LocationToOffset (anchor), LocationToOffset (lead)); } public void SetCaretLocation (DocumentLocation location, bool usePulseAnimation = false) { CaretLocation = location; ScrollTo (CaretLocation); if (usePulseAnimation) StartCaretPulseAnimation (); } public void SetCaretLocation (int line, int col, bool usePulseAnimation = false) { CaretLocation = new DocumentLocation (line, col); CenterTo (CaretLocation); if (usePulseAnimation) StartCaretPulseAnimation (); } public void ClearSelection () { textEditorImpl.ClearSelection (); } public void CenterToCaret () { textEditorImpl.CenterToCaret (); } public void StartCaretPulseAnimation () { textEditorImpl.StartCaretPulseAnimation (); } public int EnsureCaretIsNotVirtual () { return textEditorImpl.EnsureCaretIsNotVirtual (); } public void FixVirtualIndentation () { textEditorImpl.FixVirtualIndentation (); } public void RunWhenLoaded (Action action) { if (action == null) throw new ArgumentNullException ("action"); textEditorImpl.RunWhenLoaded (action); } public string FormatString (DocumentLocation insertPosition, string code) { return textEditorImpl.FormatString (LocationToOffset (insertPosition), code); } public string FormatString (int offset, string code) { return textEditorImpl.FormatString (offset, code); } public void StartInsertionMode (InsertionModeOptions insertionModeOptions) { if (insertionModeOptions == null) throw new ArgumentNullException ("insertionModeOptions"); textEditorImpl.StartInsertionMode (insertionModeOptions); } public void StartTextLinkMode (TextLinkModeOptions textLinkModeOptions) { if (textLinkModeOptions == null) throw new ArgumentNullException ("textLinkModeOptions"); textEditorImpl.StartTextLinkMode (textLinkModeOptions); } public void InsertAtCaret (string text) { InsertText (CaretOffset, text); } public DocumentLocation PointToLocation (double xp, double yp, bool endAtEol = false) { return textEditorImpl.PointToLocation (xp, yp, endAtEol); } public Xwt.Point LocationToPoint (DocumentLocation location) { return textEditorImpl.LocationToPoint (location.Line, location.Column); } public Xwt.Point LocationToPoint (int line, int column) { return textEditorImpl.LocationToPoint (line, column); } public string GetLineText (int line, bool includeDelimiter = false) { var segment = GetLine (line); return GetTextAt (includeDelimiter ? segment.SegmentIncludingDelimiter : segment); } public int LocationToOffset (int line, int column) { return ReadOnlyTextDocument.LocationToOffset (new DocumentLocation (line, column)); } public int LocationToOffset (DocumentLocation location) { return ReadOnlyTextDocument.LocationToOffset (location); } public DocumentLocation OffsetToLocation (int offset) { return ReadOnlyTextDocument.OffsetToLocation (offset); } public void InsertText (int offset, string text) { ReadWriteTextDocument.InsertText (offset, text); } public void InsertText (int offset, ITextSource text) { ReadWriteTextDocument.InsertText (offset, text); } public void RemoveText (int offset, int count) { RemoveText (new TextSegment (offset, count)); } public void RemoveText (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); ReadWriteTextDocument.RemoveText (segment); } public void ReplaceText (int offset, int count, string value) { ReadWriteTextDocument.ReplaceText (offset, count, value); } public void ReplaceText (int offset, int count, ITextSource value) { ReadWriteTextDocument.ReplaceText (offset, count, value); } public void ReplaceText (ISegment segment, string value) { if (segment == null) throw new ArgumentNullException ("segment"); ReadWriteTextDocument.ReplaceText (segment.Offset, segment.Length, value); } public void ReplaceText (ISegment segment, ITextSource value) { if (segment == null) throw new ArgumentNullException ("segment"); ReadWriteTextDocument.ReplaceText (segment.Offset, segment.Length, value); } public IDocumentLine GetLine (int lineNumber) { return ReadOnlyTextDocument.GetLine (lineNumber); } public IDocumentLine GetLineByOffset (int offset) { return ReadOnlyTextDocument.GetLineByOffset (offset); } public int OffsetToLineNumber (int offset) { return ReadOnlyTextDocument.OffsetToLineNumber (offset); } public void AddMarker (IDocumentLine line, ITextLineMarker lineMarker) { if (line == null) throw new ArgumentNullException ("line"); if (lineMarker == null) throw new ArgumentNullException ("lineMarker"); textEditorImpl.AddMarker (line, lineMarker); } public void AddMarker (int lineNumber, ITextLineMarker lineMarker) { if (lineMarker == null) throw new ArgumentNullException ("lineMarker"); AddMarker (GetLine (lineNumber), lineMarker); } public void RemoveMarker (ITextLineMarker lineMarker) { if (lineMarker == null) throw new ArgumentNullException ("lineMarker"); textEditorImpl.RemoveMarker (lineMarker); } public IEnumerable GetLineMarkers (IDocumentLine line) { if (line == null) throw new ArgumentNullException ("line"); return textEditorImpl.GetLineMarkers (line); } public IEnumerable GetTextSegmentMarkersAt (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); return textEditorImpl.GetTextSegmentMarkersAt (segment); } public IEnumerable GetTextSegmentMarkersAt (int offset) { return textEditorImpl.GetTextSegmentMarkersAt (offset); } public void AddMarker (ITextSegmentMarker marker) { if (marker == null) throw new ArgumentNullException ("marker"); textEditorImpl.AddMarker (marker); } public bool RemoveMarker (ITextSegmentMarker marker) { if (marker == null) throw new ArgumentNullException ("marker"); return textEditorImpl.RemoveMarker (marker); } public void SetFoldings (IEnumerable foldings) { if (foldings == null) throw new ArgumentNullException ("foldings"); textEditorImpl.SetFoldings (foldings); } public IEnumerable GetFoldingsContaining (int offset) { return textEditorImpl.GetFoldingsContaining (offset); } public IEnumerable GetFoldingsIn (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); return textEditorImpl.GetFoldingsIn (segment.Offset, segment.Length); } /// /// Gets a character at the specified position in the document. /// /// The index of the character to get. /// Offset is outside the valid range (0 to TextLength-1). /// The character at the specified position. /// This is the same as Text[offset], but is more efficient because /// it doesn't require creating a String object. public char GetCharAt (int offset) { return ReadOnlyTextDocument.GetCharAt (offset); } public string GetTextAt (int offset, int length) { return ReadOnlyTextDocument.GetTextAt (offset, length); } public string GetTextAt (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); return ReadOnlyTextDocument.GetTextAt (segment); } public IReadonlyTextDocument CreateDocumentSnapshot () { return ReadWriteTextDocument.CreateDocumentSnapshot (); } public string GetVirtualIndentationString (int lineNumber) { if (lineNumber < 1 || lineNumber > LineCount) throw new ArgumentOutOfRangeException ("lineNumber"); return textEditorImpl.GetVirtualIndentationString (lineNumber); } public string GetVirtualIndentationString (IDocumentLine line) { if (line == null) throw new ArgumentNullException ("line"); return textEditorImpl.GetVirtualIndentationString (line.LineNumber); } public int GetVirtualIndentationColumn (int lineNumber) { if (lineNumber < 1 || lineNumber > LineCount) throw new ArgumentOutOfRangeException ("lineNumber"); return 1 + textEditorImpl.GetVirtualIndentationString (lineNumber).Length; } public int GetVirtualIndentationColumn (IDocumentLine line) { if (line == null) throw new ArgumentNullException ("line"); return 1 + textEditorImpl.GetVirtualIndentationString (line.LineNumber).Length; } public TextReader CreateReader () { return ReadOnlyTextDocument.CreateReader (); } public TextReader CreateReader (int offset, int length) { return ReadOnlyTextDocument.CreateReader (offset, length); } public ITextSource CreateSnapshot () { return ReadOnlyTextDocument.CreateSnapshot (); } public ITextSource CreateSnapshot (int offset, int length) { return ReadOnlyTextDocument.CreateSnapshot (offset, length); } public ITextSource CreateSnapshot (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); return ReadOnlyTextDocument.CreateSnapshot (segment.Offset, segment.Length); } public void WriteTextTo (TextWriter writer) { if (writer == null) throw new ArgumentNullException ("writer"); ReadOnlyTextDocument.WriteTextTo (writer); } public void WriteTextTo (TextWriter writer, int offset, int length) { if (writer == null) throw new ArgumentNullException ("writer"); ReadOnlyTextDocument.WriteTextTo (writer, offset, length); } public void ScrollTo (int offset) { textEditorImpl.ScrollTo (offset); } public void ScrollTo (DocumentLocation loc) { ScrollTo (LocationToOffset (loc)); } public void CenterTo (int offset) { textEditorImpl.CenterTo (offset); } public void CenterTo (DocumentLocation loc) { CenterTo (LocationToOffset (loc)); } [EditorBrowsable(EditorBrowsableState.Advanced)] public void SetIndentationTracker (IndentationTracker indentationTracker) { textEditorImpl.SetIndentationTracker (indentationTracker); } [EditorBrowsable(EditorBrowsableState.Advanced)] public void SetSelectionSurroundingProvider (SelectionSurroundingProvider surroundingProvider) { textEditorImpl.SetSelectionSurroundingProvider (surroundingProvider); } [EditorBrowsable(EditorBrowsableState.Advanced)] public void SetTextPasteHandler (TextPasteHandler textPasteHandler) { textEditorImpl.SetTextPasteHandler (textPasteHandler); } public IList SkipChars { get { return textEditorImpl.SkipChars; } } /// /// Skip chars are /// public void AddSkipChar (int offset, char ch) { textEditorImpl.AddSkipChar (offset, ch); } protected override void Dispose (bool disposing) { if (disposing) { DetachExtensionChain (); textEditorImpl.Dispose (); } base.Dispose (disposing); } protected override object CreateNativeWidget () { return textEditorImpl.CreateNativeControl (); } #region Internal API ExtensionContext extensionContext; internal ExtensionContext ExtensionContext { get { return extensionContext; } set { if (extensionContext != null) { extensionContext.RemoveExtensionNodeHandler ("MonoDevelop/SourceEditor2/TooltipProviders", OnTooltipProviderChanged); // textEditorImpl.ClearTooltipProviders (); } extensionContext = value; if (extensionContext != null) extensionContext.AddExtensionNodeHandler ("MonoDevelop/SourceEditor2/TooltipProviders", OnTooltipProviderChanged); } } internal IEditorActionHost EditorActionHost { get { return textEditorImpl.Actions; } } internal ITextMarkerFactory TextMarkerFactory { get { return textEditorImpl.TextMarkerFactory; } } internal TextEditor (ITextEditorImpl textEditorImpl) { if (textEditorImpl == null) throw new ArgumentNullException ("textEditorImpl"); this.textEditorImpl = textEditorImpl; commandRouter = new InternalCommandRouter (this); fileTypeCondition.SetFileName (FileName); ExtensionContext = AddinManager.CreateExtensionContext (); ExtensionContext.RegisterCondition ("FileType", fileTypeCondition); FileNameChanged += delegate { fileTypeCondition.SetFileName (FileName); }; MimeTypeChanged += delegate { textEditorImpl.ClearTooltipProviders (); foreach (var extensionNode in allProviders) { if (extensionNode.IsValidFor (MimeType)) textEditorImpl.AddTooltipProvider ((TooltipProvider) extensionNode.CreateInstance ()); } }; } TextEditorViewContent viewContent; internal IViewContent GetViewContent () { if (viewContent == null) { viewContent = new TextEditorViewContent (this, textEditorImpl); } return viewContent; } internal IFoldSegment CreateFoldSegment (int offset, int length, bool isFolded = false) { return textEditorImpl.CreateFoldSegment (offset, length, isFolded); } #endregion #region Editor extensions InternalCommandRouter commandRouter; class InternalCommandRouter : MonoDevelop.Components.Commands.IMultiCastCommandRouter { readonly TextEditor editor; public InternalCommandRouter (TextEditor editor) { this.editor = editor; } #region IMultiCastCommandRouter implementation System.Collections.IEnumerable MonoDevelop.Components.Commands.IMultiCastCommandRouter.GetCommandTargets () { yield return editor.textEditorImpl; yield return editor.textEditorImpl.EditorExtension; } #endregion } internal object CommandRouter { get { return commandRouter; } } DocumentContext documentContext; internal DocumentContext DocumentContext { get { return documentContext; } set { documentContext = value; OnDocumentContextChanged (EventArgs.Empty); } } public event EventHandler DocumentContextChanged; void OnDocumentContextChanged (EventArgs e) { if (DocumentContext != null) { textEditorImpl.SetQuickTaskProviders (DocumentContext.GetContents ()); textEditorImpl.SetUsageTaskProviders (DocumentContext.GetContents ()); } else { textEditorImpl.SetQuickTaskProviders (Enumerable.Empty ()); textEditorImpl.SetUsageTaskProviders (Enumerable.Empty ()); } var handler = DocumentContextChanged; if (handler != null) handler (this, e); } internal void InitializeExtensionChain (DocumentContext documentContext) { if (documentContext == null) throw new ArgumentNullException ("documentContext"); DetachExtensionChain (); var extensions = ExtensionContext.GetExtensionNodes ("/MonoDevelop/Ide/TextEditorExtensions", typeof(TextEditorExtensionNode)); TextEditorExtension last = null; var mimetypeChain = DesktopService.GetMimeTypeInheritanceChainForFile (FileName).ToArray (); foreach (TextEditorExtensionNode extNode in extensions) { if (!extNode.Supports (FileName, mimetypeChain)) continue; TextEditorExtension ext; try { var instance = extNode.CreateInstance (); ext = instance as TextEditorExtension; if (ext == null) continue; } catch (Exception e) { LoggingService.LogError ("Error while creating text editor extension :" + extNode.Id + "(" + extNode.Type +")", e); continue; } if (ext.IsValidInContext (documentContext)) { if (last != null) { last.Next = ext; last = ext; } else { textEditorImpl.EditorExtension = last = ext; } ext.Initialize (this, documentContext); } } this.DocumentContext = documentContext; } void DetachExtensionChain () { var editorExtension = textEditorImpl.EditorExtension; while (editorExtension != null) { try { editorExtension.Dispose (); } catch (Exception ex) { LoggingService.LogError ("Exception while disposing extension:" + editorExtension, ex); } editorExtension = editorExtension.Next; } textEditorImpl.EditorExtension = null; } public T GetContent () where T : class { T result = textEditorImpl as T; if (result != null) return result; var ext = textEditorImpl.EditorExtension; while (ext != null) { result = ext as T; if (result != null) return result; ext = ext.Next; } return null; } public IEnumerable GetContents () where T : class { T result = textEditorImpl as T; if (result != null) yield return result; var ext = textEditorImpl.EditorExtension; while (ext != null) { result = ext as T; if (result != null) yield return result; ext = ext.Next; } } #endregion public string GetPangoMarkup (int offset, int length) { return textEditorImpl.GetPangoMarkup (offset, length); } public string GetPangoMarkup (ISegment segment) { if (segment == null) throw new ArgumentNullException ("segment"); return textEditorImpl.GetPangoMarkup (segment.Offset, segment.Length); } public static implicit operator Microsoft.CodeAnalysis.Text.SourceText (TextEditor editor) { return new MonoDevelopSourceText (editor); } #region Annotations // Annotations: points either null (no annotations), to the single annotation, // or to an AnnotationList. // Once it is pointed at an AnnotationList, it will never change (this allows thread-safety support by locking the list) object annotations; sealed class AnnotationList : List, ICloneable { // There are two uses for this custom list type: // 1) it's private, and thus (unlike List) cannot be confused with real annotations // 2) It allows us to simplify the cloning logic by making the list behave the same as a clonable annotation. public AnnotationList (int initialCapacity) : base(initialCapacity) { } public object Clone () { lock (this) { AnnotationList copy = new AnnotationList (this.Count); for (int i = 0; i < this.Count; i++) { object obj = this [i]; ICloneable c = obj as ICloneable; copy.Add (c != null ? c.Clone () : obj); } return copy; } } } public void AddAnnotation (object annotation) { if (annotation == null) throw new ArgumentNullException ("annotation"); retry: // Retry until successful object oldAnnotation = Interlocked.CompareExchange (ref this.annotations, annotation, null); if (oldAnnotation == null) { return; // we successfully added a single annotation } AnnotationList list = oldAnnotation as AnnotationList; if (list == null) { // we need to transform the old annotation into a list list = new AnnotationList (4); list.Add (oldAnnotation); list.Add (annotation); if (Interlocked.CompareExchange (ref this.annotations, list, oldAnnotation) != oldAnnotation) { // the transformation failed (some other thread wrote to this.annotations first) goto retry; } } else { // once there's a list, use simple locking lock (list) { list.Add (annotation); } } } public void RemoveAnnotations () where T : class { retry: // Retry until successful object oldAnnotations = this.annotations; var list = oldAnnotations as AnnotationList; if (list != null) { lock (list) list.RemoveAll (obj => obj is T); } else if (oldAnnotations is T) { if (Interlocked.CompareExchange (ref this.annotations, null, oldAnnotations) != oldAnnotations) { // Operation failed (some other thread wrote to this.annotations first) goto retry; } } } public T Annotation () where T: class { object annotations = this.annotations; var list = annotations as AnnotationList; if (list != null) { lock (list) { foreach (object obj in list) { T t = obj as T; if (t != null) return t; } return null; } } return annotations as T; } /// /// Gets all annotations stored on this AstNode. /// public IEnumerable Annotations { get { object annotations = this.annotations; AnnotationList list = annotations as AnnotationList; if (list != null) { lock (list) { return list.ToArray (); } } if (annotations != null) return new [] { annotations }; return Enumerable.Empty (); } } #endregion List projectedProviders = new List (); IReadOnlyList projections = null; public void SetOrUpdateProjections (DocumentContext ctx, IReadOnlyList projections, DisabledProjectionFeatures disabledFeatures = DisabledProjectionFeatures.None) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (this.projections != null) { foreach (var projection in this.projections) { projection.Dettach (); } } this.projections = projections; if (projections != null) { foreach (var projection in projections) { projection.Attach (this); } } if ((disabledFeatures & DisabledProjectionFeatures.SemanticHighlighting) != DisabledProjectionFeatures.SemanticHighlighting) { if (SemanticHighlighting is ProjectedSemanticHighlighting) { ((ProjectedSemanticHighlighting)SemanticHighlighting).UpdateProjection (projections); } else { SemanticHighlighting = new ProjectedSemanticHighlighting (this, ctx, projections); } } if ((disabledFeatures & DisabledProjectionFeatures.Tooltips) != DisabledProjectionFeatures.Tooltips) { projectedProviders.ForEach (textEditorImpl.RemoveTooltipProvider); projectedProviders = new List (); foreach (var projection in projections) { foreach (var tp in projection.ProjectedEditor.textEditorImpl.TooltipProvider) { var newProvider = new ProjectedTooltipProvider (this, ctx, projection, tp); projectedProviders.Add (newProvider); textEditorImpl.AddTooltipProvider (newProvider); } } } InitializeProjectionExtensions (ctx, disabledFeatures); } bool projectionsAdded = false; void InitializeProjectionExtensions (DocumentContext ctx, DisabledProjectionFeatures disabledFeatures) { if (projectionsAdded) { TextEditorExtension ext = textEditorImpl.EditorExtension; while (ext != null && ext.Next != null) { var pext = ext as IProjectionExtension; if (pext != null) { pext.Projections = projections; } ext = ext.Next; } return; } if (projections.Count == 0) return; TextEditorExtension lastExtension = textEditorImpl.EditorExtension; while (lastExtension != null && lastExtension.Next != null) { var completionTextEditorExtension = lastExtension.Next as CompletionTextEditorExtension; if (completionTextEditorExtension != null) { var projectedFilterExtension = new ProjectedFilterCompletionTextEditorExtension (completionTextEditorExtension, projections) { Next = completionTextEditorExtension.Next }; lastExtension.Next = projectedFilterExtension; projectedFilterExtension.Initialize (this, DocumentContext); } lastExtension = lastExtension.Next; } // no extensions -> no projections needed if (textEditorImpl.EditorExtension == null) return; if ((disabledFeatures & DisabledProjectionFeatures.Completion) != DisabledProjectionFeatures.Completion) { var projectedCompletionExtension = new ProjectedCompletionExtension (ctx, projections); projectedCompletionExtension.Next = textEditorImpl.EditorExtension; textEditorImpl.EditorExtension = projectedCompletionExtension; projectedCompletionExtension.Initialize (this, DocumentContext); } projectionsAdded = true; } public void AddOverlay (Control messageOverlayContent, Func sizeFunc) { textEditorImpl.AddOverlay (messageOverlayContent, sizeFunc); } public void RemoveOverlay (Control messageOverlayContent) { textEditorImpl.RemoveOverlay (messageOverlayContent); } } }