diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
index e98cd8b964..62eab02257 100644
--- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
+++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
@@ -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;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Editor.AvalonEdit;
using ICSharpCode.SharpDevelop.Editor.CodeCompletion;
-using ICSharpCode.SharpDevelop.Editor.Commands;
namespace ICSharpCode.AvalonEdit.AddIn
{
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
index 9140ac8fb1..c131d49962 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
@@ -60,7 +60,6 @@ namespace ICSharpCode.AvalonEdit.Highlighting
{
CheckIsHighlighting();
int number = line.LineNumber;
- InvalidateHighlighting();
storedSpanStacks.RemoveAt(number);
isValid.RemoveAt(number);
if (number < isValid.Count) {
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
index e37c2bc197..870535a262 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
@@ -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
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);
+ }
}
///
@@ -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
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);
}
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
index 9e7ff60191..81021b407e 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
@@ -262,6 +262,7 @@
TextView.cs
+
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
index 1c232ca76e..496910faee 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
@@ -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)) {
- removedLine = true;
- allVisualLines.RemoveAt(i--);
- DisposeVisualLine(visualLine);
+ 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
///
/// 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
+ /// if this property is false.
///
public bool VisualLinesValid {
get { return visibleVisualLines != null; }
}
+ ///
+ /// 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.
+ ///
+ public event EventHandler VisualLineConstructionStarting;
+
///
/// Occurs when the TextView was measured and changed its visual lines.
///
@@ -695,6 +711,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
newVisualLines = new List();
+ if (VisualLineConstructionStarting != null)
+ VisualLineConstructionStarting(this, new VisualLineConstructionStartEventArgs(firstLineInView));
+
var elementGeneratorsArray = elementGenerators.ToArray();
var lineTransformersArray = lineTransformers.ToArray();
var nextLine = firstLineInView;
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineConstructionStartEventArgs.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineConstructionStartEventArgs.cs
new file mode 100644
index 0000000000..4bd6fc05d4
--- /dev/null
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineConstructionStartEventArgs.cs
@@ -0,0 +1,33 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using ICSharpCode.AvalonEdit.Document;
+
+namespace ICSharpCode.AvalonEdit.Rendering
+{
+ ///
+ /// EventArgs for the event.
+ ///
+ public class VisualLineConstructionStartEventArgs : EventArgs
+ {
+ ///
+ /// Gets/Sets the first line that is visible in the TextView.
+ ///
+ public DocumentLine FirstLineInView { get; private set; }
+
+ ///
+ /// Creates a new VisualLineConstructionStartEventArgs instance.
+ ///
+ public VisualLineConstructionStartEventArgs(DocumentLine firstLineInView)
+ {
+ if (firstLineInView == null)
+ throw new ArgumentNullException("firstLineInView");
+ this.FirstLineInView = firstLineInView;
+ }
+ }
+}