Skip to content

Commit

Permalink
Make everything in the VSIX async
Browse files Browse the repository at this point in the history
Switch to main thread where needed
Switch off main thread for conversion itself
  • Loading branch information
GrahamTheCoder committed Jan 15, 2019
1 parent d4978c1 commit a52fc6e
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 156 deletions.
9 changes: 5 additions & 4 deletions ICSharpCode.CodeConverter/Shared/SolutionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ICSharpCode.CodeConverter.CSharp;
using Microsoft.CodeAnalysis;

Expand Down Expand Up @@ -39,13 +40,13 @@ private SolutionConverter(string solutionFilePath,
_languageConversion = languageConversion;
}

public IEnumerable<ConversionResult> Convert()
public Task<IEnumerable<ConversionResult>> Convert()
{
var projectsToUpdateReferencesOnly = _projectsToConvert.First().Solution.Projects.Except(_projectsToConvert);

return ConvertProjects()
.Concat(UpdateProjectReferences(projectsToUpdateReferencesOnly))
.Concat(new[] { ConvertSolutionFile() });
return Task.FromResult(ConvertProjects()
.Concat(UpdateProjectReferences(projectsToUpdateReferencesOnly))
.Concat(new[] { ConvertSolutionFile() }));
}

private IEnumerable<ConversionResult> ConvertProjects()
Expand Down
2 changes: 1 addition & 1 deletion Tests/TestRunners/ProjectConverterTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class ProjectConverterTestBase
? LanguageNames.VisualBasic
: LanguageNames.CSharp;
var projectsToConvert = solution.Projects.Where(p => p.Language == languageNameToConvert && shouldConvertProject(p)).ToArray();
var conversionResults = SolutionConverter.CreateFor<TLanguageConversion>(projectsToConvert).Convert().ToDictionary(c => c.TargetPathOrNull, StringComparer.OrdinalIgnoreCase);
var conversionResults = SolutionConverter.CreateFor<TLanguageConversion>(projectsToConvert).Convert().GetAwaiter().GetResult().ToDictionary(c => c.TargetPathOrNull, StringComparer.OrdinalIgnoreCase);
var expectedResultDirectory = GetExpectedResultDirectory<TLanguageConversion>(testName);

try {
Expand Down
112 changes: 59 additions & 53 deletions Vsix/CodeConversion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using ICSharpCode.CodeConverter.Shared;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
Expand All @@ -28,44 +29,50 @@ class CodeConversion
private readonly VisualStudioInteraction.OutputWindow _outputWindow;
private string SolutionDir => Path.GetDirectoryName(_visualStudioWorkspace.CurrentSolution.FilePath);

public static async Task<CodeConversion> CreateAsync(REConverterPackage serviceProvider, VisualStudioWorkspace visualStudioWorkspace, Func<ConverterOptionsPage> getOptions)
{
return new CodeConversion(serviceProvider, visualStudioWorkspace,
getOptions, await VisualStudioInteraction.OutputWindow.CreateAsync());
}

public CodeConversion(IServiceProvider serviceProvider, VisualStudioWorkspace visualStudioWorkspace,
Func<ConverterOptionsPage> getOptions)
Func<ConverterOptionsPage> getOptions, VisualStudioInteraction.OutputWindow outputWindow)
{
GetOptions = getOptions;
_serviceProvider = serviceProvider;
_visualStudioWorkspace = visualStudioWorkspace;
_outputWindow = new VisualStudioInteraction.OutputWindow();
_outputWindow = outputWindow;
}

public async Task PerformProjectConversion<TLanguageConversion>(IReadOnlyCollection<Project> selectedProjects) where TLanguageConversion : ILanguageConversion, new()
public async Task PerformProjectConversionAsync<TLanguageConversion>(IReadOnlyCollection<Project> selectedProjects) where TLanguageConversion : ILanguageConversion, new()
{
await Task.Run(() => {
var convertedFiles = ConvertProjectUnhandled<TLanguageConversion>(selectedProjects);
WriteConvertedFilesAndShowSummary(convertedFiles);
await Task.Run(async () => {
var convertedFiles = ConvertProjectUnhandledAsync<TLanguageConversion>(selectedProjects);
await WriteConvertedFilesAndShowSummaryAsync(convertedFiles);
});
}

public async Task PerformDocumentConversion<TLanguageConversion>(string documentFilePath, Span selected) where TLanguageConversion : ILanguageConversion, new()
public async Task PerformDocumentConversionAsync<TLanguageConversion>(string documentFilePath, Span selected) where TLanguageConversion : ILanguageConversion, new()
{
var conversionResult = await Task.Run(async () => {
var result = await ConvertDocumentUnhandled<TLanguageConversion>(documentFilePath, selected);
WriteConvertedFilesAndShowSummary(new[] { result });
var result = await ConvertDocumentUnhandledAsync<TLanguageConversion>(documentFilePath, selected);
await WriteConvertedFilesAndShowSummaryAsync(new[] { result });
return result;
});

if (GetOptions().CopyResultToClipboardForSingleDocument) {
Clipboard.SetText(conversionResult.ConvertedCode ?? conversionResult.GetExceptionsAsString());
_outputWindow.WriteToOutputWindow("Conversion result copied to clipboard.");
VisualStudioInteraction.ShowMessageBox(_serviceProvider, "Conversion result copied to clipboard.", conversionResult.GetExceptionsAsString(), false);
await _outputWindow.WriteToOutputWindowAsync("Conversion result copied to clipboard.");
await VisualStudioInteraction.ShowMessageBoxAsync(_serviceProvider, "Conversion result copied to clipboard.", conversionResult.GetExceptionsAsString(), false);
}

}

private void WriteConvertedFilesAndShowSummary(IEnumerable<ConversionResult> convertedFiles)
private async Task WriteConvertedFilesAndShowSummaryAsync(IEnumerable<ConversionResult> convertedFiles)
{
_outputWindow.Clear();
_outputWindow.WriteToOutputWindow(Intro);
_outputWindow.ForceShowOutputPane();
await _outputWindow.ClearAsync();
await _outputWindow.WriteToOutputWindowAsync(Intro);
await _outputWindow.ForceShowOutputPaneAsync();

var files = new List<string>();
var filesToOverwrite = new List<ConversionResult>();
Expand All @@ -80,7 +87,7 @@ private void WriteConvertedFilesAndShowSummary(IEnumerable<ConversionResult> con
continue;
}

LogProgress(convertedFile, errors);
await LogProgressAsync(convertedFile, errors);
if (string.IsNullOrWhiteSpace(convertedFile.ConvertedCode)) continue;

files.Add(convertedFile.TargetPathOrNull);
Expand All @@ -93,46 +100,46 @@ private void WriteConvertedFilesAndShowSummary(IEnumerable<ConversionResult> con
convertedFile.WriteToFile();
}

FinalizeConversion(files, errors, longestFilePath, filesToOverwrite);
await FinalizeConversionAsync(files, errors, longestFilePath, filesToOverwrite);
}

private void FinalizeConversion(List<string> files, List<string> errors, string longestFilePath, List<ConversionResult> filesToOverwrite)
private async Task FinalizeConversionAsync(List<string> files, List<string> errors, string longestFilePath, List<ConversionResult> filesToOverwrite)
{
var options = GetOptions();

var pathsToOverwrite = filesToOverwrite.Select(f => PathRelativeToSolutionDir(f.SourcePathOrNull));
var shouldOverwriteSolutionAndProjectFiles =
filesToOverwrite.Any() &&
(options.AlwaysOverwriteFiles || UserHasConfirmedOverwrite(files, errors, pathsToOverwrite.ToList()));
(options.AlwaysOverwriteFiles || await UserHasConfirmedOverwriteAsync(files, errors, pathsToOverwrite.ToList()));

if (shouldOverwriteSolutionAndProjectFiles)
{
var titleMessage = options.CreateBackups ? "Creating backups and overwriting files:" : "Overwriting files:" + "";
_outputWindow.WriteToOutputWindow(Environment.NewLine + titleMessage);
await _outputWindow.WriteToOutputWindowAsync(Environment.NewLine + titleMessage);
foreach (var fileToOverwrite in filesToOverwrite)
{
if (options.CreateBackups) File.Copy(fileToOverwrite.SourcePathOrNull, fileToOverwrite.SourcePathOrNull + ".bak", true);
fileToOverwrite.WriteToFile();

var targetPathRelativeToSolutionDir = PathRelativeToSolutionDir(fileToOverwrite.TargetPathOrNull);
_outputWindow.WriteToOutputWindow($"* {targetPathRelativeToSolutionDir}");
await _outputWindow.WriteToOutputWindowAsync($"* {targetPathRelativeToSolutionDir}");
}
files = files.Concat(filesToOverwrite.Select(f => f.SourcePathOrNull)).ToList();
} else if (longestFilePath != null) {
VisualStudioInteraction.OpenFile(new FileInfo(longestFilePath)).SelectAll();
await (await VisualStudioInteraction.OpenFileAsync(new FileInfo(longestFilePath))).SelectAllAsync();
}

var conversionSummary = GetConversionSummary(files, errors);
_outputWindow.WriteToOutputWindow(conversionSummary);
_outputWindow.ForceShowOutputPane();
var conversionSummary = await GetConversionSummaryAsync(files, errors);
await _outputWindow.WriteToOutputWindowAsync(conversionSummary);
await _outputWindow.ForceShowOutputPaneAsync();

}

private bool UserHasConfirmedOverwrite(List<string> files, List<string> errors, IReadOnlyCollection<string> pathsToOverwrite)
private Task<bool> UserHasConfirmedOverwriteAsync(List<string> files, List<string> errors, IReadOnlyCollection<string> pathsToOverwrite)
{
var maxExamples = 30; // Avoid a huge unreadable dialog going off the screen
var exampleText = pathsToOverwrite.Count > maxExamples ? $". First {maxExamples} examples" : "";
return VisualStudioInteraction.ShowMessageBox(_serviceProvider,
return VisualStudioInteraction.ShowMessageBoxAsync(_serviceProvider,
"Overwrite solution and referencing projects?",
$@"The current solution file and any referencing projects will be overwritten to reference the new project(s){exampleText}:
* {string.Join(Environment.NewLine + "* ", pathsToOverwrite.Take(maxExamples))}
Expand All @@ -146,7 +153,7 @@ private static bool WillOverwriteSource(ConversionResult convertedFile)
return string.Equals(convertedFile.SourcePathOrNull, convertedFile.TargetPathOrNull, StringComparison.OrdinalIgnoreCase);
}

private void LogProgress(ConversionResult convertedFile, List<string> errors)
private async Task LogProgressAsync(ConversionResult convertedFile, List<string> errors)
{
var exceptionsAsString = convertedFile.GetExceptionsAsString();
var indentedException = exceptionsAsString.Replace(Environment.NewLine, Environment.NewLine + " ").TrimEnd();
Expand All @@ -167,7 +174,7 @@ private void LogProgress(ConversionResult convertedFile, List<string> errors)
output += $" contains errors{Environment.NewLine} {indentedException}";
}

_outputWindow.WriteToOutputWindow(output);
await _outputWindow.WriteToOutputWindowAsync(output);
}

private string PathRelativeToSolutionDir(string path)
Expand All @@ -176,7 +183,7 @@ private string PathRelativeToSolutionDir(string path)
.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}

private string GetConversionSummary(IReadOnlyCollection<string> files, IReadOnlyCollection<string> errors)
private async Task<string> GetConversionSummaryAsync(IReadOnlyCollection<string> files, IReadOnlyCollection<string> errors)
{
var oneLine = "Code conversion failed";
var successSummary = "";
Expand All @@ -195,14 +202,14 @@ private string GetConversionSummary(IReadOnlyCollection<string> files, IReadOnly
successSummary += Environment.NewLine + "Please report issues at https://github.com/icsharpcode/CodeConverter/issues";
}

WriteStatusBarText(oneLine + " - see output window");
await VisualStudioInteraction.WriteStatusBarTextAsync(_serviceProvider, oneLine + " - see output window");
return Environment.NewLine + Environment.NewLine
+ oneLine
+ Environment.NewLine + successSummary
+ Environment.NewLine;
}

async Task<ConversionResult> ConvertDocumentUnhandled<TLanguageConversion>(string documentPath, Span selected) where TLanguageConversion : ILanguageConversion, new()
async Task<ConversionResult> ConvertDocumentUnhandledAsync<TLanguageConversion>(string documentPath, Span selected) where TLanguageConversion : ILanguageConversion, new()
{
//TODO Figure out when there are multiple document ids for a single file path
var documentId = _visualStudioWorkspace.CurrentSolution.GetDocumentIdsWithFilePath(documentPath).SingleOrDefault();
Expand Down Expand Up @@ -232,32 +239,31 @@ private static ConversionResult ConvertTextOnly<TLanguageConversion>(string docu
return convertTextOnly;
}

private IEnumerable<ConversionResult> ConvertProjectUnhandled<TLanguageConversion>(IReadOnlyCollection<Project> selectedProjects)
private async Task<IEnumerable<ConversionResult>> ConvertProjectUnhandledAsync<TLanguageConversion>(IReadOnlyCollection<Project> selectedProjects)
where TLanguageConversion : ILanguageConversion, new()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var projectsByPath =
_visualStudioWorkspace.CurrentSolution.Projects.ToLookup(p => p.FilePath, p => p);
#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread - ToList ensures this happens within the same thread just switched to above
var projects = selectedProjects.Select(p => projectsByPath[p.FullName].First()).ToList();
var convertedFiles = SolutionConverter.CreateFor<TLanguageConversion>(projects, new Progress<string>(s => _outputWindow.WriteToOutputWindow(Environment.NewLine + s))).Convert();
return convertedFiles;
#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
var solutionConverter = SolutionConverter.CreateFor<TLanguageConversion>(projects, new Progress<string>(s => {
var unusedFireAndForget = LogProgressAsync(s);
}));

return await ThreadHelper.JoinableTaskFactory.RunAsync(() => solutionConverter.Convert());
}

void WriteStatusBarText(string text)
private async Task LogProgressAsync(string s)
{
IVsStatusbar statusBar = (IVsStatusbar)_serviceProvider.GetService(typeof(SVsStatusbar));
if (statusBar == null)
return;

int frozen;
statusBar.IsFrozen(out frozen);
if (frozen != 0) {
statusBar.FreezeOutput(0);
try {
await _outputWindow.WriteToOutputWindowAsync(Environment.NewLine + s);
} catch (Exception) {
//Logging failed. TODO consider logging such issues to external file
}

statusBar.SetText(text);
statusBar.FreezeOutput(1);
}

public static bool IsCSFileName(string fileName)
{
return fileName.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
Expand All @@ -268,22 +274,22 @@ public static bool IsVBFileName(string fileName)
return fileName.EndsWith(".vb", StringComparison.OrdinalIgnoreCase);
}

public ITextSelection GetSelectionInCurrentView(Func<string, bool> predicate)
public async Task<ITextSelection> GetSelectionInCurrentViewAsync(Func<string, bool> predicate)
{
IWpfTextViewHost viewHost = GetCurrentViewHost(predicate);
IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(predicate);
if (viewHost == null)
return null;

return viewHost.TextView.Selection;
}

public IWpfTextViewHost GetCurrentViewHost(Func<string, bool> predicate)
public async Task<IWpfTextViewHost> GetCurrentViewHostAsync(Func<string, bool> predicate)
{
IWpfTextViewHost viewHost = VisualStudioInteraction.GetCurrentViewHost(_serviceProvider);
IWpfTextViewHost viewHost = await VisualStudioInteraction.GetCurrentViewHostAsync(_serviceProvider);
if (viewHost == null)
return null;

ITextDocument textDocument = viewHost.GetTextDocument();
ITextDocument textDocument = await viewHost.GetTextDocumentAsync();
if (textDocument == null || !predicate(textDocument.FilePath))
return null;

Expand Down
Loading

0 comments on commit a52fc6e

Please sign in to comment.