Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2929_PrioritizedGrammarEnumeration/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Highlighting/RichTextModel.cs @ 16147

Last change on this file since 16147 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 10.1 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Diagnostics;
22using System.Linq;
23using System.Windows;
24using System.Windows.Documents;
25using ICSharpCode.AvalonEdit.Document;
26using ICSharpCode.NRefactory.Editor;
27
28namespace ICSharpCode.AvalonEdit.Highlighting
29{
30  /// <summary>
31  /// Stores rich-text formatting.
32  /// </summary>
33  public sealed class RichTextModel
34  {
35    List<int> stateChangeOffsets = new List<int>();
36    List<HighlightingColor> stateChanges = new List<HighlightingColor>();
37   
38    int GetIndexForOffset(int offset)
39    {
40      if (offset < 0)
41        throw new ArgumentOutOfRangeException("offset");
42      int index = stateChangeOffsets.BinarySearch(offset);
43      if (index < 0) {
44        // If no color change exists directly at offset,
45        // create a new one.
46        index = ~index;
47        stateChanges.Insert(index, stateChanges[index - 1].Clone());
48        stateChangeOffsets.Insert(index, offset);
49      }
50      return index;
51    }
52   
53    int GetIndexForOffsetUseExistingSegment(int offset)
54    {
55      if (offset < 0)
56        throw new ArgumentOutOfRangeException("offset");
57      int index = stateChangeOffsets.BinarySearch(offset);
58      if (index < 0) {
59        // If no color change exists directly at offset,
60        // return the index of the color segment that contains offset.
61        index = ~index - 1;
62      }
63      return index;
64    }
65   
66    int GetEnd(int index)
67    {
68      // Gets the end of the color segment no. index.
69      if (index + 1 < stateChangeOffsets.Count)
70        return stateChangeOffsets[index + 1];
71      else
72        return int.MaxValue;
73    }
74   
75    /// <summary>
76    /// Creates a new RichTextModel.
77    /// </summary>
78    public RichTextModel()
79    {
80      stateChangeOffsets.Add(0);
81      stateChanges.Add(new HighlightingColor());
82    }
83   
84    /// <summary>
85    /// Creates a RichTextModel from a CONTIGUOUS list of HighlightedSections.
86    /// </summary>
87    internal RichTextModel(int[] stateChangeOffsets, HighlightingColor[] stateChanges)
88    {
89      Debug.Assert(stateChangeOffsets[0] == 0);
90      this.stateChangeOffsets.AddRange(stateChangeOffsets);
91      this.stateChanges.AddRange(stateChanges);
92    }
93   
94    #region UpdateOffsets
95    /// <summary>
96    /// Updates the start and end offsets of all segments stored in this collection.
97    /// </summary>
98    /// <param name="e">TextChangeEventArgs instance describing the change to the document.</param>
99    public void UpdateOffsets(TextChangeEventArgs e)
100    {
101      if (e == null)
102        throw new ArgumentNullException("e");
103      UpdateOffsets(e.GetNewOffset);
104    }
105   
106    /// <summary>
107    /// Updates the start and end offsets of all segments stored in this collection.
108    /// </summary>
109    /// <param name="change">OffsetChangeMap instance describing the change to the document.</param>
110    public void UpdateOffsets(OffsetChangeMap change)
111    {
112      if (change == null)
113        throw new ArgumentNullException("change");
114      UpdateOffsets(change.GetNewOffset);
115    }
116   
117    /// <summary>
118    /// Updates the start and end offsets of all segments stored in this collection.
119    /// </summary>
120    /// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
121    public void UpdateOffsets(OffsetChangeMapEntry change)
122    {
123      UpdateOffsets(change.GetNewOffset);
124    }
125   
126    void UpdateOffsets(Func<int, AnchorMovementType, int> updateOffset)
127    {
128      int readPos = 1;
129      int writePos = 1;
130      while (readPos < stateChangeOffsets.Count) {
131        Debug.Assert(writePos <= readPos);
132        int newOffset = updateOffset(stateChangeOffsets[readPos], AnchorMovementType.Default);
133        if (newOffset == stateChangeOffsets[writePos - 1]) {
134          // offset moved to same position as previous offset
135          // -> previous segment has length 0 and gets overwritten with this segment
136          stateChanges[writePos - 1] = stateChanges[readPos];
137        } else {
138          stateChangeOffsets[writePos] = newOffset;
139          stateChanges[writePos] = stateChanges[readPos];
140          writePos++;
141        }
142        readPos++;
143      }
144      // Delete all entries that were not written to
145      stateChangeOffsets.RemoveRange(writePos, stateChangeOffsets.Count - writePos);
146      stateChanges.RemoveRange(writePos, stateChanges.Count - writePos);
147    }
148    #endregion
149   
150    /// <summary>
151    /// Appends another RichTextModel after this one.
152    /// </summary>
153    internal void Append(int offset, int[] newOffsets, HighlightingColor[] newColors)
154    {
155      Debug.Assert(newOffsets.Length == newColors.Length);
156      Debug.Assert(newOffsets[0] == 0);
157      // remove everything before offset:
158      while (stateChangeOffsets.Count > 0 && stateChangeOffsets.Last() <= offset) {
159        stateChangeOffsets.RemoveAt(stateChangeOffsets.Count - 1);
160        stateChanges.RemoveAt(stateChanges.Count - 1);
161      }
162      // Append the new segments
163      for (int i = 0; i < newOffsets.Length; i++) {
164        stateChangeOffsets.Add(offset + newOffsets[i]);
165        stateChanges.Add(newColors[i]);
166      }
167    }
168   
169    /// <summary>
170    /// Gets a copy of the HighlightingColor for the specified offset.
171    /// </summary>
172    public HighlightingColor GetHighlightingAt(int offset)
173    {
174      return stateChanges[GetIndexForOffsetUseExistingSegment(offset)].Clone();
175    }
176   
177    /// <summary>
178    /// Applies the HighlightingColor to the specified range of text.
179    /// If the color specifies <c>null</c> for some properties, existing highlighting is preserved.
180    /// </summary>
181    public void ApplyHighlighting(int offset, int length, HighlightingColor color)
182    {
183      if (color == null || color.IsEmptyForMerge) {
184        // Optimization: don't split the HighlightingState when we're not changing
185        // any property. For example, the "Punctuation" color in C# is
186        // empty by default.
187        return;
188      }
189      int startIndex = GetIndexForOffset(offset);
190      int endIndex = GetIndexForOffset(offset + length);
191      for (int i = startIndex; i < endIndex; i++) {
192        stateChanges[i].MergeWith(color);
193      }
194    }
195   
196    /// <summary>
197    /// Sets the HighlightingColor for the specified range of text,
198    /// completely replacing the existing highlighting in that area.
199    /// </summary>
200    public void SetHighlighting(int offset, int length, HighlightingColor color)
201    {
202      if (length <= 0)
203        return;
204      int startIndex = GetIndexForOffset(offset);
205      int endIndex = GetIndexForOffset(offset + length);
206      stateChanges[startIndex] = color != null ? color.Clone() : new HighlightingColor();
207      stateChanges.RemoveRange(startIndex + 1, endIndex - (startIndex + 1));
208      stateChangeOffsets.RemoveRange(startIndex + 1, endIndex - (startIndex + 1));
209    }
210   
211    /// <summary>
212    /// Sets the foreground brush on the specified text segment.
213    /// </summary>
214    public void SetForeground(int offset, int length, HighlightingBrush brush)
215    {
216      int startIndex = GetIndexForOffset(offset);
217      int endIndex = GetIndexForOffset(offset + length);
218      for (int i = startIndex; i < endIndex; i++) {
219        stateChanges[i].Foreground = brush;
220      }
221    }
222   
223    /// <summary>
224    /// Sets the background brush on the specified text segment.
225    /// </summary>
226    public void SetBackground(int offset, int length, HighlightingBrush brush)
227    {
228      int startIndex = GetIndexForOffset(offset);
229      int endIndex = GetIndexForOffset(offset + length);
230      for (int i = startIndex; i < endIndex; i++) {
231        stateChanges[i].Background = brush;
232      }
233    }
234   
235    /// <summary>
236    /// Sets the font weight on the specified text segment.
237    /// </summary>
238    public void SetFontWeight(int offset, int length, FontWeight weight)
239    {
240      int startIndex = GetIndexForOffset(offset);
241      int endIndex = GetIndexForOffset(offset + length);
242      for (int i = startIndex; i < endIndex; i++) {
243        stateChanges[i].FontWeight = weight;
244      }
245    }
246   
247    /// <summary>
248    /// Sets the font style on the specified text segment.
249    /// </summary>
250    public void SetFontStyle(int offset, int length, FontStyle style)
251    {
252      int startIndex = GetIndexForOffset(offset);
253      int endIndex = GetIndexForOffset(offset + length);
254      for (int i = startIndex; i < endIndex; i++) {
255        stateChanges[i].FontStyle = style;
256      }
257    }
258   
259    /// <summary>
260    /// Retrieves the highlighted sections in the specified range.
261    /// The highlighted sections will be sorted by offset, and there will not be any nested or overlapping sections.
262    /// </summary>
263    public IEnumerable<HighlightedSection> GetHighlightedSections(int offset, int length)
264    {
265      int index = GetIndexForOffsetUseExistingSegment(offset);
266      int pos = offset;
267      int endOffset = offset + length;
268      while (pos < endOffset) {
269        int endPos = Math.Min(endOffset, GetEnd(index));
270        yield return new HighlightedSection {
271          Offset = pos,
272          Length = endPos - pos,
273          Color = stateChanges[index].Clone()
274        };
275        pos = endPos;
276        index++;
277      }
278    }
279   
280    /// <summary>
281    /// Creates WPF Run instances that can be used for TextBlock.Inlines.
282    /// </summary>
283    /// <param name="textSource">The text source that holds the text for this RichTextModel.</param>
284    public Run[] CreateRuns(ITextSource textSource)
285    {
286      Run[] runs = new Run[stateChanges.Count];
287      for (int i = 0; i < runs.Length; i++) {
288        int startOffset = stateChangeOffsets[i];
289        int endOffset = i + 1 < stateChangeOffsets.Count ? stateChangeOffsets[i + 1] : textSource.TextLength;
290        Run r = new Run(textSource.GetText(startOffset, endOffset - startOffset));
291        HighlightingColor state = stateChanges[i];
292        RichText.ApplyColorToTextElement(r, state);
293        runs[i] = r;
294      }
295      return runs;
296    }
297  }
298}
Note: See TracBrowser for help on using the repository browser.