Skip to content
This repository has been archived by the owner on May 4, 2023. It is now read-only.

Check for the presence of Python files when in Open Folder mode. #24

Merged
merged 1 commit into from
Dec 23, 2022
Merged
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
8 changes: 6 additions & 2 deletions src/Extension/ExtensionPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Task = System.Threading.Tasks.Task;
using Extension.SnippetSearch;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Events;
using SolutionEvents = Microsoft.VisualStudio.Shell.Events.SolutionEvents;

namespace Extension
Expand All @@ -22,7 +21,10 @@ namespace Extension
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
[ProvideOptionPage(typeof(Settings.CodigaOptionPage), "Codiga", "General", 0, 0, true, SupportsProfiles = true)]
//See https://github.com/madskristensen/SolutionLoadSample
//Required so that the package gets initialized when a solution is open
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionOpening_string, PackageAutoLoadFlags.BackgroundLoad)]
//Required so that the package gets initialized when in Open Folder mode
[ProvideAutoLoad(VSConstants.UICONTEXT.FolderOpened_string, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class ExtensionPackage : AsyncPackage
{
/// <summary>
Expand Down Expand Up @@ -55,9 +57,11 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
HandleOpenSolution();

SolutionEvents.OnAfterOpenSolution += DoAdditionalInitialization;
SolutionEvents.OnAfterOpenFolder += DoAdditionalInitialization;
SolutionEvents.OnAfterCloseSolution += CleanupCachesAndServices;
SolutionEvents.OnAfterCloseFolder += CleanupCachesAndServices;

// When initialized asynchronously, the current thread may be a background thread at this point.
// When initialized asynchronously, the current thread may be a background thread at this point.
// Do any initialization that requires the UI thread after switching to the UI thread.
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
await SnippetSearchMenuCommand.InitializeAsync(this);
Expand Down
10 changes: 10 additions & 0 deletions src/Extension/Helpers/SolutionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,15 @@ internal static class SolutionHelper
sol.GetSolutionInfo(out string dir, out string file, out string ops);
return Path.GetDirectoryName(dir);
}

/// <summary>
/// Returns whether the argument solution is in Open Folder mode.
/// </summary>
/// <param name="solution">The solution service</param>
internal static bool IsInOpenFolderMode(IVsSolution solution)
{
solution.GetProperty((int)__VSPROPID7.VSPROPID_IsInOpenFolderMode, out object folderMode);
return folderMode is bool isInOpenFolderMode && isInOpenFolderMode;
}
}
}
57 changes: 50 additions & 7 deletions src/Extension/Rosie/CodigaDefaultRulesetsInfoBarHelper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Community.VisualStudio.Toolkit;
using Extension.Caching;
using Extension.Helpers;
using Extension.Settings;
using Extension.SnippetFormats;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
Expand Down Expand Up @@ -66,7 +69,7 @@ internal static async void ShowDefaultRulesetCreationInfoBarAsync(InfoBarHolder

if (SolutionSettings.IsShouldNotifyUserToCreateCodigaConfig(serviceProvider)
&& CodigaConfigFileUtil.FindCodigaConfigFile(serviceProvider) == null
&& await IsSolutionContainPythonProject(serviceProvider))
&& await IsSolutionContainPythonAsync(serviceProvider))
{
var model = new InfoBarModel(new[]
{
Expand Down Expand Up @@ -132,23 +135,22 @@ private static void RecordCreateCodigaYaml()
}

/// <summary>
/// Returns whether any of the projects in the current solution is a Python project.
/// Returns whether any of the projects in the current solution is a Python project,
/// or if in Open Folder mode, whether the folder or any of its sub-folders contain a Python file.
/// <br/>
/// Python project guid comes from https://github.com/microsoft/PTVS/blob/main/Python/Product/VSCommon/CommonGuidList.cs
/// </summary>
/// <param name="serviceProvider">The service provider to retrieve information about the solution from.</param>
private static async Task<bool> IsSolutionContainPythonProject(SVsServiceProvider serviceProvider)
private static async Task<bool> IsSolutionContainPythonAsync(SVsServiceProvider serviceProvider)
{
var solution = await ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
return (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
});

solution.GetProperty((int)__VSPROPID7.VSPROPID_IsInOpenFolderMode, out object isInFolderMode);

//If we have a proper VS solution open, check if at least one of the projects in it is a Python project
if (!(bool)isInFolderMode)
if (!SolutionHelper.IsInOpenFolderMode(solution))
{
var projectsInSolution =
ThreadHelper.JoinableTaskFactory.Run(async () => await VS.Solutions.GetAllProjectsAsync());
Expand All @@ -157,7 +159,48 @@ private static async Task<bool> IsSolutionContainPythonProject(SVsServiceProvide
await project.IsKindAsync("888888A0-9F3D-457C-B088-3A5042F75D52")));
}

return false;
//Running the lookup in the background, so it doesn't block the UI
return await Task.Run(() => IsContainPythonFile(SolutionHelper.GetSolutionDir(serviceProvider)) != null);
}

/// <summary>
/// Searches the argument <c>directory</c> and all its sub-directories for the presence of files with <c>.py</c>
/// extension.
/// <br/>
/// It currently excludes lookup in IDE solution and project specific folders, <c>.vs</c> and <c>.idea</c>.
/// </summary>
/// <param name="directory">The root directory to search in</param>
/// <returns>The language of the found file if there is a file found, or null if no file was found.</returns>
private static LanguageUtils.LanguageEnumeration? IsContainPythonFile(string directory)
{
try
{
//Using a foreach instead of a call to '.Any()' because 'Any()' creates an extra enumerator each time it is called.
foreach (var _ in Directory.EnumerateFiles(directory, "*.py"))
return LanguageUtils.LanguageEnumeration.Python;

foreach (var subDir in Directory.EnumerateDirectories(directory))
{
//Exclude non-existent folders, and ones whose name starts with a dot
if (subDir != null)
{
string? directoryName = Path.GetDirectoryName(subDir);
if (directoryName != ".vs" && directoryName != ".idea")
{
var isContainPythonFile = IsContainPythonFile(subDir);
if (isContainPythonFile != null)
return isContainPythonFile;
}
}
}
}
catch
{
//Falling through to return null, so that we return to one level up in the recursion,
// with no specific file found
}

return null;
}
}
}