Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Visual Studio Community Toolkit to access common services, fixes #44 #45

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
142 changes: 68 additions & 74 deletions src/FantomasVs.Shared/FantomasHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;

using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio;
using ThreadHelper = Microsoft.VisualStudio.Shell.ThreadHelper;
using ThreadedWaitDialogHelper = Microsoft.VisualStudio.Shell.ThreadedWaitDialogHelper;

using Fantomas.Client;
using FantomasResponseCode = Fantomas.Client.LSPFantomasServiceTypes.FantomasResponseCode;
using Microsoft.VisualStudio.Threading;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;

namespace FantomasVs
{
Expand All @@ -31,6 +32,14 @@ public partial class FantomasHandler :
ICommandHandler<FormatSelectionCommandArgs>,
ICommandHandler<SaveCommandArgs>
{
[ImportingConstructor]
public FantomasHandler(Contracts.FantomasService fantomasService)
{
service = fantomasService;
}

private Contracts.FantomasService service;

public string DisplayName => "Automatic Formatting";

#region Patching
Expand Down Expand Up @@ -147,16 +156,13 @@ public enum FormatKind
public async Task<bool> FormatAsync(SnapshotSpan vspan, EditorCommandArgs args, CommandExecutionContext context, FormatKind kind)
{
var token = context.OperationContext.UserCancellationToken;
var instance = await FantomasVsPackage.Instance.WithCancellation(token);

await SetStatusAsync("Formatting...", instance, token);
await VS.StatusBar.ShowMessageAsync("Formatting...");
await Task.Yield();

var buffer = args.TextView.TextBuffer;
var caret = args.TextView.Caret.Position;

var service = instance.FantomasService;
var fantopts = instance.Options;
var fantopts = await Formatting.GetLiveInstanceAsync();
var document = buffer.Properties.GetProperty<ITextDocument>(typeof(ITextDocument));
var path = document.FilePath;
var workingDir = System.IO.Path.GetDirectoryName(path);
Expand Down Expand Up @@ -204,28 +210,28 @@ public async Task<bool> FormatAsync(SnapshotSpan vspan, EditorCommandArgs args,
break;
case FantomasResponseCode.ToolNotFound:
{
var view = new InstallChoiceWindow();
await VS.StatusBar.ShowMessageAsync("Fantomas tool not installed");
// prevent editor command wait dialog
context.OperationContext.TakeOwnership();
var view = new InstallChoiceWindow();
var result = await InstallAsync(view.GetDialogAction(), workingDir, token);
await VS.StatusBar.ClearAsync();
switch (result)
{
case InstallResult.Succeded:
{
InstallResultDialog.ShowDialog("Fantomas Tool was succesfully installed!");
using (var session = ThreadedWaitDialogHelper.StartWaitDialog(instance.DialogFactory, "Starting instance..."))
{
await FormatAsync(vspan, args, context, kind);
}
await VS.StatusBar.ShowMessageAsync("Starting Fantomas instance...");
await FormatAsync(vspan, args, context, kind);
break;
}
case InstallResult.Failed:
{
hasError = true;
InstallResultDialog.ShowDialog("Fantomas Tool could not be installed. You may not have a tool manifest set up. Please check the log for details.");
await FocusLogAsync(token);
await FocusLogAsync();
break;
}
}

break;
}

Expand All @@ -235,40 +241,40 @@ public async Task<bool> FormatAsync(SnapshotSpan vspan, EditorCommandArgs args,
{
hasError = true;
var error = response.Content.Value;
await SetStatusAsync($"Could not format: {error.Replace(path, "")}", instance, token);
await WriteLogAsync(error, token);
await FocusLogAsync(token);
await VS.StatusBar.ShowMessageAsync($"Could not format: {error.Replace(path, "")}");
await WriteLogAsync(error);
await FocusLogAsync();
break;
}
case FantomasResponseCode.DaemonCreationFailed:
{
await WriteLogAsync($"Creating the Fantomas Daemon failed:\n{response.Content?.Value}", token);
await FocusLogAsync(token);
await WriteLogAsync($"Creating the Fantomas Daemon failed:\n{response.Content?.Value}");
await FocusLogAsync();
hasError = true;
break;
}
default:
throw new NotSupportedException($"The {nameof(FantomasResponseCode)} value '{response.Code}' is unexpected.\n Error: {response.Content?.Value}");
}

if(hasError)
if (hasError)
{
await WriteLogAsync("Attempting to find Fantomas Tool...", token);
await WriteLogAsync("Attempting to find Fantomas Tool...");
var folder = LSPFantomasServiceTypes.Folder.NewFolder(workingDir);
var toolLocation = FantomasToolLocator.findFantomasTool(folder);
var result = toolLocation.IsError ? $"Failed to find tool: {toolLocation.ErrorValue}" : $"Found at: {toolLocation.ResultValue}";
await WriteLogAsync(result, token);
await WriteLogAsync(result);
}
}
catch (NotSupportedException ex)
{
await WriteLogAsync($"The operation is not supported:\n {ex.Message}", token);
await WriteLogAsync($"The operation is not supported:\n {ex.Message}");
}
catch (Exception ex)
{
hasError = true;
await WriteLogAsync($"The formatting operation failed:\n {ex}", token);
await SetStatusAsync($"Could not format: {ex.Message.Replace(path, "")}", instance, token);
await WriteLogAsync($"The formatting operation failed:\n {ex}");
await VS.StatusBar.ShowMessageAsync($"Could not format: {ex.Message.Replace(path, "")}");
}

args.TextView.Caret.MoveTo(
Expand All @@ -282,8 +288,7 @@ public async Task<bool> FormatAsync(SnapshotSpan vspan, EditorCommandArgs args,
vspan.TranslateTo(args.TextView.TextSnapshot, SpanTrackingMode.EdgeInclusive),
false);

if (hasError) await Task.Delay(2000);
await SetStatusAsync("Ready.", instance, token);
if (!hasError) { await VS.StatusBar.ClearAsync(); }

return hasDiff;
}
Expand Down Expand Up @@ -324,6 +329,7 @@ public async Task<bool> FormatAsync(SnapshotSpan vspan, EditorCommandArgs args,

public async Task<InstallResult> InstallAsync(InstallAction installAction, string workingDir, CancellationToken token)
{
await VS.StatusBar.ShowMessageAsync("Installing Fantomas tool...");
async Task<InstallResult> LaunchUrl(string uri)
{
try
Expand All @@ -332,21 +338,21 @@ async Task<InstallResult> LaunchUrl(string uri)
}
catch (Exception ex)
{
await WriteLogAsync($"Failed to launch url: {uri}\n{ex}", token);
await WriteLogAsync($"Failed to launch url: {uri}\n{ex}");
}

return InstallResult.Skipped;
}

async Task<InstallResult> LaunchDotnet(string caption, string args)
{
await WriteLogAsync(caption, token);
await WriteLogAsync("Running dotnet installation...", token);
await WriteLogAsync(caption);
await WriteLogAsync("Running dotnet installation...");
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(token);
var instance = await FantomasVsPackage.Instance;
using var session = ThreadedWaitDialogHelper.StartWaitDialog(instance.DialogFactory, caption);
var fac = (IVsThreadedWaitDialogFactory)await VS.Services.GetThreadedWaitDialogAsync();
using var session = fac.StartWaitDialog(caption);
var (success, output) = await RunProcessAsync("dotnet", args, workingDir, session.UserCancellationToken);
await WriteLogAsync(output, token);
await WriteLogAsync(output);
return success ? InstallResult.Succeded : InstallResult.Failed;
}

Expand Down Expand Up @@ -386,50 +392,38 @@ public Task<bool> FormatAsync(EditorCommandArgs args, CommandExecutionContext co
return FormatAsync(vspan, args, context, FormatKind.Document);
}

protected async Task SetStatusAsync(string text, FantomasVsPackage instance, CancellationToken token)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(token);
var statusBar = instance.Statusbar;
// Make sure the status bar is not frozen

if (statusBar.IsFrozen(out var frozen) == VSConstants.S_OK && frozen != 0)
statusBar.FreezeOutput(0);

// Set the status bar text and make its display static.
statusBar.SetText(text);
}

#endregion

#region Output Window

public OuptutLogging Logging { get; } = new();
public Task WriteLogAsync(string text) => OutputLogging.LogTextAsync(text);

public Task WriteLogAsync(string text, CancellationToken token) => Logging.LogTextAsync(text, token);

public Task FocusLogAsync(CancellationToken token) => Logging.BringToFrontAsync(token);
public Task FocusLogAsync() => OutputLogging.BringToFrontAsync();

#endregion

#region Logging

protected void LogTask(Task task)
public void LogTask<T>(Func<Task<T>> asyncMethod)
{
var _ = task.ContinueWith(async t =>
try
{
if (t.IsFaulted)
await WriteLogAsync(t.Exception.ToString(), CancellationToken.None);

}, TaskScheduler.Default);
ThreadHelper.JoinableTaskFactory.Run(asyncMethod);
}
catch (Exception ex)
{
_ = ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
{
await WriteLogAsync(ex.ToString());
await Task.Delay(2000);
await VS.StatusBar.ClearAsync();
});
}
}

#endregion

#region Format Document

public bool ExecuteCommand(FormatDocumentCommandArgs args, CommandExecutionContext executionContext)
public bool ExecuteCommand(FormatDocumentCommandArgs args, CommandExecutionContext context)
{
LogTask(FormatAsync(args, executionContext));
LogTask(() => FormatAsync(args, context));
return CommandHandled;
}

Expand Down Expand Up @@ -458,7 +452,7 @@ public bool ExecuteCommand(FormatSelectionCommandArgs args, CommandExecutionCont
return false;

var vspan = new SnapshotSpan(args.TextView.TextSnapshot, selections.Single().Span);
LogTask(FormatAsync(vspan, args, executionContext, FormatKind.Selection));
LogTask(() => FormatAsync(vspan, args, executionContext, FormatKind.Selection));
return CommandHandled;
}

Expand All @@ -473,28 +467,28 @@ public CommandState GetCommandState(SaveCommandArgs args)

public bool ExecuteCommand(SaveCommandArgs args, CommandExecutionContext executionContext)
{
LogTask(FormatOnSaveAsync(args, executionContext));
LogTask(() => FormatOnSaveAsync(args, executionContext));
return false;
}

protected async Task FormatOnSaveAsync(SaveCommandArgs args, CommandExecutionContext executionContext)
protected async Task<bool> FormatOnSaveAsync(SaveCommandArgs args, CommandExecutionContext executionContext)
{
var instance = await FantomasVsPackage.Instance;
if (!instance.Options.FormatOnSave)
return;
var options = await Formatting.GetLiveInstanceAsync();
if (!options.FormatOnSave)
return false;

var hasDiff = await FormatAsync(args, executionContext);

if (!hasDiff || !instance.Options.CommitChanges)
return;
if (!hasDiff || !options.CommitChanges)
return false;

var buffer = args.SubjectBuffer;
var document = buffer.Properties.GetProperty<ITextDocument>(typeof(ITextDocument));

document?.Save();
return true;
}

#endregion
}
}

}
24 changes: 19 additions & 5 deletions src/FantomasVs.Shared/FantomasOptionsPage.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
using System;
using System.ComponentModel;
using Microsoft.VisualStudio.Shell;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Community.VisualStudio.Toolkit;

namespace FantomasVs
{
[Guid(GuidString)]
public class FantomasOptionsPage : DialogPage
//[Guid(GuidString)]
//[ComVisible(true)]
internal partial class OptionsProvider
{
public const string GuidString = "74927147-72e8-4b47-a70d-5568807d6878";

// Register the options with these attributes on your package class:
// [ProvideOptionPage(typeof(OptionsProvider.GeneralOptions), "MyExtension", "General", 0, 0, true)]
// [ProvideProfile(typeof(OptionsProvider.GeneralOptions), "MyExtension", "General", 0, 0, true)]
[ComVisible(true)]
public class FormattingOptions : BaseOptionPage<Formatting> { }
}

public class Formatting : BaseOptionModel<Formatting>
{

#region Performance

[Category("Performance")]
[DisplayName("Apply As Diff")]
[Description("Applies the formatting as changes, which shows which lines were changed. Turn off if computing the diff is too slow. ")]
[DefaultValue(true)]
public bool ApplyDiff { get; set; } = true;

[Category("Performance")]
[DisplayName("Enable SpaceBar Heating")]
[Description("xkcd/1172")]
[DefaultValue(false)]
public bool EnableSpaceBarHeating { get; set; } = false;

#endregion
Expand All @@ -29,11 +41,13 @@ public class FantomasOptionsPage : DialogPage
[Category("On Save")]
[DisplayName("Format On Save")]
[Description("This triggers a formatting whenever you hit save")]
[DefaultValue(false)]
public bool FormatOnSave { get; set; } = false;

[Category("On Save")]
[DisplayName("Commit Changes")]
[Description("Set this to false if you don't want to commit formatting changes to the file unless you hit save once again")]
[DefaultValue(true)]
public bool CommitChanges { get; set; } = true;

#endregion
Expand Down
13 changes: 13 additions & 0 deletions src/FantomasVs.Shared/FantomasServiceExport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel.Composition;

using static Fantomas.Client.Contracts;
using static Fantomas.Client.LSPFantomasService;

namespace FantomasVs
{
internal class FantomasServiceExport
{
[Export(typeof(FantomasService))]
public FantomasService FantomasService => new LSPFantomasService();
}
}
3 changes: 2 additions & 1 deletion src/FantomasVs.Shared/FantomasVs.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
<Compile Include="$(MSBuildThisFileDirectory)FantomasOptionsPage.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)FantomasServiceExport.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FantomasVsPackage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)InstallChoice.xaml.cs">
<DependentUpon>InstallChoice.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)InstallResultDialog.xaml.cs">
<DependentUpon>InstallResultDialog.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)OuptutLogging.cs" />
<Compile Include="$(MSBuildThisFileDirectory)OutputLogging.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Theme.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PredefinedCommandHandlerNames.cs" />
</ItemGroup>
Expand Down
Loading