Browse Source

AvalonEdit: Fixed repainting issues when document was modified above the visible region (e.g. using split view) and the highlighting stack changed.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5584 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
pull/1/head
Daniel Grunwald 16 years ago
parent
commit
7d3a410d0b
  1. 5
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
  2. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
  3. 78
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
  4. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  5. 25
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  6. 33
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineConstructionStartEventArgs.cs

5
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs

@ -7,23 +7,19 @@ @@ -7,23 +7,19 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.AddIn.Options;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Indentation;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.Core;
using ICSharpCode.Core.Presentation;
@ -33,7 +29,6 @@ using ICSharpCode.SharpDevelop.Dom; @@ -33,7 +29,6 @@ using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Editor.AvalonEdit;
using ICSharpCode.SharpDevelop.Editor.CodeCompletion;
using ICSharpCode.SharpDevelop.Editor.Commands;
namespace ICSharpCode.AvalonEdit.AddIn
{

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs

@ -60,7 +60,6 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -60,7 +60,6 @@ namespace ICSharpCode.AvalonEdit.Highlighting
{
CheckIsHighlighting();
int number = line.LineNumber;
InvalidateHighlighting();
storedSpanStacks.RemoveAt(number);
isValid.RemoveAt(number);
if (number < isValid.Count) {

78
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs

@ -80,6 +80,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -80,6 +80,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
{
base.OnAddToTextView(textView);
textView.DocumentChanged += textView_DocumentChanged;
textView.VisualLineConstructionStarting += textView_VisualLineConstructionStarting;
OnDocumentChanged(textView);
}
@ -90,6 +91,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -90,6 +91,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting
textView.Services.RemoveService(typeof(IHighlighter));
textView.Services.RemoveService(typeof(DocumentHighlighter));
textView.DocumentChanged -= textView_DocumentChanged;
textView.VisualLineConstructionStarting -= textView_VisualLineConstructionStarting;
}
void textView_VisualLineConstructionStarting(object sender, VisualLineConstructionStartEventArgs e)
{
IHighlighter highlighter = ((TextView)sender).Services.GetService(typeof(IHighlighter)) as IHighlighter;
if (highlighter != null) {
// Force update of highlighting state up to the position where we start generating visual lines.
// This is necessary in case the document gets modified above the FirstLineInView so that the highlighting state changes.
// We need to detect this case and issue a redraw (through TextViewDocumentHighligher.OnHighlightStateChanged)
// before the visual line construction reuses existing lines that were built using the invalid highlighting state.
highlighter.GetSpanStack(e.FirstLineInView.LineNumber - 1);
}
}
/// <inheritdoc/>
@ -128,7 +142,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -128,7 +142,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
sealed class TextViewDocumentHighlighter : DocumentHighlighter
{
TextView textView;
readonly TextView textView;
public TextViewDocumentHighlighter(TextView textView, TextDocument document, HighlightingRuleSet baseRuleSet)
: base(document, baseRuleSet)
@ -140,18 +154,58 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -140,18 +154,58 @@ namespace ICSharpCode.AvalonEdit.Highlighting
protected override void OnHighlightStateChanged(DocumentLine line, int lineNumber)
{
base.OnHighlightStateChanged(line, lineNumber);
int lineEndOffset = line.Offset + line.TotalLength;
if (lineEndOffset >= 0) {
// Do not use colorizer.CurrentContext - the colorizer might not be the only
// class calling DocumentHighlighter.HighlightLine, the the context might be null.
int length = this.Document.TextLength - lineEndOffset;
if (length != 0) {
// don't redraw if length == 0: at the end of the document, this would cause
// the last line which was already constructed to be redrawn ->
// we would get an exception due to disposing the line that was already constructed
textView.Redraw(lineEndOffset, length, DispatcherPriority.Normal);
}
if (textView.Document != this.Document) {
// May happen if document on text view was changed but some user code is still using the
// existing IHighlighter instance.
return;
}
// The user may have inserted "/*" into the current line, and so far only that line got redrawn.
// So when the highlighting state is changed, we issue a redraw for the line immediately below.
// If the highlighting state change applies to the lines below, too, the construction of each line
// will invalidate the next line, and the construction pass will regenerate all lines.
Debug.WriteLine("OnHighlightStateChanged forces redraw of line " + (lineNumber + 1));
// If the VisualLine construction is in progress, we have to avoid sending redraw commands for
// anything above the line currently being constructed.
// It takes some explanation to see why this cannot happen.
// VisualLines always get constructed from top to bottom.
// Each VisualLine construction calls into the highlighter and thus forces an update of the
// highlighting state for all lines up to the one being constructed.
// To guarantee that we don't redraw lines we just constructed, we need to show that when
// a VisualLine is being reused, the highlighting state at that location is still up-to-date.
// This isn't exactly trivial and the initial implementation was incorrect in the presence of external document changes
// (e.g. split view).
// For the first line in the view, the TextView.VisualLineConstructionStarting event is used to check that the
// highlighting state is up-to-date. If it isn't, this method will be executed, and it'll mark the first line
// in the view as requiring a redraw. This is safely possible because that event occurs before any lines are reused.
// Once we take care of the first visual line, we won't get in trouble with other lines due to the top-to-bottom
// construction process.
// We'll prove that: if line N is being reused, then the highlighting state is up-to-date until (end of) line N-1.
// Start of induction: the first line in view can is reused only if the highlighting state was up-to-date
// until line N-1 (no change detected in VisualLineConstructionStarting event).
// Induction step:
// If another line N+1 is being reused, then either
// a) the previous line (the visual line containing document line N) was newly constructed
// or b) the previous line was reused
// In case a, the construction updated the highlighting state. This means the stack at end of line N is up-to-date.
// In case b, the highlighting state at N-1 was up-to-date, and the text of line N was not changed.
// (if the text was changed, the line could not have been reused).
// From this follows that the highlighting state at N is still up-to-date.
// The above proof holds even in the presence of folding: folding only ever hides text in the middle of a visual line.
// The HighlightingColorizer will always be asked to highlight the LastDocumentLine of a visual line, so it will always
// invalidate the next visual line when a folded line is constructed and the highlighting stack changed.
textView.Redraw(line.NextLine, DispatcherPriority.Normal);
}
}
}

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -262,6 +262,7 @@ @@ -262,6 +262,7 @@
<DependentUpon>TextView.cs</DependentUpon>
</Compile>
<Compile Include="Rendering\VisualLine.cs" />
<Compile Include="Rendering\VisualLineConstructionStartEventArgs.cs" />
<Compile Include="Rendering\VisualLineElement.cs" />
<Compile Include="Rendering\VisualLineElementGenerator.cs" />
<Compile Include="Rendering\VisualLineElementTextRunProperties.cs">

25
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -398,18 +398,27 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -398,18 +398,27 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
VerifyAccess();
bool removedLine = false;
bool changedSomethingBeforeOrInLine = false;
for (int i = 0; i < allVisualLines.Count; i++) {
VisualLine visualLine = allVisualLines[i];
int lineStart = visualLine.FirstDocumentLine.Offset;
int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength;
if (!(lineEnd < offset || lineStart > offset + length)) {
if (offset <= lineEnd) {
changedSomethingBeforeOrInLine = true;
if (offset + length >= lineStart) {
removedLine = true;
allVisualLines.RemoveAt(i--);
DisposeVisualLine(visualLine);
}
}
}
if (removedLine) {
visibleVisualLines = null;
}
if (changedSomethingBeforeOrInLine) {
// Repaint not only when something in visible area was changed, but also when anything in front of it
// was changed. We might have to redraw the line number margin. Or the highlighting changed.
// However, we'll try to reuse the existing VisualLines.
InvalidateMeasure(redrawPriority);
}
}
@ -571,13 +580,20 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -571,13 +580,20 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// <summary>
/// Gets whether the visual lines are valid.
/// Will return false after a call to Redraw(). Accessing the visual lines property
/// will force immediate regeneration of valid lines.
/// Will return false after a call to Redraw().
/// Accessing the visual lines property will cause a <see cref="VisualLinesInvalidException"/>
/// if this property is <c>false</c>.
/// </summary>
public bool VisualLinesValid {
get { return visibleVisualLines != null; }
}
/// <summary>
/// Occurs when the TextView is about to be measured and will regenerate its visual lines.
/// This event may be used to mark visual lines as invalid that would otherwise be reused.
/// </summary>
public event EventHandler<VisualLineConstructionStartEventArgs> VisualLineConstructionStarting;
/// <summary>
/// Occurs when the TextView was measured and changed its visual lines.
/// </summary>
@ -695,6 +711,9 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -695,6 +711,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
newVisualLines = new List<VisualLine>();
if (VisualLineConstructionStarting != null)
VisualLineConstructionStarting(this, new VisualLineConstructionStartEventArgs(firstLineInView));
var elementGeneratorsArray = elementGenerators.ToArray();
var lineTransformersArray = lineTransformers.ToArray();
var nextLine = firstLineInView;

33
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineConstructionStartEventArgs.cs

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Rendering
{
/// <summary>
/// EventArgs for the <see cref="TextView.VisualLineConstructionStarting"/> event.
/// </summary>
public class VisualLineConstructionStartEventArgs : EventArgs
{
/// <summary>
/// Gets/Sets the first line that is visible in the TextView.
/// </summary>
public DocumentLine FirstLineInView { get; private set; }
/// <summary>
/// Creates a new VisualLineConstructionStartEventArgs instance.
/// </summary>
public VisualLineConstructionStartEventArgs(DocumentLine firstLineInView)
{
if (firstLineInView == null)
throw new ArgumentNullException("firstLineInView");
this.FirstLineInView = firstLineInView;
}
}
}
Loading…
Cancel
Save