Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
cb50377
ATR-966: reworked and extended concurrent utils - added extensions fo…
SENya1990 May 8, 2026
716400e
ATR-966: fixed file indentation and formatting
SENya1990 May 8, 2026
966c42a
ATR-966: refactoring - fixed mistype
SENya1990 May 8, 2026
5e10626
ATR-966: refactoring - fixed mistype and renamed file
SENya1990 May 8, 2026
8af26d9
ATR-966: added LastTaggingWasSuccessful flag to the base tagger class…
SENya1990 May 8, 2026
079f548
ATR-966: added more error processing for tagging faluts to the backgr…
SENya1990 May 8, 2026
21221cb
ATR-966: refactored tagger type enum into a separate file
SENya1990 May 12, 2026
a4550f8
ATR-966: refactoring - used more sensitive parameter name
SENya1990 May 12, 2026
4aacc69
ATR-966: removed the check for the reference to Acumatica platform f…
SENya1990 May 12, 2026
605d159
ATR-966: added HasReferenceToAcumaticaPlatform abstract flag to tagge…
SENya1990 May 12, 2026
15d6024
ATR-966: moved check for the Acumatica platform reference to the colo…
SENya1990 May 12, 2026
2854336
ATR-966: added synhronous version of raising tags changes event to th…
SENya1990 May 12, 2026
4108e8f
ATR-966: made input snapshot in ResetCacheAndFlags nullable + refacto…
SENya1990 May 12, 2026
2552594
ATR-966: reworked the workspace changed event handler to reduce the n…
SENya1990 May 12, 2026
cabf809
ATR-966: removed base tagger provide class to not cache workspace in …
SENya1990 May 12, 2026
5779b2c
ATR-966: fixed mistype in the constant name
SENya1990 May 12, 2026
9935883
ATR-966: removed RegEx coloring option from Acuminator VS options
SENya1990 May 12, 2026
5c63e66
ATR-966: removed the regex tagger and the delegating tagger from the …
SENya1990 May 12, 2026
6f45620
ATR-966: removed tagger type enum
SENya1990 May 12, 2026
5177969
ATR-966: refactoring - formatted indentation
SENya1990 May 12, 2026
6478e40
ATR-966: removed redundant tag cache components
SENya1990 May 12, 2026
d40a9f6
ATR-966: merged PXColorizerTaggerBase and PXRoslynColorizer taggers t…
SENya1990 May 12, 2026
a43ef41
ATR-966: reworked starting the tagging task on the thread pool
SENya1990 May 12, 2026
61424cf
ATR-966: refactoring - reduced indentation
SENya1990 May 12, 2026
207228c
ATR-966: removed remaining RegEx coloring options
SENya1990 May 12, 2026
3a27f4b
ATR-966: added filtering of miscellanous files workspace created by V…
SENya1990 May 12, 2026
6a915a9
ATR-966: updated workspace initialization logic in the tagger
SENya1990 May 13, 2026
b238d5f
ATR-966: made retagging on settings changed called synchronously
SENya1990 May 13, 2026
da57b71
ATR-966: updated comments
SENya1990 May 13, 2026
eb79ff9
ATR-966: made sync call to raise outlining tags changed event
SENya1990 May 13, 2026
44f5b80
ATR-966: moved check for unnecesassary parsing and retagging to the d…
SENya1990 May 13, 2026
191bca7
ATR-966: added Roslyn Workspace provider - a dedicated component for …
SENya1990 May 13, 2026
1785354
ATR-966: integrated Roslyn workspace provider into the Roslyn colorin…
SENya1990 May 13, 2026
02e168b
ATR-966: refactoring - renamed event args
SENya1990 May 13, 2026
77764a1
ATR-966: integration of roslyn workspace provider into tagger - part 2
SENya1990 May 13, 2026
7b29cfe
ATR-966: reverting the start of the thread pool operation to how it w…
SENya1990 May 13, 2026
2089037
ATR-966: reworked workspace changed checks to use faster checks that …
SENya1990 May 13, 2026
d558db9
Merge branch 'dev' into bugfix/ATR-966-dev-fix-coloring-part-2
SENya1990 May 13, 2026
3171f4c
ATR-966: fixed AI remark by always recalculating HasReferenceToAcumat…
SENya1990 May 14, 2026
9af304c
ATR-966: fixes suggested by Claude AI
SENya1990 May 14, 2026
1bfd667
ATR-966: fixed calculation of HasReferenceToAcumaticaPlatform from th…
SENya1990 May 14, 2026
853e124
ATR-966: added unwrapping of the continuation task from the AI sugges…
SENya1990 May 14, 2026
484ac47
ATR-966: reworked the disposal and unsubscribing logic after review f…
SENya1990 May 14, 2026
5dc8567
ATR-966: added diagnostic failure message from Claude suggestion
SENya1990 May 14, 2026
3f60956
ATR-966: added synchronization for access to the shared LastTaggingWa…
SENya1990 May 14, 2026
5756e0a
ATR-966: removed unused _buffer field from Claude remark
SENya1990 May 14, 2026
830da86
ATR-966: removed redundant setter for Workspace from the Claude sugge…
SENya1990 May 14, 2026
96a6f0a
ATR-966: reworked contention race in the tagger counstuctor with the …
SENya1990 May 14, 2026
6a290bd
ATR-966: fixed nulalble ValueTask as per Claude suggestions
SENya1990 May 14, 2026
e69a4bb
ATR-966: reworked disposal logic in the workspace provider and tagger…
SENya1990 May 14, 2026
bfccbad
ATR-966: fixes for minor Claude remarks
SENya1990 May 14, 2026
26aab1c
ATR-966: added clearing of LastTaggingWasSuccessful from the Copilot …
SENya1990 May 14, 2026
8fe6392
ATR-966: reworked parsing of the document by the colorizer to handle …
SENya1990 May 14, 2026
a014661
ATR-966: updated semantic model utils
SENya1990 May 14, 2026
c32878d
ATR-966: reworked tagger disposal logic from Copiolt remark
SENya1990 May 14, 2026
51cfe3b
ATR-966: added logging capabilities to TryAwait and integrated them i…
SENya1990 May 14, 2026
d223e67
ATR-966: minor refactoring - removed empty lines
SENya1990 May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<RestorePackagesPath>../../packages</RestorePackagesPath>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<NoWarn>$(NoWarn);CS0168</NoWarn>
Comment thread
SENya1990 marked this conversation as resolved.
</PropertyGroup>
<PropertyGroup>
<PackageLicenseUrl>https://github.com/Acumatica/Acuminator/blob/dev/LICENSE</PackageLicenseUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,119 @@ public static void Clear<T>(this ConcurrentQueue<T>? queue)
}

/// <summary>
/// A Task extension method that attempts to await task which could be cancelled.
/// A Task extension method that attempts to await task which could be cancelled or faulted.
/// </summary>
/// <param name="task">The task to act on.</param>
/// <returns/>
public async static Task<bool> TryAwait(this Task? task)
/// <param name="logger">(Optional) The optional logger for non cancellation exceptions.</param>
/// <param name="continueOnCapturedContext">(Optional) True to continue on captured context.</param>
/// <returns/>
public async static Task<bool> TryAwait(this Task? task, Action<Exception>? logger = null,
bool continueOnCapturedContext = false)
{
if (task == null || task.IsCanceled || task.IsFaulted)
return false;

try
{
await task.ConfigureAwait(false);
await task.ConfigureAwait(continueOnCapturedContext);
return true;
}
catch (OperationCanceledException cancelledException)
{
return false;
}
catch (Exception exception)
{
logger?.Invoke(exception);
return false;
}
}

/// <summary>
/// A <see cref="ValueTask"/> extension method that attempts to await task which could be cancelled or faulted.
/// </summary>
/// <param name="task">The task to act on.</param>
/// <param name="logger">(Optional) The optional logger for non cancellation exceptions.</param>
/// <param name="continueOnCapturedContext">(Optional) True to continue on captured context.</param>
/// <returns/>
public async static ValueTask<bool> TryAwait(this ValueTask task, Action<Exception>? logger = null,
bool continueOnCapturedContext = false)
{
if (task.IsCanceled || task.IsFaulted)
return false;

try
{
await task.ConfigureAwait(continueOnCapturedContext);
return true;
}
catch (OperationCanceledException)
catch (OperationCanceledException cancelledException)
{
return false;
}
catch (Exception exception)
{
logger?.Invoke(exception);
return false;
}
}

/// <summary>
/// A <see cref="Task{TResult}"/> extension method that attempts to await task which could be cancelled.
/// A <see cref="Task{TResult}"/> extension method that attempts to await task which could be cancelled or faulted.
/// </summary>
/// <typeparam name="TResult">Type of the result.</typeparam>
/// <param name="task">The task to act on.</param>
/// <param name="logger">(Optional) The optional logger for non cancellation exceptions.</param>
/// <param name="continueOnCapturedContext">(Optional) True to continue on captured context.</param>
/// <returns/>
public async static Task<TaskResult<TResult>> TryAwait<TResult>(this Task<TResult>? task)
public async static Task<TaskResult<TResult>> TryAwait<TResult>(this Task<TResult>? task, Action<Exception>? logger = null,
bool continueOnCapturedContext = false)
{
if (task == null || task.IsCanceled || task.IsFaulted)
return new TaskResult<TResult>(false, default);

try
{
TResult? result = await task.ConfigureAwait(false);
TResult? result = await task.ConfigureAwait(continueOnCapturedContext);
return new TaskResult<TResult>(true, result);
}
catch (OperationCanceledException)
catch (OperationCanceledException cancelledException)
{
return new TaskResult<TResult>(false, default);
}
catch (Exception exception)
{
logger?.Invoke(exception);
return new TaskResult<TResult>(false, default);
}
}

/// <summary>
/// A <see cref="ValueTask{TResult}"/> extension method that attempts to await task which could be cancelled or faulted.
/// </summary>
/// <typeparam name="TResult">Type of the result.</typeparam>
/// <param name="task">The task to act on.</param>
/// <param name="logger">(Optional) The optional logger for non cancellation exceptions.</param>
/// <param name="continueOnCapturedContext">(Optional) True to continue on captured context.</param>
/// <returns/>
public async static ValueTask<TaskResult<TResult>> TryAwait<TResult>(this ValueTask<TResult> task, Action<Exception>? logger = null,
bool continueOnCapturedContext = false)
{
if (task.IsCanceled || task.IsFaulted)
return new TaskResult<TResult>(false, default);

try
{
TResult? result = await task.ConfigureAwait(continueOnCapturedContext);
return new TaskResult<TResult>(true, result);
}
catch (OperationCanceledException cancelledException)
{
return new TaskResult<TResult>(false, default);
}
catch (Exception exception)
{
logger?.Invoke(exception);
return new TaskResult<TResult>(false, default);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ namespace Acuminator.Utilities.Roslyn.Semantic
public static class SemanticModelUtils
{
/// <summary>
/// Safely analyse data flow for a <paramref name="node"/> and return <see cref="DataFlowAnalysis"/> if analysis succeeded.
/// Safely analyze data flow for a <paramref name="node"/> and return <see cref="DataFlowAnalysis"/> if analysis succeeded.
/// </summary>
/// <param name="semanticModel">The semanticModel to act on.</param>
/// <param name="node">The node to analyse.</param>
/// <param name="node">The node to analyze.</param>
/// <returns>
/// A <see cref="DataFlowAnalysis"/> if the data flow analysis succeeded, <see langword="null"/> if not.
/// </returns>
Expand Down Expand Up @@ -62,12 +62,14 @@ public static class SemanticModelUtils

[SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Aggregated await is used")]
public static async Task<(SemanticModel? SemanticModel, SyntaxNode? Root)> GetSemanticModelAndRootAsync(this Document document,
CancellationToken cancellation = default)
CancellationToken cancellation = default,
bool continueOnCapturedContext = false)
{
var semanticModelTask = document.CheckIfNull().GetSemanticModelAsync(cancellation);
var syntaxRootTask = document.GetSyntaxRootAsync(cancellation);

await Task.WhenAll(semanticModelTask, syntaxRootTask).ConfigureAwait(false);
await Task.WhenAll(semanticModelTask, syntaxRootTask)
.ConfigureAwait(continueOnCapturedContext);

return (semanticModelTask.Result, syntaxRootTask.Result);
}
Expand Down
3 changes: 0 additions & 3 deletions src/Acuminator/Acuminator.Vsix/AcuminatorVSPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,6 @@ private static void DeployCodeSnippets(AcuminatorMyDocumentsStorage? myDocuments
#region Package Settings
public bool ColoringEnabled => GeneralOptionsPage?.ColoringEnabled ?? AcuminatorConstants.Settings.Coloring.ColoringEnabledDefault;


public bool UseRegexColoring => GeneralOptionsPage?.UseRegexColoring ?? AcuminatorConstants.Settings.Coloring.UseRegexColoringDefault;

public bool UseBqlOutlining => GeneralOptionsPage?.UseBqlOutlining ?? AcuminatorConstants.Settings.Outlining.UseBqlOutliningDefault;

public bool UseBqlDetailedOutlining =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private BannedApiDeployer(AcuminatorMyDocumentsStorage myDocumentsStorage, strin
return null;
try
{
string bannedApiFolder = Path.Combine(myDocumentsStorage.AcuminatorFolder, Constants.BannedApi.BannnedApiFolder);
string bannedApiFolder = Path.Combine(myDocumentsStorage.AcuminatorFolder, Constants.BannedApi.BannedApiFolder);
return new BannedApiDeployer(myDocumentsStorage, bannedApiFolder);
}
catch (Exception e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand All @@ -13,10 +11,9 @@

namespace Acuminator.Vsix.Coloriser
{
public class BackgroundTagging : IDisposable
internal class BackgroundTagging : IDisposable
{
private static TaskScheduler? _vsTaskScheduler;

private static TaskScheduler? _vsTaskScheduler;
private CancellationTokenSource _cancellationTokenSource = new();

public CancellationToken CancellationToken => _cancellationTokenSource.Token;
Expand All @@ -27,10 +24,9 @@ public class BackgroundTagging : IDisposable

private BackgroundTagging()
{

}

public static BackgroundTagging StartBackgroundTagging(PXColorizerTaggerBase tagger)
public static BackgroundTagging StartBackgroundTagging(PXRoslynColorizerTagger tagger)
{
tagger.ThrowOnNull();

Expand All @@ -48,10 +44,22 @@ public static BackgroundTagging StartBackgroundTagging(PXColorizerTaggerBase tag
// No need for synchronization because FromCurrentSynchronizationContext creates schedulers which wrap around the same synchronization context
// Therefore all schedulers should be identical and nothing wrong will happen if different thread will create multiple instance of the scheduler in a race condition
_vsTaskScheduler = _vsTaskScheduler ?? TaskScheduler.FromCurrentSynchronizationContext();
backgroundTagging.TaggingTask = taggingTask.ContinueWith(task => AfterTaggingActionAsync(tagger, backgroundTagging.CancellationToken), //continuation should be on the UI thread
var continuationTask = taggingTask.ContinueWith(task => AfterTaggingActionAsync(task, tagger, backgroundTagging.CancellationToken), //continuation should be on the UI thread
backgroundTagging.CancellationToken,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskContinuationOptions.NotOnCanceled,
_vsTaskScheduler);

// ContinueWith schedules the lambda on the VS UI thread scheduler. The lambda runs on the UI thread and calls AfterTaggingActionAsync(...).
// Inside AfterTaggingActionAsync, the important path calls ThreadHelper.JoinableTaskFactory.RunAsync(tagger.RaiseTagsChangedAsync).Task
// this starts RaiseTagsChangedAsync and immediately returns the underlying Task representing it (still running).
// The lambda returns that inner Task immediately — it does not await it.
// The outer Task<Task> stored in TaggingTask is marked as Completed (RanToCompletion) at this point, because the lambda has returned.
// The outer task's result is the still-running inner task, but the outer task itself is done.
// RaiseTagsChangedAsync may still be running in the background raising tags-changed notifications.
//
// Thus, we need to keep the nested unwrapped task as the tagging task to be able to correctly calculate IsTaskRunning() and
// handle exceptions thrown in the AfterTaggingActionAsync.
backgroundTagging.TaggingTask = continuationTask.Unwrap();
return backgroundTagging;
}

Expand All @@ -76,12 +84,21 @@ public void Dispose()
_cancellationTokenSource.Dispose();
}

private static Task AfterTaggingActionAsync(PXColorizerTaggerBase tagger, CancellationToken cancellationToken)
private static Task AfterTaggingActionAsync(Task taggingTask, PXRoslynColorizerTagger tagger, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
if (taggingTask.IsCanceled || cancellationToken.IsCancellationRequested)
{
tagger.LastTaggingWasSuccessful = false;
return Task.FromCanceled(cancellationToken);

// We should be on UI thread here but the tagger.RaiseTagsChangedAsync switches to UI thread from non UI threads internally if needed
}

if (taggingTask.IsFaulted)
{
tagger.LastTaggingWasSuccessful = false;
return Task.FromException(taggingTask.Exception!);
}

// We should be on UI thread here but the tagger.RaiseTagsChangedAsync switches to UI thread from non UI threads internally if needed
return Shell.ThreadHelper.JoinableTaskFactory.RunAsync(tagger.RaiseTagsChangedAsync).Task;
}
}
Expand Down

This file was deleted.

Loading