From 44cdc0086d858d11a1f860a92418f6f2fd83959e Mon Sep 17 00:00:00 2001 From: Mads Kristensen Date: Sun, 2 Jan 2022 05:47:07 -0800 Subject: [PATCH] Forced parsing to a background thread [release] --- src/RestClient/Parser/Document.cs | 2 +- src/RestClient/Parser/DocumentParser.cs | 46 ++++++++++--------- .../Commands/GoToDefinitionCommand.cs | 2 +- .../Commands/SendRequestCommand.cs | 2 +- src/RestClientVS/ExtensionMethods.cs | 3 ++ .../Language/RestCompletionSource.cs | 4 +- .../Language/RestIntratextAdornmentTagger.cs | 2 +- src/RestClientVS/Language/TokenTagger.cs | 43 ++++++++++------- src/RestClientVS/RestClientVS.csproj | 3 ++ src/RestClientVS/RestDocument.cs | 14 +++--- test/RestClientTest/HttpTest.cs | 1 - test/RestClientTest/TokenTest.cs | 32 ++++--------- test/RestClientTest/VariableTest.cs | 13 ++---- 13 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/RestClient/Parser/Document.cs b/src/RestClient/Parser/Document.cs index 5a7e3b7..6666a51 100644 --- a/src/RestClient/Parser/Document.cs +++ b/src/RestClient/Parser/Document.cs @@ -11,7 +11,7 @@ public partial class Document protected Document(string[] lines) { _lines = lines; - _ = ParseAsync(); + Parse(); } public List Items { get; private set; } = new List(); diff --git a/src/RestClient/Parser/DocumentParser.cs b/src/RestClient/Parser/DocumentParser.cs index aa7e70c..e32193d 100644 --- a/src/RestClient/Parser/DocumentParser.cs +++ b/src/RestClient/Parser/DocumentParser.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace RestClient { @@ -16,42 +15,45 @@ public partial class Document public bool IsParsing { get; private set; } public bool IsValid { get; private set; } - public Task ParseAsync() + public void Parse() { IsParsing = true; + var isSuccess = false; + var start = 0; - return Task.Run(() => + try { - var start = 0; + List tokens = new(); - try + foreach (var line in _lines) { - List tokens = new(); + IEnumerable? current = ParseLine(start, line, tokens); - foreach (var line in _lines) + if (current != null) { - IEnumerable? current = ParseLine(start, line, tokens); + tokens.AddRange(current); + } - if (current != null) - { - tokens.AddRange(current); - } + start += line.Length; + } - start += line.Length; - } + Items = tokens; - Items = tokens; + OrganizeItems(); + ExpandVariables(); + ValidateDocument(); - OrganizeItems(); - ExpandVariables(); - ValidateDocument(); - } - finally + isSuccess = true; + } + finally + { + IsParsing = false; + + if (isSuccess) { - IsParsing = false; Parsed?.Invoke(this, EventArgs.Empty); } - }); + } } private IEnumerable ParseLine(int start, string line, List tokens) diff --git a/src/RestClientVS/Commands/GoToDefinitionCommand.cs b/src/RestClientVS/Commands/GoToDefinitionCommand.cs index 6c999d6..d1324f5 100644 --- a/src/RestClientVS/Commands/GoToDefinitionCommand.cs +++ b/src/RestClientVS/Commands/GoToDefinitionCommand.cs @@ -21,7 +21,7 @@ public bool ExecuteCommand(GoToDefinitionCommandArgs args, CommandExecutionConte { var position = args.TextView.Caret.Position.BufferPosition.Position; - Document document = RestDocument.FromTextbuffer(args.TextView.TextBuffer); + Document document = args.TextView.TextBuffer.GetRestDocument(); ParseItem token = document.FindItemFromPosition(position); if (token?.Type == ItemType.Reference) diff --git a/src/RestClientVS/Commands/SendRequestCommand.cs b/src/RestClientVS/Commands/SendRequestCommand.cs index e1320b8..6b08c52 100644 --- a/src/RestClientVS/Commands/SendRequestCommand.cs +++ b/src/RestClientVS/Commands/SendRequestCommand.cs @@ -20,7 +20,7 @@ protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) if (docView != null) { var position = docView.TextView.Caret.Position.BufferPosition.Position; - Document doc = RestDocument.FromTextbuffer(docView.TextBuffer); + Document doc = docView.TextBuffer.GetRestDocument(); Request request = doc.Requests.FirstOrDefault(r => r.Contains(position)); if (request != null) diff --git a/src/RestClientVS/ExtensionMethods.cs b/src/RestClientVS/ExtensionMethods.cs index b40889e..4e230a6 100644 --- a/src/RestClientVS/ExtensionMethods.cs +++ b/src/RestClientVS/ExtensionMethods.cs @@ -7,5 +7,8 @@ public static class ExtensionMethods { public static Span ToSpan(this ParseItem token) => new(token.Start, token.Length); + + public static RestDocument GetRestDocument(this ITextBuffer buffer) => + buffer.Properties.GetOrCreateSingletonProperty(() => new RestDocument(buffer)); } } diff --git a/src/RestClientVS/Language/RestCompletionSource.cs b/src/RestClientVS/Language/RestCompletionSource.cs index dc18086..ba331ff 100644 --- a/src/RestClientVS/Language/RestCompletionSource.cs +++ b/src/RestClientVS/Language/RestCompletionSource.cs @@ -44,7 +44,7 @@ public Task GetCompletionContextAsync(IAsyncCompletionSession { ITextSnapshotLine line = triggerLocation.GetContainingLine(); - Document document = RestDocument.FromTextbuffer(session.TextView.TextBuffer); + Document document = session.TextView.TextBuffer.GetRestDocument(); SnapshotPoint lineStart = line.Start; ParseItem token = GetPreviousToken(document, lineStart, out var hasEmptyLine); @@ -167,7 +167,7 @@ public CompletionStartData InitializeCompletion(CompletionTrigger trigger, Snaps return CompletionStartData.DoesNotParticipateInCompletion; } - Document document = RestDocument.FromTextbuffer(triggerLocation.Snapshot.TextBuffer); + Document document = triggerLocation.Snapshot.TextBuffer.GetRestDocument(); ParseItem item = document?.FindItemFromPosition(triggerLocation.Position); if (item?.Type == ItemType.Reference) diff --git a/src/RestClientVS/Language/RestIntratextAdornmentTagger.cs b/src/RestClientVS/Language/RestIntratextAdornmentTagger.cs index dccc0f3..4d1a701 100644 --- a/src/RestClientVS/Language/RestIntratextAdornmentTagger.cs +++ b/src/RestClientVS/Language/RestIntratextAdornmentTagger.cs @@ -30,7 +30,7 @@ internal class RestIntratextAdornmentTagger : ITagger public RestIntratextAdornmentTagger(ITextBuffer buffer) { _buffer = buffer; - _document = RestDocument.FromTextbuffer(buffer); + _document = buffer.GetRestDocument(); } public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) diff --git a/src/RestClientVS/Language/TokenTagger.cs b/src/RestClientVS/Language/TokenTagger.cs index 3b423fc..b225643 100644 --- a/src/RestClientVS/Language/TokenTagger.cs +++ b/src/RestClientVS/Language/TokenTagger.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Adornments; using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Utilities; using RestClient; @@ -34,10 +35,15 @@ internal class TokenTagger : ITagger, IDisposable internal TokenTagger(ITextBuffer buffer) { _buffer = buffer; - _document = RestDocument.FromTextbuffer(buffer); + _document = buffer.GetRestDocument(); _document.Parsed += ReParse; _tagsCache = new Dictionary>(); - ReParse(); + + ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await TaskScheduler.Default; + ReParse(); + }).FireAndForget(); } public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) @@ -47,26 +53,31 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection private void ReParse(object sender = null, EventArgs e = null) { - ThreadHelper.JoinableTaskFactory.StartOnIdle(() => - { - Dictionary> list = new(); + // Make sure this is running on a background thread. + ThreadHelper.ThrowIfOnUIThread(); - foreach (ParseItem item in _document.Items) - { - AddTagToList(list, item); + Dictionary> list = new(); - foreach (ParseItem variable in item.References) - { - AddTagToList(list, variable); - } + foreach (ParseItem item in _document.Items) + { + if (_document.IsParsing) + { + // Abort and wait for the next parse event to finish + return; } - _tagsCache = list; + AddTagToList(list, item); + + foreach (ParseItem variable in item.References) + { + AddTagToList(list, variable); + } + } - SnapshotSpan span = new(_buffer.CurrentSnapshot, 0, _buffer.CurrentSnapshot.Length); - TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(span)); + _tagsCache = list; - }, VsTaskRunContext.UIThreadIdlePriority).FireAndForget(); + SnapshotSpan span = new(_buffer.CurrentSnapshot, 0, _buffer.CurrentSnapshot.Length); + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(span)); } private void AddTagToList(Dictionary> list, ParseItem item) diff --git a/src/RestClientVS/RestClientVS.csproj b/src/RestClientVS/RestClientVS.csproj index 7f20403..6d6d36b 100644 --- a/src/RestClientVS/RestClientVS.csproj +++ b/src/RestClientVS/RestClientVS.csproj @@ -108,6 +108,9 @@ compile; build; native; contentfiles; analyzers; buildtransitive + + 17.0.64 + diff --git a/src/RestClientVS/RestDocument.cs b/src/RestClientVS/RestDocument.cs index a2fa67e..81af6ab 100644 --- a/src/RestClientVS/RestDocument.cs +++ b/src/RestClientVS/RestDocument.cs @@ -1,5 +1,7 @@ using System.Linq; +using System.Threading.Tasks; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Threading; using RestClient; namespace RestClientVS @@ -20,13 +22,12 @@ public RestDocument(ITextBuffer buffer) FileName = buffer.GetFileName(); -#pragma warning disable VSTHRD104 // Offer async methods - ThreadHelper.JoinableTaskFactory.Run(async () => + ThreadHelper.JoinableTaskFactory.RunAsync(async () => { + await Task.Yield(); Project project = await VS.Solutions.GetActiveProjectAsync(); ProjectName = project?.Name; - }); -#pragma warning restore VSTHRD104 // Offer async methods + }).FireAndForget(); } private void BufferChanged(object sender, TextContentChangedEventArgs e) @@ -35,9 +36,10 @@ private void BufferChanged(object sender, TextContentChangedEventArgs e) ParseAsync().FireAndForget(); } - public static RestDocument FromTextbuffer(ITextBuffer buffer) + private async Task ParseAsync() { - return buffer.Properties.GetOrCreateSingletonProperty(() => new RestDocument(buffer)); + await TaskScheduler.Default; + Parse(); } public void Dispose() diff --git a/test/RestClientTest/HttpTest.cs b/test/RestClientTest/HttpTest.cs index 6dc4274..eebf559 100644 --- a/test/RestClientTest/HttpTest.cs +++ b/test/RestClientTest/HttpTest.cs @@ -17,7 +17,6 @@ public class HttpTest public async Task SendAsync(string url) { var doc = Document.FromLines(url); - await doc.WaitForParsingCompleteAsync(); RequestResult client = await RequestSender.SendAsync(doc.Requests.First(), TimeSpan.FromSeconds(10)); var raw = await client.Response.ToRawStringAsync(); diff --git a/test/RestClientTest/TokenTest.cs b/test/RestClientTest/TokenTest.cs index 94e4d03..6039373 100644 --- a/test/RestClientTest/TokenTest.cs +++ b/test/RestClientTest/TokenTest.cs @@ -1,12 +1,10 @@ using System; using System.Linq; -using System.Threading.Tasks; using RestClient; using Xunit; namespace RestClientTest { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] public class TokenTest { [Theory] @@ -14,10 +12,9 @@ public class TokenTest [InlineData(@"post https://example.com?hat&ost")] [InlineData(@"options https://example.com")] [InlineData(@"Trace https://example.com?hat&ost")] - public async Task OneLiners(string line) + public void OneLiners(string line) { var doc = Document.FromLines(line); - await doc.WaitForParsingCompleteAsync(); ParseItem request = doc.Items?.First(); ParseItem method = doc.Items?.ElementAt(1); @@ -29,12 +26,11 @@ public async Task OneLiners(string line) } [Fact] - public async Task RequestTextAfterLineBreak() + public void RequestTextAfterLineBreak() { var lines = new[] { "\r", Environment.NewLine, @"GET https://example.com" }; var doc = Document.FromLines(lines); - await doc.WaitForParsingCompleteAsync(); Request request = doc.Requests?.FirstOrDefault(); @@ -43,7 +39,7 @@ public async Task RequestTextAfterLineBreak() } [Fact] - public async Task MultipleRequests() + public void MultipleRequests() { var lines = new[] {@"get http://example.com\r\n", "\r\n", @@ -52,13 +48,12 @@ public async Task MultipleRequests() "post http://bing.com"}; var doc = Document.FromLines(lines); - await doc.WaitForParsingCompleteAsync(); Assert.Equal(2, doc.Requests.Count); } [Fact] - public async Task RequestWithHeaderAndBody() + public void RequestWithHeaderAndBody() { var lines = new[] { @@ -69,7 +64,6 @@ public async Task RequestWithHeaderAndBody() }; var doc = Document.FromLines(lines); - await doc.WaitForParsingCompleteAsync(); Request request = doc.Requests?.FirstOrDefault(); Assert.Single(doc.Requests); @@ -77,7 +71,7 @@ public async Task RequestWithHeaderAndBody() } [Fact] - public async Task RequestWithHeaderAndMultilineBody() + public void RequestWithHeaderAndMultilineBody() { var lines = new[] { @@ -90,7 +84,6 @@ public async Task RequestWithHeaderAndMultilineBody() }; var doc = Document.FromLines(lines); - await doc.WaitForParsingCompleteAsync(); Request request = doc.Requests.First(); Assert.NotNull(request.Body); @@ -98,7 +91,7 @@ public async Task RequestWithHeaderAndMultilineBody() } [Fact] - public async Task RequestWithHeaderAndBodyAndComment() + public void RequestWithHeaderAndBodyAndComment() { var lines = new[] { @@ -112,7 +105,6 @@ public async Task RequestWithHeaderAndBodyAndComment() }; var doc = Document.FromLines(lines); - await doc.WaitForParsingCompleteAsync(); Request first = doc.Requests.FirstOrDefault(); Assert.NotNull(first); @@ -131,10 +123,9 @@ public async Task RequestWithHeaderAndBodyAndComment() [InlineData("\r")] [InlineData("\n")] [InlineData("\r\n")] - public async Task EmptyLines(string line) + public void EmptyLines(string line) { var doc = Document.FromLines(line); - await doc.WaitForParsingCompleteAsync(); ParseItem first = doc.Items?.FirstOrDefault(); Assert.NotNull(first); @@ -146,7 +137,7 @@ public async Task EmptyLines(string line) } [Fact] - public async Task BodyAfterComment() + public void BodyAfterComment() { var text = new[] { @"TraCe https://{{host}}/authors/{{name}}\r\n", "Content-Type: at{{contentType}}svin\r\n", @@ -163,7 +154,6 @@ public async Task BodyAfterComment() "\r\n",}; var doc = Document.FromLines(text); - await doc.WaitForParsingCompleteAsync(); Request request = doc.Requests.First(); Assert.NotNull(request.Body); @@ -172,12 +162,11 @@ public async Task BodyAfterComment() } [Fact] - public async Task VariableTokenization() + public void VariableTokenization() { var text = $"@name = value"; var doc = Document.FromLines(text); - await doc.WaitForParsingCompleteAsync(); ParseItem name = doc.Items.FirstOrDefault(); Assert.Equal(0, name.Start); @@ -187,7 +176,7 @@ public async Task VariableTokenization() } [Fact] - public async Task CommentInBetweenHeaders() + public void CommentInBetweenHeaders() { var text = new[] { @"POST https://example.com", "Content-Type:application/json", @@ -195,7 +184,6 @@ public async Task CommentInBetweenHeaders() "Accept: gzip" }; var doc = Document.FromLines(text); - await doc.WaitForParsingCompleteAsync(); Assert.Equal(8, doc.Items.Count); } diff --git a/test/RestClientTest/VariableTest.cs b/test/RestClientTest/VariableTest.cs index 62c970f..4cbd691 100644 --- a/test/RestClientTest/VariableTest.cs +++ b/test/RestClientTest/VariableTest.cs @@ -1,11 +1,9 @@ using System.Linq; -using System.Threading.Tasks; using RestClient; using Xunit; namespace RestClientTest { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] public class VariableTest { [Theory] @@ -14,29 +12,25 @@ public class VariableTest [InlineData("@name= value", "value")] [InlineData("@name =value", "value")] [InlineData("@name\t=\t value", "value")] - public async Task VariableDeclarations(string line, string value) + public void VariableDeclarations(string line, string value) { var doc = Document.FromLines(line); - await doc.WaitForParsingCompleteAsync(); Variable first = doc.Variables?.FirstOrDefault(); Assert.NotNull(first); Assert.Equal(0, first.Name.Start); Assert.EndsWith(value, first.Value.Text); - //Assert.Equal(name, first.Name.Text); - //Assert.Equal(value, first.Value.Text); } [Theory] [InlineData("var1", "1")] - public async Task ExpandUrlVariables(string name, string value) + public void ExpandUrlVariables(string name, string value) { var variable = $"@{name}={value}"; var request = "GET http://example.com?{{" + name + "}}"; var doc = Document.FromLines(variable, request); - await doc.WaitForParsingCompleteAsync(); Request r = doc.Requests.FirstOrDefault(); @@ -44,14 +38,13 @@ public async Task ExpandUrlVariables(string name, string value) } [Fact] - public async Task ExpandUrlVariablesRecursive() + public void ExpandUrlVariablesRecursive() { var text = new[] { "@hostname=bing.com\r\n", "@host={{hostname}}\r\n", "GET https://{{host}}" }; var doc = Document.FromLines(text); - await doc.WaitForParsingCompleteAsync(); Request r = doc.Requests.FirstOrDefault();