Skip to content

Commit

Permalink
Fix CodingConventions support in 17.10 (#125)
Browse files Browse the repository at this point in the history
* In Dev17, use IEditorOptions to retrieve coding conventions instead of the CodingConventions package

* Remove global.json
Because VS 2019 doesn't support dotnet 8 and VS 2022 doesn't install dotnet 7
Let each version pick the latest supported SDK.

* Revert back to 17.0 SDK

* Seal CodingConventions class
  • Loading branch information
pharring committed Jan 27, 2024
1 parent f4d85d3 commit c6edb6f
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 39 deletions.
6 changes: 0 additions & 6 deletions global.json

This file was deleted.

46 changes: 22 additions & 24 deletions src/ColumnGuide/ColumnGuideAdornment.cs
@@ -1,7 +1,6 @@
// Copyright (c) Paul Harrington. All Rights Reserved. Licensed under the MIT License. See LICENSE in the project root for license information.

using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.VisualStudio.CodingConventions;
using Microsoft.VisualStudio.Text.Editor;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -65,19 +64,18 @@ internal class ColumnGuideAdornment : IDisposable
/// <param name="view">The <see cref="IWpfTextView"/> upon which the adornment will be drawn</param>
/// <param name="settings">The guideline settings.</param>
/// <param name="guidelineBrush">The guideline brush.</param>
/// <param name="codingConventionsManager">The coding conventions manager for handling .editorconfig settings.</param>
/// <param name="telemetry">Telemetry interface.</param>
public ColumnGuideAdornment(IWpfTextView view, ITextEditorGuidesSettings settings, GuidelineBrush guidelineBrush, ICodingConventionsManager codingConventionsManager)
/// <param name="codingConventions">The coding conventions manager for handling .editorconfig settings.</param>
public ColumnGuideAdornment(IWpfTextView view, ITextEditorGuidesSettings settings, GuidelineBrush guidelineBrush, CodingConventions codingConventions)
{
_view = view;
_guidelineBrush = guidelineBrush;
_guidelineBrush.BrushChanged += GuidelineBrushChanged;
_strokeParameters = StrokeParameters.FromBrush(_guidelineBrush.Brush);

if (codingConventionsManager != null && view.TryGetTextDocument(out var textDocument))
if (codingConventions != null)
{
_codingConventionsCancellationTokenSource = new CancellationTokenSource();
var fireAndForgetTask = LoadGuidelinesFromEditorConfigAsync(codingConventionsManager, textDocument.FilePath);
var fireAndForgetTask = LoadGuidelinesFromEditorConfigAsync(codingConventions, view);
}

InitializeGuidelines(settings.GuideLinePositionsInChars);
Expand Down Expand Up @@ -274,32 +272,34 @@ private void AddGuidelinesToAdornmentLayer()
/// <summary>
/// Try to load guideline positions from .editorconfig
/// </summary>
/// <param name="codingConventionsManager">The coding conventions (.editorconfig) manager.</param>
/// <param name="filePath">Path to the document being edited.</param>
/// <param name="codingConventions">The coding conventions (.editorconfig) manager.</param>
/// <param name="view">Editor's WPF view.</param>
/// <returns>A task which completes when the convention has been loaded and applied.</returns>
private async Task LoadGuidelinesFromEditorConfigAsync(ICodingConventionsManager codingConventionsManager, string filePath)
private async Task LoadGuidelinesFromEditorConfigAsync(CodingConventions codingConventions, IWpfTextView view)
{
var cancellationToken = _codingConventionsCancellationTokenSource.Token;
var codingConventionContext = await codingConventionsManager.GetConventionContextAsync(filePath, cancellationToken).ConfigureAwait(false);
CancellationToken cancellationToken = _codingConventionsCancellationTokenSource.Token;
CodingConventions.Context context = await codingConventions.CreateContextAsync(view, cancellationToken).ConfigureAwait(false);
if (context is null)
{
return;
}

codingConventionContext.CodingConventionsChangedAsync += OnCodingConventionsChangedAsync;
cancellationToken.Register(() => codingConventionContext.CodingConventionsChangedAsync -= OnCodingConventionsChangedAsync);
context.ConventionsChanged += UpdateGuidelinesFromCodingConvention;
cancellationToken.Register(() => context.ConventionsChanged -= UpdateGuidelinesFromCodingConvention);

await UpdateGuidelinesFromCodingConventionAsync(codingConventionContext, cancellationToken).ConfigureAwait(false);
UpdateGuidelinesFromCodingConvention(context);
}

private Task OnCodingConventionsChangedAsync(object sender, CodingConventionsChangedEventArgs arg) => UpdateGuidelinesFromCodingConventionAsync((ICodingConventionContext)sender, _codingConventionsCancellationTokenSource.Token);

private Task UpdateGuidelinesFromCodingConventionAsync(ICodingConventionContext codingConventionContext, CancellationToken cancellationToken)
private void UpdateGuidelinesFromCodingConvention(CodingConventions.Context context)
{
if (cancellationToken.IsCancellationRequested)
if (_codingConventionsCancellationTokenSource.Token.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
return;
}

StrokeParameters strokeParameters = null;

if (codingConventionContext.CurrentConventions.TryGetConventionValue("guidelines_style", out string guidelines_style))
if (context.TryGetCurrentSetting("guidelines_style", out string guidelines_style))
{
if (TryParseStrokeParametersFromCodingConvention(guidelines_style, out strokeParameters))
{
Expand All @@ -310,13 +310,13 @@ private Task UpdateGuidelinesFromCodingConventionAsync(ICodingConventionContext

ICollection<Guideline> guidelines = null;

if (codingConventionContext.CurrentConventions.TryGetConventionValue("guidelines", out string guidelinesConventionValue))
if (context.TryGetCurrentSetting("guidelines", out string guidelinesConventionValue))
{
guidelines = ParseGuidelinesFromCodingConvention(guidelinesConventionValue, strokeParameters);
}

// Also support max_line_length: https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#max_line_length
if (codingConventionContext.CurrentConventions.TryGetConventionValue("max_line_length", out string max_line_length) && TryParsePosition(max_line_length, out int maxLineLengthValue))
if (context.TryGetCurrentSetting("max_line_length", out string max_line_length) && TryParsePosition(max_line_length, out int maxLineLengthValue))
{
(guidelines ?? (guidelines = new List<Guideline>())).Add(new Guideline(maxLineLengthValue, strokeParameters));
}
Expand Down Expand Up @@ -354,8 +354,6 @@ private Task UpdateGuidelinesFromCodingConventionAsync(ICodingConventionContext
Telemetry.Client.TrackEvent(eventTelemetry);
s_sentEditorConfigTelemetry = true;
}

return Task.CompletedTask;
}

private bool HaveGuidelinesChanged(IEnumerable<Guideline> newGuidelines)
Expand Down
9 changes: 4 additions & 5 deletions src/ColumnGuide/ColumnGuideFactory.cs
@@ -1,7 +1,6 @@
// Copyright (c) Paul Harrington. All Rights Reserved. Licensed under the MIT License. See LICENSE in the project root for license information.

using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.VisualStudio.CodingConventions;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using System.Collections.Generic;
Expand Down Expand Up @@ -42,7 +41,7 @@ public void TextViewCreated(IWpfTextView textView)
{
// Always create the adornment, even if there are no guidelines, since we
// respond to dynamic changes.
var _ = new ColumnGuideAdornment(textView, TextEditorGuidesSettings, GuidelineBrush, CodingConventionsManager);
var _ = new ColumnGuideAdornment(textView, TextEditorGuidesSettings, GuidelineBrush, CodingConventions);
}

public void OnImportsSatisfied()
Expand Down Expand Up @@ -120,11 +119,11 @@ internal static void AddGuidelinesToTelemetry(EventTelemetry eventTelemetry, IEn
[Import]
private GuidelineBrush GuidelineBrush { get; set; }

[Import(AllowDefault = true)]
private ICodingConventionsManager CodingConventionsManager { get; set; }
[Import]
private CodingConventions CodingConventions { get; set; }

[Import]
private HostServices HostServices { get; set; }
}
#endregion //Adornment Factory
#endregion //Adornment Factory
}
71 changes: 71 additions & 0 deletions src/VSIX/CodingConventions.cs
@@ -0,0 +1,71 @@
// Copyright (c) Paul Harrington. All Rights Reserved. Licensed under the MIT License. See LICENSE in the project root for license information.

using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.CodingConventions;
using Microsoft.VisualStudio.Text.Editor;

namespace EditorGuidelines
{
/// <summary>
/// Coding conventions support via Microsoft.VisualStudio.CodingConventions assembly.
/// </summary>
[Export]
internal sealed class CodingConventions
{
/// <summary>
/// Try to import from Microsoft.VisualStudio.CodingConventions.
/// </summary>
[Import(AllowDefault = true)]
private ICodingConventionsManager CodingConventionsManager { get; set; }

public async Task<Context> CreateContextAsync(IWpfTextView view, CancellationToken cancellationToken)
{
if (CodingConventionsManager is null)
{
// Coding Conventions not available in this SKU.
return null;
}

if (!view.TryGetTextDocument(out var textDocument))
{
return null;
}

string filePath = textDocument.FilePath;
ICodingConventionContext codingConventionContext = await CodingConventionsManager.GetConventionContextAsync(filePath, cancellationToken).ConfigureAwait(false);
return new Context(codingConventionContext, cancellationToken);
}

/// <summary>
/// Coding conventions narrowed to a single file.
/// </summary>
public class Context
{
private readonly ICodingConventionContext _innerContext;

public Context(ICodingConventionContext innerContext, CancellationToken cancellationToken)
{
_innerContext = innerContext;
_innerContext.CodingConventionsChangedAsync += OnCodingConventionsChangedAsync;
cancellationToken.Register(() => _innerContext.CodingConventionsChangedAsync -= OnCodingConventionsChangedAsync);
}

private Task OnCodingConventionsChangedAsync(object sender, CodingConventionsChangedEventArgs arg)
{
ConventionsChanged?.Invoke(this);
return Task.CompletedTask;
}

public delegate void ConventionsChangedEventHandler(Context sender);

public event ConventionsChangedEventHandler ConventionsChanged;

public bool TryGetCurrentSetting(string key, out string value)
{
return _innerContext.CurrentConventions.TryGetConventionValue(key, out value);
}
}
}
}
72 changes: 72 additions & 0 deletions src/VSIX_Dev17/CodingConventions.cs
@@ -0,0 +1,72 @@
// Copyright (c) Paul Harrington. All Rights Reserved. Licensed under the MIT License. See LICENSE in the project root for license information.

using System.ComponentModel.Composition;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.VisualStudio.Text.Editor;
using System.Collections.Generic;

namespace EditorGuidelines
{
/// <summary>
/// Dev 17 support for Coding Conventions. Uses the dictionary supplied via <see cref="IEditorOptions"/>.
/// </summary>
[Export]
internal sealed class CodingConventions
{
public Task<Context> CreateContextAsync(IWpfTextView view, CancellationToken cancellationToken)
{
return Task.FromResult(new Context(view.Options, cancellationToken));
}

/// <summary>
/// Coding conventions narrowed to a single file.
/// </summary>
public class Context
{
private readonly IEditorOptions _editorOptions;
private readonly CancellationToken _cancellationToken;
private IReadOnlyDictionary<string, object> _currentConventions;

// This is the same as DefaultOptions.RawCodingConventionsSnapshotOptionName from the 17.6
// editor SDK. However, by not referencing that constant, we can avoid taking a dependency
// on the 17.6 SDK and can continue to load on earlier versions (albeit without
// CodingConventions support).
private const string c_codingConventionsSnapshotOptionName = "CodingConventionsSnapshot";

public Context(IEditorOptions editorOptions, CancellationToken cancellationToken)
{
_editorOptions = editorOptions;
_cancellationToken = cancellationToken;

_editorOptions.OptionChanged += OnEditorOptionChanged;
_cancellationToken.Register(() => _editorOptions.OptionChanged -= OnEditorOptionChanged);
}

private void OnEditorOptionChanged(object sender, EditorOptionChangedEventArgs e)
{
if (e.OptionId == c_codingConventionsSnapshotOptionName)
{
_currentConventions = _editorOptions.GetOptionValue<IReadOnlyDictionary<string, object>>(c_codingConventionsSnapshotOptionName);
ConventionsChanged?.Invoke(this);
}
}

public delegate void ConventionsChangedEventHandler(Context sender);

public event ConventionsChangedEventHandler ConventionsChanged;

public bool TryGetCurrentSetting(string key, out string value)
{
if (_currentConventions is null || !_currentConventions.TryGetValue(key, out object obj) || obj is null)
{
value = null;
return false;
}

value = obj.ToString();
return !string.IsNullOrEmpty(value);
}
}
}
}
5 changes: 1 addition & 4 deletions src/VSIX_Dev17/VSIX_Dev17.csproj
Expand Up @@ -39,13 +39,10 @@

<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.1.0" />
<!-- Microsoft.VisualStudio.CodingConventions will be loaded from Common7\IDE\PublicAssemblies
at runtime, so we don't need to include it in the VSIX. -->
<PackageReference Include="Microsoft.VisualStudio.CodingConventions" Version="1.1.20180503.2" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.32112.339" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.5.4072">
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.8.2365">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down

0 comments on commit c6edb6f

Please sign in to comment.