Skip to content

Commit

Permalink
Get main edit menu working by not insisting the window is in focus
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamTheCoder committed Sep 15, 2019
1 parent 2e3511a commit d04d109
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 55 deletions.
23 changes: 11 additions & 12 deletions Vsix/CodeConversion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ public static async Task<CodeConversion> CreateAsync(REConverterPackage serviceP

public async Task PerformProjectConversionAsync<TLanguageConversion>(IReadOnlyCollection<Project> selectedProjects) where TLanguageConversion : ILanguageConversion, new()
{
await _outputWindow.ClearAsync();
await _outputWindow.ForceShowOutputPaneAsync();

await _joinableTaskFactory.RunAsync(async () => {
var convertedFiles = ConvertProjectUnhandledAsync<TLanguageConversion>(selectedProjects);
await WriteConvertedFilesAndShowSummaryAsync(await convertedFiles);
Expand All @@ -62,9 +59,6 @@ public async Task PerformProjectConversionAsync<TLanguageConversion>(IReadOnlyCo

public async Task PerformDocumentConversionAsync<TLanguageConversion>(string documentFilePath, Span selected) where TLanguageConversion : ILanguageConversion, new()
{
await _outputWindow.ClearAsync();
await _outputWindow.ForceShowOutputPaneAsync();

var conversionResult = await _joinableTaskFactory.RunAsync(async () => {
var result = await ConvertDocumentUnhandledAsync<TLanguageConversion>(documentFilePath, selected);
await WriteConvertedFilesAndShowSummaryAsync(new[] { result });
Expand All @@ -81,9 +75,7 @@ public async Task PerformDocumentConversionAsync<TLanguageConversion>(string doc

private async Task WriteConvertedFilesAndShowSummaryAsync(IEnumerable<ConversionResult> convertedFiles)
{
await _outputWindow.ClearAsync();
await _outputWindow.WriteToOutputWindowAsync(Intro);
await _outputWindow.ForceShowOutputPaneAsync();
await _outputWindow.WriteToOutputWindowAsync(Intro, true, true);

var files = new List<string>();
var filesToOverwrite = new List<ConversionResult>();
Expand Down Expand Up @@ -141,8 +133,7 @@ private async Task FinalizeConversionAsync(List<string> files, List<string> erro
}

var conversionSummary = await GetConversionSummaryAsync(files, errors);
await _outputWindow.WriteToOutputWindowAsync(conversionSummary);
await _outputWindow.ForceShowOutputPaneAsync();
await _outputWindow.WriteToOutputWindowAsync(conversionSummary, false, true);

}

Expand Down Expand Up @@ -221,7 +212,9 @@ private async Task<string> GetConversionSummaryAsync(IReadOnlyCollection<string>
}

private async Task<ConversionResult> ConvertDocumentUnhandledAsync<TLanguageConversion>(string documentPath, Span selected) where TLanguageConversion : ILanguageConversion, new()
{
{
await _outputWindow.WriteToOutputWindowAsync($"Converting {documentPath}...", true, true);

//TODO Figure out when there are multiple document ids for a single file path
var documentId = _visualStudioWorkspace.CurrentSolution.GetDocumentIdsWithFilePath(documentPath).SingleOrDefault();
if (documentId == null) {
Expand Down Expand Up @@ -251,12 +244,18 @@ private async Task<IEnumerable<ConversionResult>> ConvertProjectUnhandledAsync<T
where TLanguageConversion : ILanguageConversion, new()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var projectDesc = selectedProjects.Count == 1
? selectedProjects.Single().Name
: selectedProjects.Count + " projects";
await _outputWindow.WriteToOutputWindowAsync($"Converting {projectDesc}...", true, true);

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();
#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
await TaskScheduler.Default;

var solutionConverter = SolutionConverter.CreateFor<TLanguageConversion>(projects,
new Progress<ConversionProgress>(s => {
_outputWindow.WriteToOutputWindowAsync(FormatForOutputWindow(s)).ForgetNoThrow();
Expand Down
16 changes: 12 additions & 4 deletions Vsix/ConvertCSToVBCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService
// Command in main menu
var menuCommandId = new CommandID(CommandSet, MainMenuCommandId);
var menuItem = package.CreateCommand(CodeEditorMenuItemCallbackAsync, menuCommandId);
menuItem.BeforeQueryStatus += CodeEditorMenuItem_BeforeQueryStatusAsync;
menuItem.BeforeQueryStatus += MainEditMenuItem_BeforeQueryStatusAsync;
commandService.AddCommand(menuItem);

// Command in code editor's context menu
Expand Down Expand Up @@ -114,11 +114,19 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService
}
}

private async Task MainEditMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e)
{
if (sender is OleMenuCommand menuItem) {
var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsCSFileName, false);
menuItem.Visible = selectionInCurrentViewAsync != null;
}
}

private async Task CodeEditorMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e)
{
if (sender is OleMenuCommand menuItem) {
var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsCSFileName);
menuItem.Visible = selectionInCurrentViewAsync?.IsEmpty == false;
var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsCSFileName, true);
menuItem.Visible = selectionInCurrentViewAsync != null;
}
}

Expand Down Expand Up @@ -147,7 +155,7 @@ private async Task SolutionOrProjectMenuItem_BeforeQueryStatusAsync(object sende

private async Task CodeEditorMenuItemCallbackAsync(object sender, EventArgs e)
{
var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsCSFileName);
var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsCSFileName, false);
if (filePath != null && selection != null) {
await ConvertDocumentAsync(filePath, selection.Value);
}
Expand Down
16 changes: 12 additions & 4 deletions Vsix/ConvertVBToCSCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService
// Command in main menu
var menuCommandId = new CommandID(CommandSet, MainMenuCommandId);
var menuItem = package.CreateCommand(CodeEditorMenuItemCallbackAsync, menuCommandId);
menuItem.BeforeQueryStatus += CodeEditorMenuItem_BeforeQueryStatusAsync;
menuItem.BeforeQueryStatus += MainEditMenuItem_BeforeQueryStatusAsync;
commandService.AddCommand(menuItem);

// Command in code editor's context menu
Expand Down Expand Up @@ -113,11 +113,19 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService
}
}

private async Task MainEditMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e)
{
if (sender is OleMenuCommand menuItem) {
var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsVBFileName, true);
menuItem.Visible = selectionInCurrentViewAsync != null;
}
}

private async Task CodeEditorMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e)
{
if (sender is OleMenuCommand menuItem) {
var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsVBFileName);
menuItem.Visible = selectionInCurrentViewAsync?.IsEmpty == false;
var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsVBFileName, false);
menuItem.Visible = selectionInCurrentViewAsync != null;
}
}

Expand Down Expand Up @@ -146,7 +154,7 @@ private async Task SolutionOrProjectMenuItem_BeforeQueryStatusAsync(object sende

private async Task CodeEditorMenuItemCallbackAsync(object sender, EventArgs e)
{
var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsVBFileName);
var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsVBFileName, false);
if (filePath != null && selection != null) {
await ConvertDocumentAsync(filePath, selection.Value);
}
Expand Down
20 changes: 13 additions & 7 deletions Vsix/OutputWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,27 @@ public async Task ClearAsync()
public async Task ForceShowOutputPaneAsync()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
VisualStudioInteraction.Dte.Windows.Item(Constants.vsWindowKindOutput).Visible = true;
_outputPane.Activate();
ForceShowInner();
await TaskScheduler.Default;
}

public async Task WriteToOutputWindowAsync(string message)
public async Task WriteToOutputWindowAsync(string message, bool clearFirst = false, bool forceShow = false)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
lock (_outputPane) {
_hasOutputSinceSolutionOpened = true;;
_outputPane.OutputStringThreadSafe(message);
}
_hasOutputSinceSolutionOpened = true;
if (clearFirst) _outputPane.Clear();
_outputPane.OutputStringThreadSafe(message);
if (forceShow) ForceShowInner();
await TaskScheduler.Default;
}

private void ForceShowInner()
{
ThreadHelper.ThrowIfNotOnUIThread();
VisualStudioInteraction.Dte.Windows.Item(Constants.vsWindowKindOutput).Visible = true;
_outputPane.Activate();
}

#pragma warning disable VSTHRD100 // Avoid async void methods - fire and forget event handler
private async void OnSolutionOpened()
#pragma warning restore VSTHRD100 // Avoid async void methods
Expand Down
20 changes: 7 additions & 13 deletions Vsix/REConverterPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,15 @@
namespace CodeConverter.VsExtension
{
/// <summary>
/// This is the class that implements the package exposed by this assembly.
/// Implements the VS package exposed by this assembly.
///
/// This package will load when:
/// * Visual Studio has been configured not to support UIContextRules and has a solution with a csproj or vbproj
/// * Someone clicks one of the menu items
/// * Someone opens the options page (it doesn't need to load in this case, but it seems to anyway)
/// </summary>
/// <remarks>
/// <para>
/// The minimum requirement for a class to be considered a valid package for Visual Studio
/// is to implement the IVsPackage interface and register itself with the shell.
/// This package uses the helper classes defined inside the Managed Package Framework (MPF)
/// to do it: it derives from the Package class that provides the implementation of the
/// IVsPackage interface and uses the registration attributes defined in the framework to
/// register itself and its components with the shell. These attributes tell the pkgdef creation
/// utility what data to put into .pkgdef file.
/// </para>
/// <para>
/// To get loaded into VS, the package must be referred by &lt;Asset Type="Microsoft.VisualStudio.VsPackage" ...&gt; in .vsixmanifest file.
/// </para>
/// Until the package is loaded, converting a multiple selection of projects won't work because there's no way to set a ProvideUIContextRule that covers that case
/// </remarks>
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[InstalledProductRegistration("#110", "#112", "1.0")] // Info on this package for Help/About
Expand Down
31 changes: 16 additions & 15 deletions Vsix/VisualStudioInteraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,22 @@ public static async Task WriteStatusBarTextAsync(IAsyncServiceProvider servicePr
}

public static async Task<Span?> GetFirstSelectedSpanInCurrentViewAsync(IAsyncServiceProvider serviceProvider,
Func<string, bool> predicate)
Func<string, bool> predicate, bool mustHaveFocus)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var span = await FirstSelectedSpanInCurrentViewPrivateAsync(serviceProvider, predicate);
var span = await FirstSelectedSpanInCurrentViewPrivateAsync(serviceProvider, predicate, mustHaveFocus);
await TaskScheduler.Default;
return span;
}

public static async Task<(string FilePath, Span? Selection)> GetCurrentFilenameAndSelectionAsync(IAsyncServiceProvider asyncServiceProvider, Func<string, bool> predicate)
public static async Task<(string FilePath, Span? Selection)> GetCurrentFilenameAndSelectionAsync(
IAsyncServiceProvider asyncServiceProvider, Func<string, bool> predicate, bool mustHaveFocus)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

var span = await GetFirstSelectedSpanInCurrentViewAsync(asyncServiceProvider, predicate); ;
var span = await GetFirstSelectedSpanInCurrentViewAsync(asyncServiceProvider, predicate, mustHaveFocus); ;
var currentViewHostAsync =
await GetCurrentViewHostAsync(asyncServiceProvider, predicate);
await GetCurrentViewHostAsync(asyncServiceProvider, predicate, mustHaveFocus);
var textDocumentAsync = await currentViewHostAsync.GetTextDocumentAsync();
var result = (textDocumentAsync?.FilePath, span);

Expand Down Expand Up @@ -206,16 +207,15 @@ private static async Task<VsDocument> GetSingleSelectedItemOrDefaultAsync()
#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
}

private static async Task<IWpfTextViewHost> GetCurrentViewHostAsync(IAsyncServiceProvider serviceProvider)
private static async Task<IWpfTextViewHost> GetCurrentViewHostAsync(IAsyncServiceProvider serviceProvider, bool mustHaveFocus)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var txtMgr = await serviceProvider.GetServiceAsync<SVsTextManager, IVsTextManager>();
int mustHaveFocus = 1;
if (txtMgr == null) {
return null;
}

txtMgr.GetActiveView(mustHaveFocus, null, out IVsTextView vTextView);
txtMgr.GetActiveView(mustHaveFocus ? 1 : 0, null, out IVsTextView vTextView);
if (!(vTextView is IVsUserData userData)) {
return null;
}
Expand All @@ -226,27 +226,28 @@ private static async Task<IWpfTextViewHost> GetCurrentViewHostAsync(IAsyncServic
return holder as IWpfTextViewHost;
}

private static async Task<Span?> FirstSelectedSpanInCurrentViewPrivateAsync(IAsyncServiceProvider serviceProvider,
Func<string, bool> predicate)
private static async Task<Span?> FirstSelectedSpanInCurrentViewPrivateAsync(
IAsyncServiceProvider serviceProvider,
Func<string, bool> predicate, bool mustHaveFocus)
{
var selection = await GetSelectionInCurrentViewAsync(serviceProvider, predicate);
var selection = await GetSelectionInCurrentViewAsync(serviceProvider, predicate, mustHaveFocus);
return selection?.SelectedSpans.First().Span;
}

private static async Task<ITextSelection> GetSelectionInCurrentViewAsync(IAsyncServiceProvider serviceProvider,
Func<string, bool> predicate)
Func<string, bool> predicate, bool mustHaveFocus)
{
IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider, predicate);
IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider, predicate, mustHaveFocus);
if (viewHost == null)
return null;

return viewHost.TextView.Selection;
}

private static async Task<IWpfTextViewHost> GetCurrentViewHostAsync(IAsyncServiceProvider serviceProvider,
Func<string, bool> predicate)
Func<string, bool> predicate, bool mustHaveFocus)
{
IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider);
IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider, mustHaveFocus);
if (viewHost == null)
return null;

Expand Down

0 comments on commit d04d109

Please sign in to comment.