Browse Source

Use BeginHighlighting()/EndHighlighting() to improve performance of CSharpSemanticHighlighter.

newNRvisualizers
Daniel Grunwald 14 years ago
parent
commit
c3bd806532
  1. 118
      src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpSemanticHighlighter.cs
  2. 2
      src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/CSharpSymbolSearch.cs
  3. 2
      src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/Parser.cs
  4. 5
      src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesCommand.cs
  5. 2
      src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlSymbolSearch.cs
  6. 2
      src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs
  7. 19
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
  8. 40
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
  9. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/IHighlighter.cs
  10. 2
      src/Main/Base/Project/Src/Services/RefactoringService/FindReferencesAndRenameHelper.cs

118
src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpSemanticHighlighter.cs

@ -7,6 +7,7 @@ using System.Diagnostics; @@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using System.Windows.Threading;
using CSharpBinding.Parser;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.Core;
@ -38,14 +39,17 @@ namespace CSharpBinding @@ -38,14 +39,17 @@ namespace CSharpBinding
readonly HighlightingColor parameterModifierColor;
readonly HighlightingColor inactiveCodeColor;
List<IDocumentLine> invalidLines = new List<IDocumentLine>();
List<CachedLine> cachedLines = new List<CachedLine>();
List<IDocumentLine> invalidLines;
List<CachedLine> cachedLines;
bool hasCrashed;
bool forceParseOnNextRefresh;
bool eventHandlersAreRegistered;
bool inHighlightingGroup;
int lineNumber;
HighlightedLine line;
CSharpAstResolver resolver;
CSharpFullParseInformation parseInfo;
bool isInAccessor;
@ -66,14 +70,30 @@ namespace CSharpBinding @@ -66,14 +70,30 @@ namespace CSharpBinding
this.parameterModifierColor = highlighting.GetNamedColor("ParameterModifiers");
this.inactiveCodeColor = highlighting.GetNamedColor("InactiveCode");
SD.ParserService.ParseInformationUpdated += ParserService_ParseInformationUpdated;
SD.ParserService.LoadSolutionProjectsThread.Finished += ParserService_LoadSolutionProjectsThreadEnded;
if (document is TextDocument && SD.MainThread.CheckAccess()) {
// Use the cache only for the live AvalonEdit document
// Highlighting in read-only documents (e.g. search results) does
// not need the cache as it does not need to highlight the same line multiple times
cachedLines = new List<CachedLine>();
// Line invalidation is only necessary for the live AvalonEdit document
invalidLines = new List<IDocumentLine>();
// Also, attach these event handlers only for real documents in the editor,
// we don't need them for the highlighting in search results etc.
SD.ParserService.ParseInformationUpdated += ParserService_ParseInformationUpdated;
SD.ParserService.LoadSolutionProjectsThread.Finished += ParserService_LoadSolutionProjectsThreadEnded;
eventHandlersAreRegistered = true;
}
}
public void Dispose()
{
SD.ParserService.ParseInformationUpdated -= ParserService_ParseInformationUpdated;
SD.ParserService.LoadSolutionProjectsThread.Finished -= ParserService_LoadSolutionProjectsThreadEnded;
if (eventHandlersAreRegistered) {
SD.ParserService.ParseInformationUpdated -= ParserService_ParseInformationUpdated;
SD.ParserService.LoadSolutionProjectsThread.Finished -= ParserService_LoadSolutionProjectsThreadEnded;
eventHandlersAreRegistered = false;
}
this.resolver = null;
this.parseInfo = null;
}
#endregion
@ -196,34 +216,53 @@ namespace CSharpBinding @@ -196,34 +216,53 @@ namespace CSharpBinding
}
ITextSourceVersion newVersion = document.Version;
CachedLine cachedLine = null;
for (int i = 0; i < cachedLines.Count; i++) {
if (cachedLines[i].DocumentLine == documentLine) {
if (newVersion == null || !newVersion.BelongsToSameDocumentAs(cachedLines[i].OldVersion)) {
// cannot list changes from old to new: we can't update the cache, so we'll remove it
cachedLines.RemoveAt(i);
} else {
cachedLine = cachedLines[i];
if (cachedLines != null) {
for (int i = 0; i < cachedLines.Count; i++) {
if (cachedLines[i].DocumentLine == documentLine) {
if (newVersion == null || !newVersion.BelongsToSameDocumentAs(cachedLines[i].OldVersion)) {
// cannot list changes from old to new: we can't update the cache, so we'll remove it
cachedLines.RemoveAt(i);
} else {
cachedLine = cachedLines[i];
}
break;
}
break;
}
if (cachedLine != null && cachedLine.IsValid && newVersion.CompareAge(cachedLine.OldVersion) == 0) {
// the file hasn't changed since the cache was created, so just reuse the old highlighted line
return cachedLine.HighlightedLine;
}
}
if (cachedLine != null && cachedLine.IsValid && newVersion.CompareAge(cachedLine.OldVersion) == 0) {
// the file hasn't changed since the cache was created, so just reuse the old highlighted line
return cachedLine.HighlightedLine;
bool wasInHighlightingGroup = inHighlightingGroup;
if (!inHighlightingGroup) {
BeginHighlighting();
}
CSharpFullParseInformation parseInfo;
if (forceParseOnNextRefresh) {
forceParseOnNextRefresh = false;
parseInfo = SD.ParserService.Parse(FileName.Create(document.FileName), document) as CSharpFullParseInformation;
} else {
parseInfo = SD.ParserService.GetCachedParseInformation(FileName.Create(document.FileName), document.Version) as CSharpFullParseInformation;
try {
return DoHighlightLine(lineNumber, documentLine, cachedLine, newVersion);
} finally {
line = null;
if (!wasInHighlightingGroup)
EndHighlighting();
}
}
HighlightedLine DoHighlightLine(int lineNumber, IDocumentLine documentLine, CachedLine cachedLine, ITextSourceVersion newVersion)
{
if (parseInfo == null) {
if (forceParseOnNextRefresh) {
forceParseOnNextRefresh = false;
parseInfo = SD.ParserService.Parse(FileName.Create(document.FileName), document) as CSharpFullParseInformation;
} else {
parseInfo = SD.ParserService.GetCachedParseInformation(FileName.Create(document.FileName), newVersion) as CSharpFullParseInformation;
}
}
if (parseInfo == null) {
if (!invalidLines.Contains(documentLine))
if (invalidLines != null && !invalidLines.Contains(documentLine)) {
invalidLines.Add(documentLine);
Debug.WriteLine("Semantic highlighting for line {0} - marking as invalid", lineNumber);
Debug.WriteLine("Semantic highlighting for line {0} - marking as invalid", lineNumber);
}
if (cachedLine != null) {
// If there's a cached version, adjust it to the latest document changes and return it.
@ -235,11 +274,12 @@ namespace CSharpBinding @@ -235,11 +274,12 @@ namespace CSharpBinding
}
}
var compilation = SD.ParserService.GetCompilationForFile(parseInfo.FileName);
this.resolver = parseInfo.GetResolver(compilation);
if (resolver == null) {
var compilation = SD.ParserService.GetCompilationForFile(parseInfo.FileName);
resolver = parseInfo.GetResolver(compilation);
}
HighlightedLine line = new HighlightedLine(document, documentLine);
this.line = line;
line = new HighlightedLine(document, documentLine);
this.lineNumber = lineNumber;
if (Debugger.IsAttached) {
parseInfo.SyntaxTree.AcceptVisitor(this);
@ -251,10 +291,8 @@ namespace CSharpBinding @@ -251,10 +291,8 @@ namespace CSharpBinding
throw new ApplicationException("Error highlighting line " + lineNumber, ex);
}
}
this.line = null;
this.resolver = null;
//Debug.WriteLine("Semantic highlighting for line {0} - added {1} sections", lineNumber, line.Sections.Count);
if (document.Version != null) {
if (cachedLines != null && document.Version != null) {
cachedLines.Add(new CachedLine(line, document.Version));
}
return line;
@ -268,12 +306,22 @@ namespace CSharpBinding @@ -268,12 +306,22 @@ namespace CSharpBinding
public void BeginHighlighting()
{
if (inHighlightingGroup)
throw new InvalidOperationException();
inHighlightingGroup = true;
if (invalidLines == null) {
// if invalidation isn't available, we're forced to parse the file now
forceParseOnNextRefresh = true;
}
}
public void EndHighlighting()
{
// use this event to remove cached lines which are no longer visible
inHighlightingGroup = false;
this.resolver = null;
this.parseInfo = null;
// TODO use this to remove cached lines which are no longer visible
// var visibleDocumentLines = new HashSet<IDocumentLine>(syntaxHighlighter.GetVisibleDocumentLines());
// cachedLines.RemoveAll(c => !visibleDocumentLines.Contains(c.DocumentLine));
}

2
src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/CSharpSymbolSearch.cs

@ -106,6 +106,8 @@ namespace CSharpBinding @@ -106,6 +106,8 @@ namespace CSharpBinding
if (document == null) {
document = new ReadOnlyDocument(textSource, fileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
if (highlighter != null)
highlighter.BeginHighlighting();
}
Identifier identifier = node.GetChildByRole(Roles.Identifier);
if (!identifier.IsNull)

2
src/AddIns/BackendBindings/CSharpBinding/Project/Src/Parser/Parser.cs

@ -145,6 +145,8 @@ namespace CSharpBinding.Parser @@ -145,6 +145,8 @@ namespace CSharpBinding.Parser
if (document == null) {
document = new ReadOnlyDocument(fileContent, parseInfo.FileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
if (highlighter != null)
highlighter.BeginHighlighting();
}
var region = new DomRegion(parseInfo.FileName, node.StartLocation, node.EndLocation);
int offset = document.GetOffset(node.StartLocation);

5
src/AddIns/BackendBindings/CSharpBinding/Project/Src/Refactoring/SearchForIssuesCommand.cs

@ -164,6 +164,9 @@ namespace CSharpBinding.Refactoring @@ -164,6 +164,9 @@ namespace CSharpBinding.Refactoring
if (document == null) {
document = new ReadOnlyDocument(fileContent, fileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
if (highlighter != null) {
highlighter.BeginHighlighting();
}
}
results.Add(SearchResultMatch.Create(document, issue.Start, issue.End, highlighter));
}
@ -247,6 +250,8 @@ namespace CSharpBinding.Refactoring @@ -247,6 +250,8 @@ namespace CSharpBinding.Refactoring
}
if (allIssues.Count > 0) {
using (var highlighter = SD.EditorControlService.CreateHighlighter(document)) {
if (highlighter != null)
highlighter.BeginHighlighting();
return allIssues.Select(issue => SearchResultMatch.Create(document, issue.Start, issue.End, highlighter)).ToList();
}
} else {

2
src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlSymbolSearch.cs

@ -94,6 +94,8 @@ namespace ICSharpCode.XamlBinding @@ -94,6 +94,8 @@ namespace ICSharpCode.XamlBinding
if (document == null) {
document = new ReadOnlyDocument(textSource, fileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
if (highlighter != null)
highlighter.BeginHighlighting();
}
var result = resolver.Resolve(parseInfo, document.GetLocation(offset + entity.Name.Length / 2 + 1), compilation, cancellationToken);
int length = entity.Name.Length;

2
src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchManager.cs

@ -211,6 +211,8 @@ namespace SearchAndReplace @@ -211,6 +211,8 @@ namespace SearchAndReplace
if (document == null) {
document = new ReadOnlyDocument(source, fileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
if (highlighter != null)
highlighter.BeginHighlighting();
}
var start = document.GetLocation(result.Offset);
var end = document.GetLocation(result.Offset + result.Length);

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

@ -31,6 +31,8 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -31,6 +31,8 @@ namespace ICSharpCode.AvalonEdit.Highlighting
readonly IHighlightingDefinition definition;
readonly WeakLineTracker weakLineTracker;
bool isHighlighting;
bool isInHighlightingGroup;
bool isDisposed;
/// <summary>
/// Gets the document that this DocumentHighlighter is highlighting.
@ -50,6 +52,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -50,6 +52,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
throw new ArgumentNullException("definition");
this.document = document;
this.definition = definition;
document.VerifyAccess();
weakLineTracker = WeakLineTracker.Register(document, this);
InvalidateHighlighting();
}
@ -75,6 +78,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -75,6 +78,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
{
if (weakLineTracker != null)
weakLineTracker.Deregister();
isDisposed = true;
}
void ILineTracker.BeforeRemoveLine(DocumentLine line)
@ -197,6 +201,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -197,6 +201,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
void CheckIsHighlighting()
{
if (isDisposed) {
throw new ObjectDisposedException("DocumentHighlighter");
}
if (isHighlighting) {
throw new InvalidOperationException("Invalid call - a highlighting operation is currently running.");
}
@ -505,20 +512,28 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -505,20 +512,28 @@ namespace ICSharpCode.AvalonEdit.Highlighting
}
#endregion
/// <inheritdoc/>
public HighlightingColor DefaultTextColor {
get { return null; }
}
/// <inheritdoc/>
public void BeginHighlighting()
{
if (isInHighlightingGroup)
throw new InvalidOperationException("Highlighting group is already open");
isInHighlightingGroup = true;
}
/// <inheritdoc/>
public void EndHighlighting()
{
if (!isInHighlightingGroup)
throw new InvalidOperationException("Highlighting group is not open");
isInHighlightingGroup = false;
}
/// <inheritdoc/>
public HighlightingColor GetNamedColor(string name)
{
return definition.GetNamedColor(name);

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

@ -20,6 +20,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -20,6 +20,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
readonly IHighlightingDefinition definition;
TextView textView;
IHighlighter highlighter;
bool isFixedHighlighter;
/// <summary>
/// Creates a new HighlightingColorizer instance.
@ -33,7 +34,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -33,7 +34,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
}
/// <summary>
/// Creates a new HighlightingColorizer instance.
/// Creates a new HighlightingColorizer instance that uses a fixed highlighter instance.
/// The colorizer can only be used with text views that show the document for which
/// the highlighter was created.
/// </summary>
/// <param name="highlighter">The highlighter to be used.</param>
public HighlightingColorizer(IHighlighter highlighter)
@ -41,6 +44,15 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -41,6 +44,15 @@ namespace ICSharpCode.AvalonEdit.Highlighting
if (highlighter == null)
throw new ArgumentNullException("highlighter");
this.highlighter = highlighter;
this.isFixedHighlighter = true;
}
/// <summary>
/// Creates a new HighlightingColorizer instance.
/// Derived classes using this constructor must override the <see cref="CreateHighlighter"/> method.
/// </summary>
protected HighlightingColorizer()
{
}
void textView_DocumentChanged(object sender, EventArgs e)
@ -61,7 +73,11 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -61,7 +73,11 @@ namespace ICSharpCode.AvalonEdit.Highlighting
// remove highlighter if it is registered
if (textView.Services.GetService(typeof(IHighlighter)) == highlighter)
textView.Services.RemoveService(typeof(IHighlighter));
if (!isFixedHighlighter) {
if (highlighter != null)
highlighter.Dispose();
highlighter = null;
}
}
}
@ -72,8 +88,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -72,8 +88,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
protected virtual void RegisterServices(TextView textView)
{
if (textView.Document != null) {
highlighter = textView.Document != null ? CreateHighlighter(textView, textView.Document) : null;
if (highlighter != null) {
if (!isFixedHighlighter)
highlighter = textView.Document != null ? CreateHighlighter(textView, textView.Document) : null;
if (highlighter != null && highlighter.Document == textView.Document) {
// add service only if it doesn't already exist
if (textView.Services.GetService(typeof(IHighlighter)) == null) {
textView.Services.AddService(typeof(IHighlighter), highlighter);
@ -88,7 +105,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -88,7 +105,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// </summary>
protected virtual IHighlighter CreateHighlighter(TextView textView, TextDocument document)
{
return highlighter ?? new DocumentHighlighter(document, definition);
if (definition != null)
return new DocumentHighlighter(document, definition);
else
throw new NotSupportedException("Cannot create a highlighter because no IHighlightingDefinition was specified, and the CreateHighlighter() method was not overridden.");
}
/// <inheritdoc/>
@ -101,6 +121,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -101,6 +121,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
this.textView = textView;
textView.DocumentChanged += textView_DocumentChanged;
textView.VisualLineConstructionStarting += textView_VisualLineConstructionStarting;
textView.VisualLinesChanged += textView_VisualLinesChanged;
RegisterServices(textView);
}
@ -110,6 +131,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -110,6 +131,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
DeregisterServices(textView);
textView.DocumentChanged -= textView_DocumentChanged;
textView.VisualLineConstructionStarting -= textView_VisualLineConstructionStarting;
textView.VisualLinesChanged -= textView_VisualLinesChanged;
base.OnRemoveFromTextView(textView);
this.textView = null;
}
@ -122,11 +144,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -122,11 +144,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting
// 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.
lineNumberBeingColorized = e.FirstLineInView.LineNumber - 1;
highlighter.BeginHighlighting();
highlighter.UpdateHighlightingState(lineNumberBeingColorized);
lineNumberBeingColorized = 0;
}
}
void textView_VisualLinesChanged(object sender, EventArgs e)
{
if (highlighter != null) {
highlighter.EndHighlighting();
}
}
DocumentLine lastColorizedLine;
/// <inheritdoc/>

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/IHighlighter.cs

@ -63,13 +63,21 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -63,13 +63,21 @@ namespace ICSharpCode.AvalonEdit.Highlighting
event HighlightingStateChangedEventHandler HighlightingStateChanged;
/// <summary>
/// Opens a group of HighlightLine calls.
/// Opens a group of <see cref="HighlightLine"/> calls.
/// It is not necessary to call this method before calling <see cref="HighlightLine"/>,
/// however, doing so can make the highlighting much more performant in some cases
/// (e.g. the C# semantic highlighter in SharpDevelop will re-use the resolver within a highlighting group).
/// </summary>
/// <remarks>
/// The group is closed by either a <see cref="EndHighlighting"/> or a <see cref="IDisposable.Dispose"/> call.
/// Nested groups are not allowed.
/// </remarks>
void BeginHighlighting();
/// <summary>
/// Closes the currently opened group of HighlightLine calls.
/// Closes the currently opened group of <see cref="HighlightLine"/> calls.
/// </summary>
/// <seealso cref="BeginHighlighting"/>.
void EndHighlighting();
/// <summary>

2
src/Main/Base/Project/Src/Services/RefactoringService/FindReferencesAndRenameHelper.cs

@ -354,6 +354,8 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -354,6 +354,8 @@ namespace ICSharpCode.SharpDevelop.Refactoring
buffer = SD.FileService.GetFileContent(r.FileName);
document = new ReadOnlyDocument(buffer, r.FileName);
highlighter = SD.EditorControlService.CreateHighlighter(document);
if (highlighter != null)
highlighter.BeginHighlighting();
}
var start = r.StartLocation;
var end = r.EndLocation;

Loading…
Cancel
Save