Skip to content

Commit

Permalink
Merge pull request #66 from mhutch/background-parse-service
Browse files Browse the repository at this point in the history
Background parse service
  • Loading branch information
mhutch authored Jul 7, 2023
2 parents d62e1cc + 7179963 commit 08baed4
Show file tree
Hide file tree
Showing 28 changed files with 170 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Editor.Tests/Commands/SmartIndentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

using Microsoft.VisualStudio.Text.Editor;

using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Editor.SmartIndent;

using NUnit.Framework;
Expand Down
2 changes: 1 addition & 1 deletion Editor.Tests/Completion/XmlCompletionTestSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#nullable enable

using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
Expand All @@ -21,6 +20,7 @@
using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Logging;
using MonoDevelop.Xml.Editor.Parsing;

namespace MonoDevelop.Xml.Editor.Tests.Completion
{
Expand Down
2 changes: 1 addition & 1 deletion Editor.Tests/XmlEditorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Utilities;

using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Parsing;

namespace MonoDevelop.Xml.Editor.Tests
{
Expand Down
2 changes: 1 addition & 1 deletion Editor/BraceCompletion/XmlBraceCompletionCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
using Microsoft.VisualStudio.Utilities;

using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Parser;

using BF = System.Reflection.BindingFlags;
Expand Down
2 changes: 1 addition & 1 deletion Editor/Commands/AutoClosingTagCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
using Microsoft.VisualStudio.Utilities;

using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Options;
using MonoDevelop.Xml.Editor.Parsing;

namespace MonoDevelop.Xml.Editor.Commands
{
Expand Down
1 change: 1 addition & 0 deletions Editor/Commands/CommentUncommentCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Parser;

using JoinableTaskContext = Microsoft.VisualStudio.Threading.JoinableTaskContext;
Expand Down
1 change: 1 addition & 0 deletions Editor/Completion/XmlCompletionSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Microsoft.VisualStudio.Text.Editor;

using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Parser;

namespace MonoDevelop.Xml.Editor.Completion
Expand Down
2 changes: 1 addition & 1 deletion Editor/HighlightReferences/XmlHighlightEndTagTagger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using Microsoft.VisualStudio.Text.Editor;

using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.HighlightReferences;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Editor.Tagging;
using MonoDevelop.Xml.Parser;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using Microsoft.VisualStudio.Utilities;

using MonoDevelop.Xml.Editor;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Editor.Logging;
using MonoDevelop.Xml.Editor.Tagging;

Expand Down
2 changes: 1 addition & 1 deletion Editor/Outlining/StructureTagger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
using Microsoft.VisualStudio.Text.Tagging;

using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Editor.Completion;
using MonoDevelop.Xml.Editor.Parsing;
using MonoDevelop.Xml.Logging;

namespace MonoDevelop.Xml.Editor.Tagging
Expand Down
3 changes: 2 additions & 1 deletion Editor/Outlining/StructureTaggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using MonoDevelop.Xml.Editor.Completion;

using MonoDevelop.Xml.Editor.Logging;
using MonoDevelop.Xml.Editor.Parsing;

namespace MonoDevelop.Xml.Editor.Tagging
{
Expand Down
20 changes: 20 additions & 0 deletions Editor/Parsing/BackgroundParseService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Threading.Tasks;

namespace MonoDevelop.Xml.Editor.Parsing;

/// <summary>
/// Allows observing the status of background parsing operations.
/// May be extended in future to provide custom scheduling.
/// </summary>
public interface IBackgroundParseService
{
void RegisterBackgroundOperation (Task task);
event EventHandler RunningStateChanged;
public bool IsRunning { get; }
}
89 changes: 89 additions & 0 deletions Editor/Parsing/BackgroundParseServiceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.Text;

using MonoDevelop.Xml.Editor.Logging;
using MonoDevelop.Xml.Logging;

namespace MonoDevelop.Xml.Editor.Parsing;

/// <summary>
/// Import to obtain <see cref="IBackgroundParseService"/> instances.
/// </summary>
[Export (typeof (BackgroundParseServiceProvider))]
public sealed class BackgroundParseServiceProvider
{
readonly object lockerObject = new ();
readonly IEditorLoggerFactory loggerFactory;

ImmutableDictionary<string, BackgroundParserService> parseServices = ImmutableDictionary<string, BackgroundParserService>.Empty;

[ImportingConstructor]
public BackgroundParseServiceProvider (IEditorLoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}

public IBackgroundParseService GetParseServiceForBuffer (ITextBuffer buffer) => GetParseServiceForContentType (buffer.ContentType.TypeName);
public IBackgroundParseService GetParseServiceForContentType (string contentTypeName)
{
if (parseServices.TryGetValue (contentTypeName, out var service)) {
return service;
}
lock (lockerObject) {
if (parseServices.TryGetValue (contentTypeName, out service)) {
return service;
}
var logger = loggerFactory.CreateLogger<BackgroundParserService> (contentTypeName);
service = new BackgroundParserService (logger);
parseServices = parseServices.Add (contentTypeName, service);
}
return service;
}

class BackgroundParserService : IBackgroundParseService
{
readonly ILogger logger;

[ImportingConstructor]
public BackgroundParserService (ILogger logger)
{
this.logger = logger;
}

int runningTasks = 0;

public bool IsRunning => runningTasks > 0;

public void RegisterBackgroundOperation (Task task)
{
int taskCount = Interlocked.Increment (ref runningTasks);
task.ContinueWith (OperationCompleted, TaskScheduler.Default).CatchAndLogWarning (logger);

if (taskCount == 1 || taskCount == 0) {
RunningStateChanged?.Invoke (this, EventArgs.Empty);
}
}

void OperationCompleted (Task t)
{
int taskCount = Interlocked.Decrement (ref runningTasks);

if (taskCount == 1 || taskCount == 0) {
RunningStateChanged?.Invoke (this, EventArgs.Empty);
}
}

public event EventHandler? RunningStateChanged;
}
}
2 changes: 1 addition & 1 deletion Editor/Parsing/BackgroundParser.ParseOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Threading;
using System.Threading.Tasks;

namespace MonoDevelop.Xml.Editor.Completion
namespace MonoDevelop.Xml.Editor.Parsing
{
public abstract partial class BackgroundProcessor<TInput, TOutput>
{
Expand Down
33 changes: 22 additions & 11 deletions Editor/Parsing/BackgroundParser.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MonoDevelop.Xml.Editor.Completion
namespace MonoDevelop.Xml.Editor.Parsing
{
public abstract partial class BackgroundProcessor<TInput, TOutput> : IDisposable
where TInput : class
where TOutput : class
{
readonly IBackgroundParseService parseService;

protected BackgroundProcessor (IBackgroundParseService parseService)
{
this.parseService = parseService;
}

Operation CreateOperation (TInput input)
{
var tokenSource = new CancellationTokenSource ();
var task = StartOperationAsync (input, tokenSource.Token);
var operation = new Operation (this, task, input, tokenSource);

parseService.RegisterBackgroundOperation (task);

#pragma warning disable VSTHRD110, VSTHRD105 // Observe result of async calls, Avoid method overloads that assume TaskScheduler.Current

//capture successful parses
task.ContinueWith ((t, state) => {
var op = ((Operation)state);
Operation op = (Operation)state;
if (t.IsCompleted) {
op.Processor.lastSuccessfulOperation = op;
try {
op.Processor.OnOperationCompleted (op.Input, op.Output);
} catch (Exception ex) {
op.Processor.OnUnhandledParseError (ex);
} catch (Exception eventException) {
op.Processor.OnUnhandledParseError (eventException);
}
}
if (t.IsFaulted) {
op.Processor.HandleUnhandledParseError (t.Exception);
if (t.IsFaulted && t.Exception is Exception operationException) {
op.Processor.HandleUnhandledParseError (operationException);
}
}, operation, tokenSource.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

Expand Down Expand Up @@ -78,10 +89,10 @@ protected virtual void OnUnhandledParseError (Exception ex)
LastDitchLog (ex);
}

Operation currentOperation;
Operation lastSuccessfulOperation;
Operation? currentOperation;
Operation? lastSuccessfulOperation;

protected abstract Task<TOutput> StartOperationAsync (TInput input, TOutput previousOutput, TInput previousInput, CancellationToken token);
protected abstract Task<TOutput> StartOperationAsync (TInput input, TOutput? previousOutput, TInput? previousInput, CancellationToken token);

protected abstract int CompareInputs (TInput a, TInput b);

Expand All @@ -91,10 +102,10 @@ Task<TOutput> StartOperationAsync (TInput input, CancellationToken token)
if (lastSuccessful != null && CompareInputs (lastSuccessful.Input, input) < 0) {
return StartOperationAsync (input, lastSuccessful.Output, lastSuccessful.Input, token);
}
return StartOperationAsync (input, default, null, token);
return StartOperationAsync (input, default, default, token);
}

public TOutput LastOutput => lastSuccessfulOperation?.Output;
public TOutput? LastOutput => lastSuccessfulOperation?.Output;

/// <summary>
/// Get an existing completed or running processor task for the provided input if available, or creates a new processor task.
Expand Down
5 changes: 3 additions & 2 deletions Editor/Parsing/BufferParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
using System.Threading;
using Microsoft.VisualStudio.Text;

namespace MonoDevelop.Xml.Editor.Completion

namespace MonoDevelop.Xml.Editor.Parsing
{
/// <summary>
/// Base class for parsers that parse a text buffer on every change.
Expand All @@ -16,7 +17,7 @@ public abstract class BufferParser<TParseResult> : BackgroundProcessor<ITextSnap
{
internal object? providerKey;

public BufferParser (ITextBuffer2 buffer)
public BufferParser (ITextBuffer2 buffer, IBackgroundParseService parseService) : base (parseService)
{
Buffer = buffer;

Expand Down
2 changes: 1 addition & 1 deletion Editor/Parsing/BufferParserProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using Microsoft.VisualStudio.Text;

namespace MonoDevelop.Xml.Editor.Completion
namespace MonoDevelop.Xml.Editor.Parsing
{
public abstract class BufferParserProvider<TParser, TParseResult> : IParserProvider<TParser, TParseResult>
where TParser : BufferParser<TParseResult>
Expand Down
4 changes: 2 additions & 2 deletions Editor/Parsing/IParserProvider.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.VisualStudio.Text;

namespace MonoDevelop.Xml.Editor.Completion
namespace MonoDevelop.Xml.Editor.Parsing
{
public interface IParserProvider<TParser,TParseResult> where TParser : BufferParser<TParseResult> where TParseResult : class
{
Expand Down
4 changes: 2 additions & 2 deletions Editor/Parsing/ParseCompletedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Microsoft.VisualStudio.Text;

namespace MonoDevelop.Xml.Editor.Completion
namespace MonoDevelop.Xml.Editor.Parsing
{
public class ParseCompletedEventArgs<T> : EventArgs
{
Expand Down
8 changes: 4 additions & 4 deletions Editor/Parsing/XmlBackgroundParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
using MonoDevelop.Xml.Dom;
using MonoDevelop.Xml.Parser;

namespace MonoDevelop.Xml.Editor.Completion
namespace MonoDevelop.Xml.Editor.Parsing
{
public partial class XmlBackgroundParser : BufferParser<XmlParseResult>
{
private readonly ILogger logger;

public XmlBackgroundParser (ITextBuffer2 buffer, ILogger logger) : base (buffer)
public XmlBackgroundParser (ITextBuffer2 buffer, ILogger logger, IBackgroundParseService parseService) : base (buffer, parseService)
{
StateMachine = CreateParserStateMachine ();
this.logger = logger;
Expand All @@ -32,8 +32,8 @@ public XmlBackgroundParser (ITextBuffer2 buffer, ILogger logger) : base (buffer)
protected XmlRootState StateMachine { get; private set; }

protected override Task<XmlParseResult> StartOperationAsync (ITextSnapshot input,
XmlParseResult previousOutput,
ITextSnapshot previousInput,
XmlParseResult? previousOutput,
ITextSnapshot? previousInput,
CancellationToken token)
{
var parser = new XmlTreeParser (StateMachine);
Expand Down
Loading

0 comments on commit 08baed4

Please sign in to comment.