From 0aee08dc57c32b1c6f18f7b3ce70742ed0bae387 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 22 Jul 2013 00:30:06 +0200 Subject: [PATCH] Replace HighlightedInlineBuilder with RichTextModel. --- .../Src/Completion/CSharpInsightItem.cs | 5 +- .../AvalonEdit.AddIn/Src/DocumentPrinter.cs | 12 +- .../Project/Gui/SearchResultNode.cs | 10 +- .../Document/OffsetChangeMap.cs | 2 +- .../Highlighting/HighlightedInlineBuilder.cs | 14 +- .../Highlighting/HighlightedLine.cs | 17 +- .../Highlighting/HighlightingColor.cs | 6 + .../Highlighting/RichText.cs | 2 +- .../Highlighting/RichTextModel.cs | 205 +++++++++++------- .../Highlighting/RichTextModelWriter.cs | 5 +- .../Project/Editor/Search/SearchResultsPad.cs | 12 +- 11 files changed, 168 insertions(+), 122 deletions(-) diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs index 0aeaa92216..bd217f36f1 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpInsightItem.cs @@ -53,10 +53,11 @@ namespace CSharpBinding.Completion var stringBuilder = new StringBuilder(); var formatter = new ParameterHighlightingOutputFormatter(stringBuilder, highlightedParameterIndex); ambience.ConvertEntity(Method, formatter, FormattingOptionsFactory.CreateSharpDevelop()); - var inlineBuilder = new HighlightedInlineBuilder(stringBuilder.ToString()); + string code = stringBuilder.ToString(); + var inlineBuilder = new RichTextModel(); inlineBuilder.SetFontWeight(formatter.parameterStartOffset, formatter.parameterLength, FontWeights.Bold); header.Inlines.Clear(); - header.Inlines.AddRange(inlineBuilder.CreateRuns()); + header.Inlines.AddRange(new RichText(code, inlineBuilder).CreateRuns()); } public object Content { diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/DocumentPrinter.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/DocumentPrinter.cs index 8ae7994ec9..3e95a21c96 100755 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/DocumentPrinter.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/DocumentPrinter.cs @@ -50,16 +50,14 @@ namespace ICSharpCode.AvalonEdit.AddIn // TableRow row = new TableRow(); // trg.Rows.Add(row); // row.Cells.Add(new TableCell(new Paragraph(new Run(lineNumber.ToString()))) { TextAlignment = TextAlignment.Right }); - HighlightedInlineBuilder inlineBuilder = new HighlightedInlineBuilder(document.GetText(line)); +// Paragraph p = new Paragraph(); +// row.Cells.Add(new TableCell(p)); if (highlighter != null) { HighlightedLine highlightedLine = highlighter.HighlightLine(lineNumber); - int lineStartOffset = line.Offset; - foreach (HighlightedSection section in highlightedLine.Sections) - inlineBuilder.SetHighlighting(section.Offset - lineStartOffset, section.Length, section.Color); + p.Inlines.AddRange(highlightedLine.ToRichText().CreateRuns()); + } else { + p.Inlines.Add(document.GetText(line)); } -// Paragraph p = new Paragraph(); -// row.Cells.Add(new TableCell(p)); - p.Inlines.AddRange(inlineBuilder.CreateRuns()); } return p; } diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchResultNode.cs b/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchResultNode.cs index 17d0ebe002..60315ee17e 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchResultNode.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Gui/SearchResultNode.cs @@ -68,13 +68,13 @@ namespace SearchAndReplace RichText displayText = result.DisplayText; if (displayText != null) { - HighlightedInlineBuilder builder = new HighlightedInlineBuilder(displayText); if (IsSelected) { - builder = builder.Clone(); - builder.SetForeground(0, builder.Text.Length, null); - builder.SetBackground(0, builder.Text.Length, null); + RichTextModel model = displayText.ToRichTextModel(); + model.SetForeground(0, displayText.Length, null); + model.SetBackground(0, displayText.Length, null); + displayText = new RichText(displayText.Text, model); } - textBlock.Inlines.AddRange(builder.CreateRuns()); + textBlock.Inlines.AddRange(displayText.CreateRuns()); } if (showFileName) { diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs index 42d1e673eb..692b15e7b4 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs @@ -255,7 +255,7 @@ namespace ICSharpCode.AvalonEdit.Document /// /// Gets the new offset where the specified offset moves after this document change. /// - public int GetNewOffset(int oldOffset, AnchorMovementType movementType) + public int GetNewOffset(int oldOffset, AnchorMovementType movementType = AnchorMovementType.Default) { int insertionLength = this.InsertionLength; int removalLength = this.RemovalLength; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs index 4fee0bf8d3..734c053969 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedInlineBuilder.cs @@ -22,6 +22,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// into a TextBlock. /// In SharpDevelop, we use it to provide syntax highlighting inside the search results pad. /// + [Obsolete("Use RichText / RichTextModel instead")] public sealed class HighlightedInlineBuilder { static HighlightingBrush MakeBrush(Brush b) @@ -160,19 +161,6 @@ namespace ICSharpCode.AvalonEdit.Highlighting } } - /// - /// Sets the font family on the specified text segment. - /// - public void SetFontFamily(int offset, int length, FontFamily family) - { - int startIndex = GetIndexForOffset(offset); - int endIndex = GetIndexForOffset(offset + length); - for (int i = startIndex; i < endIndex; i++) { - throw new NotSupportedException(); - //stateChanges[i].FontFamily = family; - } - } - /// /// Creates WPF Run instances that can be used for TextBlock.Inlines. /// diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs index 0893a7ba1b..90ab30f757 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs @@ -286,23 +286,36 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// /// Creates a that stores the text and highlighting of this line. /// + [Obsolete("Use ToRichText() instead")] public HighlightedInlineBuilder ToInlineBuilder() { HighlightedInlineBuilder builder = new HighlightedInlineBuilder(Document.GetText(DocumentLine)); int startOffset = DocumentLine.Offset; - // copy only the foreground and background colors foreach (HighlightedSection section in Sections) { builder.SetHighlighting(section.Offset - startOffset, section.Length, section.Color); } return builder; } + /// + /// Creates a that stores the highlighting of this line. + /// + public RichTextModel ToRichTextModel() + { + var builder = new RichTextModel(); + int startOffset = DocumentLine.Offset; + foreach (HighlightedSection section in Sections) { + builder.ApplyHighlighting(section.Offset - startOffset, section.Length, section.Color); + } + return builder; + } + /// /// Creates a that stores the text and highlighting of this line. /// public RichText ToRichText() { - return ToInlineBuilder().ToRichText(); + return new RichText(Document.GetText(DocumentLine), ToRichTextModel()); } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColor.cs index 6632b93603..d518b962b9 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColor.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColor.cs @@ -254,5 +254,11 @@ namespace ICSharpCode.AvalonEdit.Highlighting if (color.background != null) this.background = color.background; } + + internal bool IsEmptyForMerge { + get { + return fontWeight == null && fontStyle == null && foreground == null && background == null; + } + } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichText.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichText.cs index 104b8505da..46fd4c9902 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichText.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichText.cs @@ -127,7 +127,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// public RichTextModel ToRichTextModel() { - return new RichTextModel(GetHighlightedSections(0, this.Length)); + return new RichTextModel(stateChangeOffsets, stateChanges); } /// diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModel.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModel.cs index 6517875eb5..ca1f588612 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModel.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModel.cs @@ -4,6 +4,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.AvalonEdit.Document; @@ -14,52 +16,77 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// /// Stores rich-text formatting. /// - public sealed class RichTextModel // TODO: maybe rename to HighlightingModel? + public sealed class RichTextModel : AbstractFreezable { - CompressingTreeList list = new CompressingTreeList(object.Equals); + List stateChangeOffsets = new List(); + List stateChanges = new List(); - /// - /// Gets the length of the document. - /// This has an effect on which coordinates are valid for this RichTextModel. - /// - public int DocumentLength { - get { return list.Count; } + int GetIndexForOffset(int offset) + { + if (offset < 0) + throw new ArgumentOutOfRangeException("offset"); + int index = stateChangeOffsets.BinarySearch(offset); + if (index < 0) { + // If no color change exists directly at offset, + // create a new one. + index = ~index; + stateChanges.Insert(index, stateChanges[index - 1].Clone()); + stateChangeOffsets.Insert(index, offset); + } + return index; + } + + int GetIndexForOffsetUseExistingSegment(int offset) + { + if (offset < 0) + throw new ArgumentOutOfRangeException("offset"); + int index = stateChangeOffsets.BinarySearch(offset); + if (index < 0) { + // If no color change exists directly at offset, + // return the index of the color segment that contains offset. + index = ~index - 1; + } + return index; + } + + int GetEnd(int index) + { + // Gets the end of the color segment no. index. + if (index + 1 < stateChangeOffsets.Count) + return stateChangeOffsets[index + 1]; + else + return int.MaxValue; } /// - /// Creates a new RichTextModel that needs manual calls to . + /// Creates a new RichTextModel. /// - public RichTextModel(int documentLength) + public RichTextModel() { - list.InsertRange(0, documentLength, HighlightingColor.Empty); + stateChangeOffsets.Add(0); + stateChanges.Add(new HighlightingColor()); } /// /// Creates a RichTextModel from a CONTIGUOUS list of HighlightedSections. /// - internal RichTextModel(IEnumerable sections) + internal RichTextModel(int[] stateChangeOffsets, HighlightingColor[] stateChanges) { - foreach (var section in sections) { - list.InsertRange(section.Offset, section.Length, section.Color); - } + this.stateChangeOffsets.AddRange(stateChangeOffsets); + this.stateChanges.AddRange(stateChanges); } #region UpdateOffsets /// /// Updates the start and end offsets of all segments stored in this collection. /// - /// DocumentChangeEventArgs instance describing the change to the document. - public void UpdateOffsets(DocumentChangeEventArgs e) + /// TextChangeEventArgs instance describing the change to the document. + public void UpdateOffsets(TextChangeEventArgs e) { if (e == null) throw new ArgumentNullException("e"); - OffsetChangeMap map = e.OffsetChangeMapOrNull; - if (map != null) { - foreach (OffsetChangeMapEntry entry in map) { - UpdateOffsetsInternal(entry); - } - } else { - UpdateOffsetsInternal(e.CreateSingleChangeMapEntry()); + for (int i = 0; i < stateChangeOffsets.Count; i++) { + stateChangeOffsets[i] = e.GetNewOffset(stateChangeOffsets[i]); } } @@ -69,30 +96,18 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// OffsetChangeMapEntry instance describing the change to the document. public void UpdateOffsets(OffsetChangeMapEntry change) { - UpdateOffsetsInternal(change); - } - - void UpdateOffsetsInternal(OffsetChangeMapEntry entry) - { - HighlightingColor color; - if (entry.RemovalLength > 0) { - color = list[entry.Offset]; - list.RemoveRange(entry.Offset, entry.RemovalLength); - } else if (list.Count > 0) { - color = list[Math.Max(0, entry.Offset - 1)]; - } else { - color = HighlightingColor.Empty; + for (int i = 0; i < stateChangeOffsets.Count; i++) { + stateChangeOffsets[i] = change.GetNewOffset(stateChangeOffsets[i]); } - list.InsertRange(entry.Offset, entry.InsertionLength, color); } #endregion /// - /// Gets the HighlightingColor for the specified offset. + /// Gets a copy of the HighlightingColor for the specified offset. /// public HighlightingColor GetHighlightingAt(int offset) { - return list[offset]; + return stateChanges[GetIndexForOffsetUseExistingSegment(offset)].Clone(); } /// @@ -101,12 +116,17 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// public void ApplyHighlighting(int offset, int length, HighlightingColor color) { - list.TransformRange(offset, length, c => { - var newColor = c.Clone(); - newColor.MergeWith(color); - newColor.Freeze(); - return newColor; - }); + if (color == null || color.IsEmptyForMerge) { + // Optimization: don't split the HighlightingState when we're not changing + // any property. For example, the "Punctuation" color in C# is + // empty by default. + return; + } + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].MergeWith(color); + } } /// @@ -115,61 +135,82 @@ namespace ICSharpCode.AvalonEdit.Highlighting /// public void SetHighlighting(int offset, int length, HighlightingColor color) { - list.SetRange(offset, length, FreezableHelper.GetFrozenClone(color)); + if (length <= 0) + return; + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + stateChanges[startIndex] = color != null ? color.Clone() : new HighlightingColor(); + stateChanges.RemoveRange(startIndex + 1, endIndex - (startIndex + 1)); + stateChangeOffsets.RemoveRange(startIndex + 1, endIndex - (startIndex + 1)); } /// - /// Retrieves the highlighted sections in the specified range. - /// The highlighted sections will be sorted by offset, and there will not be any nested or overlapping sections. + /// Sets the foreground brush on the specified text segment. /// - public IEnumerable GetHighlightedSections(int offset, int length) + public void SetForeground(int offset, int length, HighlightingBrush brush) { - int pos = offset; - int endOffset = offset + length; - while (pos < endOffset) { - int endPos = Math.Min(endOffset, list.GetEndOfRun(pos)); - yield return new HighlightedSection { - Offset = pos, - Length = endPos - pos, - Color = list[pos] - }; - pos = endPos; + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Foreground = brush; } } - #region WriteDocumentTo /// - /// Writes the specified document, with the formatting from this rich text model applied, - /// to the RichTextWriter. + /// Sets the background brush on the specified text segment. /// - public void WriteDocumentTo(ITextSource document, RichTextWriter writer) + public void SetBackground(int offset, int length, HighlightingBrush brush) { - WriteDocumentTo(document, new SimpleSegment(0, DocumentLength), writer); + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].Background = brush; + } } /// - /// Writes a segment of the specified document, with the formatting from this rich text model applied, - /// to the RichTextWriter. + /// Sets the font weight on the specified text segment. /// - public void WriteDocumentTo(ITextSource document, ISegment segment, RichTextWriter writer) + public void SetFontWeight(int offset, int length, FontWeight weight) { - if (document == null) - throw new ArgumentNullException("document"); - if (segment == null) - throw new ArgumentNullException("segment"); - if (writer == null) - throw new ArgumentNullException("writer"); - - int pos = segment.Offset; - int endOffset = segment.EndOffset; + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].FontWeight = weight; + } + } + + /// + /// Sets the font style on the specified text segment. + /// + public void SetFontStyle(int offset, int length, FontStyle style) + { + int startIndex = GetIndexForOffset(offset); + int endIndex = GetIndexForOffset(offset + length); + for (int i = startIndex; i < endIndex; i++) { + stateChanges[i].FontStyle = style; + } + } + + /// + /// Retrieves the highlighted sections in the specified range. + /// The highlighted sections will be sorted by offset, and there will not be any nested or overlapping sections. + /// + public IEnumerable GetHighlightedSections(int offset, int length) + { + int index = GetIndexForOffsetUseExistingSegment(offset); + int pos = offset; + int endOffset = offset + length; while (pos < endOffset) { - int endPos = Math.Min(endOffset, list.GetEndOfRun(pos)); - writer.BeginSpan(list[pos]); - document.WriteTextTo(writer, pos, endPos - pos); - writer.EndSpan(); + int endPos = Math.Min(endOffset, GetEnd(index)); + yield return new HighlightedSection { + Offset = pos, + Length = endPos - pos, + Color = stateChanges[index].Clone() + }; pos = endPos; + index++; } } - #endregion } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModelWriter.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModelWriter.cs index a1abc2aa97..777fea51e1 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModelWriter.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/RichTextModelWriter.cs @@ -34,10 +34,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting throw new ArgumentNullException("richTextModel"); this.richTextModel = richTextModel; this.documentTextWriter = (DocumentTextWriter)base.textWriter; - if (richTextModel.DocumentLength == 0) - currentColor = HighlightingColor.Empty; - else - currentColor = richTextModel.GetHighlightingAt(Math.Max(0, insertionOffset - 1)); + currentColor = richTextModel.GetHighlightingAt(Math.Max(0, insertionOffset - 1)); } /// diff --git a/src/Main/Base/Project/Editor/Search/SearchResultsPad.cs b/src/Main/Base/Project/Editor/Search/SearchResultsPad.cs index ee52b1a165..59c2002fee 100644 --- a/src/Main/Base/Project/Editor/Search/SearchResultsPad.cs +++ b/src/Main/Base/Project/Editor/Search/SearchResultsPad.cs @@ -152,21 +152,23 @@ namespace ICSharpCode.SharpDevelop.Editor.Search public static RichText CreateInlineBuilder(TextLocation startPosition, TextLocation endPosition, IDocument document, IHighlighter highlighter) { if (startPosition.Line >= 1 && startPosition.Line <= document.LineCount) { - var inlineBuilder = highlighter.HighlightLine(startPosition.Line).ToInlineBuilder(); + var highlightedLine = highlighter.HighlightLine(startPosition.Line); + var documentLine = highlightedLine.DocumentLine; + var inlineBuilder = highlightedLine.ToRichTextModel(); // reset bold/italics - inlineBuilder.SetFontWeight(0, inlineBuilder.Text.Length, FontWeights.Normal); - inlineBuilder.SetFontStyle(0, inlineBuilder.Text.Length, FontStyles.Normal); + inlineBuilder.SetFontWeight(0, documentLine.Length, FontWeights.Normal); + inlineBuilder.SetFontStyle(0, documentLine.Length, FontStyles.Normal); // now highlight the match in bold if (startPosition.Column >= 1) { if (endPosition.Line == startPosition.Line && endPosition.Column > startPosition.Column) { // subtract one from the column to get the offset inside the line's text int startOffset = startPosition.Column - 1; - int endOffset = Math.Min(inlineBuilder.Text.Length, endPosition.Column - 1); + int endOffset = Math.Min(documentLine.Length, endPosition.Column - 1); inlineBuilder.SetFontWeight(startOffset, endOffset - startOffset, FontWeights.Bold); } } - return inlineBuilder.ToRichText(); + return new RichText(document.GetText(documentLine), inlineBuilder); } return null; }