diff --git a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio.csproj b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio.csproj index 527db7988..62baf6d8e 100644 --- a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio.csproj +++ b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio.csproj @@ -46,7 +46,9 @@ 4 - + + Component + @@ -58,7 +60,7 @@ - + @@ -88,11 +90,4 @@ - \ No newline at end of file diff --git a/Src/CSharpier.VisualStudio/CSharpierOptionsPage.cs b/Src/CSharpier.VisualStudio/CSharpierOptionsPage.cs index 347d52fd9..3470c70c1 100644 --- a/Src/CSharpier.VisualStudio/CSharpierOptionsPage.cs +++ b/Src/CSharpier.VisualStudio/CSharpierOptionsPage.cs @@ -3,16 +3,11 @@ namespace CSharpier.VisualStudio { - // TODO see https://github.com/Elders/VSE-FormatDocumentOnSave for how that works, I think Settings can go away public class CSharpierOptionsPage : DialogPage { [Category("CSharpier")] [DisplayName("Reformat with CSharpier on Save")] [Description("Reformat with CSharpier on Save")] - public bool RunOnSave - { - get => Settings.Instance.RunOnSave; - set => Settings.Instance.RunOnSave = value; - } + public bool RunOnSave { get; set; } } } diff --git a/Src/CSharpier.VisualStudio/CSharpierPackage.cs b/Src/CSharpier.VisualStudio/CSharpierPackage.cs index 6ff3eef4e..208f82f6a 100644 --- a/Src/CSharpier.VisualStudio/CSharpierPackage.cs +++ b/Src/CSharpier.VisualStudio/CSharpierPackage.cs @@ -1,10 +1,8 @@ using System; using System.Runtime.InteropServices; using System.Threading; -using Microsoft.VisualStudio.Settings; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Shell.Settings; using Task = System.Threading.Tasks.Task; namespace CSharpier.VisualStudio @@ -28,12 +26,20 @@ IProgress progress var outputPane = await this.GetServiceAsync(typeof(SVsOutputWindow)) as IVsOutputWindow; var logger = new Logger(outputPane); logger.Log("Starting"); - + + await InfoBarService.InitializeAsync(this); + var csharpierService = new CSharpierService(logger); var formattingService = new FormattingService(logger, csharpierService); - await Settings.InitializeAsync(this); - await ReformatWithCSharpierOnSave.InitializeAsync(this, formattingService); + var csharpierOptionsPage = (CSharpierOptionsPage)GetDialogPage( + typeof(CSharpierOptionsPage) + ); + await ReformatWithCSharpierOnSave.InitializeAsync( + this, + formattingService, + csharpierOptionsPage + ); await ReformatWithCSharpier.InitializeAsync(this, formattingService); } } diff --git a/Src/CSharpier.VisualStudio/CSharpierProcessPipeMultipleFiles.cs b/Src/CSharpier.VisualStudio/CSharpierProcessPipeMultipleFiles.cs index 1d209b0b0..93bc6db83 100644 --- a/Src/CSharpier.VisualStudio/CSharpierProcessPipeMultipleFiles.cs +++ b/Src/CSharpier.VisualStudio/CSharpierProcessPipeMultipleFiles.cs @@ -7,7 +7,6 @@ namespace CSharpier.VisualStudio { - // TODO what about disposing this? public class CSharpierProcessPipeMultipleFiles : ICSharpierProcess { private readonly Logger logger; @@ -17,8 +16,11 @@ public class CSharpierProcessPipeMultipleFiles : ICSharpierProcess public CSharpierProcessPipeMultipleFiles(string csharpierPath, Logger logger) { this.logger = logger; - - var processStartInfo = new ProcessStartInfo("dotnet", csharpierPath + " --pipe-multiple-files") + + var processStartInfo = new ProcessStartInfo( + "dotnet", + csharpierPath + " --pipe-multiple-files" + ) { RedirectStandardInput = true, RedirectStandardOutput = true, @@ -32,6 +34,8 @@ public CSharpierProcessPipeMultipleFiles(string csharpierPath, Logger logger) this.FormatFile("public class ClassName { }", "Test.cs"); } + public bool CanFormat => true; + public string FormatFile(string content, string fileName) { process.StandardInput.Write(fileName); @@ -45,7 +49,7 @@ public string FormatFile(string content, string fileName) var outputReaderThread = CreateReadingThread(process.StandardOutput, output); var errorReaderThread = CreateReadingThread(process.StandardError, errorOutput); - + outputReaderThread.Start(); errorReaderThread.Start(); @@ -53,41 +57,46 @@ public string FormatFile(string content, string fileName) { Thread.Sleep(TimeSpan.FromMilliseconds(1)); } - + outputReaderThread.Interrupt(); errorReaderThread.Interrupt(); - + var errorResult = errorOutput.ToString(); if (string.IsNullOrEmpty(errorResult)) { return output.ToString(); } - + this.logger.Log("Got error output: " + errorResult); return ""; - } private Thread CreateReadingThread(StreamReader reader, StringBuilder stringBuilder) { - return new Thread(() => { - try { - var nextCharacter = reader.Read(); - while (nextCharacter != -1) { - if (nextCharacter == '\u0003') + return new Thread( + () => + { + try + { + var nextCharacter = reader.Read(); + while (nextCharacter != -1) { - done = true; - return; + if (nextCharacter == '\u0003') + { + done = true; + return; + } + stringBuilder.Append((char)nextCharacter); + nextCharacter = reader.Read(); } - stringBuilder.Append((char) nextCharacter); - nextCharacter = reader.Read(); } - } catch (Exception e) - { - // TODO log - done = true; + catch (Exception e) + { + logger.Log(e); + done = true; + } } - }); + ); } } -} \ No newline at end of file +} diff --git a/Src/CSharpier.VisualStudio/CSharpierProcessSingleFile.cs b/Src/CSharpier.VisualStudio/CSharpierProcessSingleFile.cs index 6246d9a5c..a88db40e1 100644 --- a/Src/CSharpier.VisualStudio/CSharpierProcessSingleFile.cs +++ b/Src/CSharpier.VisualStudio/CSharpierProcessSingleFile.cs @@ -14,6 +14,8 @@ public CSharpierProcessSingleFile(string csharpierPath, Logger logger) this.logger = logger; } + public bool CanFormat => true; + public string FormatFile(string content, string fileName) { var output = new StringBuilder(); diff --git a/Src/CSharpier.VisualStudio/CSharpierService.cs b/Src/CSharpier.VisualStudio/CSharpierService.cs index 2117e7a51..5e75bafd6 100644 --- a/Src/CSharpier.VisualStudio/CSharpierService.cs +++ b/Src/CSharpier.VisualStudio/CSharpierService.cs @@ -1,8 +1,12 @@ using System; using System.Diagnostics; +using System.Windows.Forms; namespace CSharpier.VisualStudio { + // TODO figure out how to publish https://docs.microsoft.com/en-us/visualstudio/extensibility/walkthrough-publishing-a-visual-studio-extension?view=vs-2022 + // TODO make this work in 2022 https://docs.microsoft.com/en-us/visualstudio/extensibility/migration/update-visual-studio-extension?view=vs-2022 + public class CSharpierService { private readonly string csharpierPath; @@ -42,8 +46,6 @@ public string GetCSharpierPath() public string ExecuteCommand(string cmd, string arguments) { - // TODO when testing, this runs from in the csharpier directory, which means it uses csharpier from there instead of globally - var processStartInfo = new ProcessStartInfo(cmd, arguments) { UseShellExecute = false, @@ -51,6 +53,10 @@ public string ExecuteCommand(string cmd, string arguments) RedirectStandardOutput = true, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, +#if DEBUG + // TODO when testing, this runs from in the csharpier directory, which means it uses csharpier from there instead of globally + WorkingDirectory = "C:/" +#endif }; using var process = new Process { StartInfo = processStartInfo }; @@ -66,7 +72,7 @@ private ICSharpierProcess SetupCSharpierProcess() { var version = ExecuteCommand("dotnet", this.csharpierPath + " --version"); this.logger.Log("CSharpier version: " + version); - if (version == null) + if (string.IsNullOrEmpty(version)) { this.DisplayInstallNeededMessage(); } @@ -78,10 +84,7 @@ private ICSharpierProcess SetupCSharpierProcess() { var content = "Please upgrade to CSharpier >= 0.12.0 for bug fixes and improved formatting speed."; - // TODO notify if csharpier should be updated - // NotificationGroupManager.getInstance().getNotificationGroup("CSharpier") - // .createNotification(content, NotificationType.INFORMATION) - // .notify(project); + InfoBarService.Instance.ShowInfoBar(content); return new CSharpierProcessSingleFile(this.csharpierPath, this.logger); } @@ -98,17 +101,18 @@ private ICSharpierProcess SetupCSharpierProcess() private void DisplayInstallNeededMessage() { - // TODO notify if not installed - // Notification notification = NotificationGroupManager.getInstance().getNotificationGroup("CSharpier") - // .createNotification("CSharpier must be installed globally to support formatting.", NotificationType.WARNING); - // - // // notification.addAction(new EditAction()); - // - // notification.notify(project); + InfoBarService.Instance.ShowInfoBar("CSharpier must be installed globally to support formatting."); } + public bool CanFormat => this.csharpierProcess.CanFormat; + public string Format(string content, string filePath) { + if (!this.csharpierProcess.CanFormat) + { + return null; + } + this.logger.Log("Formatting " + filePath); try { diff --git a/Src/CSharpier.VisualStudio/FormattingService.cs b/Src/CSharpier.VisualStudio/FormattingService.cs index 51b2b106d..7d8f98e5e 100644 --- a/Src/CSharpier.VisualStudio/FormattingService.cs +++ b/Src/CSharpier.VisualStudio/FormattingService.cs @@ -5,14 +5,16 @@ namespace CSharpier.VisualStudio public class FormattingService { private readonly Logger logger; - private readonly CSharpierService cSharpierService; + private readonly CSharpierService csharpierService; - public FormattingService(Logger logger, CSharpierService cSharpierService) + public FormattingService(Logger logger, CSharpierService csharpierService) { this.logger = logger; - this.cSharpierService = cSharpierService; + this.csharpierService = csharpierService; } + public bool CanFormat => this.csharpierService.CanFormat; + public void Format(Document document) { Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); @@ -32,7 +34,7 @@ public void Format(Document document) var endPoint = textDocument.EndPoint.CreateEditPoint(); var text = editPoint.GetText(endPoint); - var newText = this.cSharpierService.Format(text, document.FullName); + var newText = this.csharpierService.Format(text, document.FullName); if (string.IsNullOrEmpty(newText)) { return; diff --git a/Src/CSharpier.VisualStudio/ICSharpierProcess.cs b/Src/CSharpier.VisualStudio/ICSharpierProcess.cs index 9d4ef41c9..91b59c0da 100644 --- a/Src/CSharpier.VisualStudio/ICSharpierProcess.cs +++ b/Src/CSharpier.VisualStudio/ICSharpierProcess.cs @@ -2,6 +2,7 @@ namespace CSharpier.VisualStudio { public interface ICSharpierProcess { + bool CanFormat { get; } string FormatFile(string content, string fileName); } } diff --git a/Src/CSharpier.VisualStudio/InfoBarService.cs b/Src/CSharpier.VisualStudio/InfoBarService.cs new file mode 100644 index 000000000..7afbb76fa --- /dev/null +++ b/Src/CSharpier.VisualStudio/InfoBarService.cs @@ -0,0 +1,65 @@ +using System.Windows.Forms; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; + +namespace CSharpier.VisualStudio +{ +public class InfoBarService : IVsInfoBarUIEvents + { + private readonly CSharpierPackage csharpierPackage; + private uint cookie; + + private InfoBarService(CSharpierPackage csharpierPackage) + { + this.csharpierPackage = csharpierPackage; + } + + public static InfoBarService Instance { get; private set; } + + public static Task InitializeAsync(CSharpierPackage serviceProvider) + { + Instance = new InfoBarService(serviceProvider); + + return Task.CompletedTask; + } + + public void OnClosed(IVsInfoBarUIElement infoBarUiElement) + { + infoBarUiElement.Unadvise(cookie); + } + + public void OnActionItemClicked(IVsInfoBarUIElement infoBarUIElement, IVsInfoBarActionItem actionItem) + { + throw new System.NotImplementedException(); + } + + public void ShowInfoBar(string message) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var shell = csharpierPackage.GetServiceAsync(typeof(SVsShell)).Result as IVsShell; + if (shell == null) + { + return; + } + + shell.GetProperty((int) __VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out var property); + if (!(property is IVsInfoBarHost infoBarHost)) + { + return; + } + var text = new InfoBarTextSpan(message); + + var spans = new[] { text }; + var actions = new InfoBarActionItem[] { }; + var infoBarModel = new InfoBarModel(spans, actions, KnownMonikers.StatusInformation, isCloseButtonVisible: true); + + var factory = csharpierPackage.GetServiceAsync(typeof(SVsInfoBarUIFactory)).Result as IVsInfoBarUIFactory; + var element = factory.CreateInfoBar(infoBarModel); + element.Advise(this, out cookie); + infoBarHost.AddInfoBar(element); + } + } +} \ No newline at end of file diff --git a/Src/CSharpier.VisualStudio/NullCSharpierProcess.cs b/Src/CSharpier.VisualStudio/NullCSharpierProcess.cs index f7509502f..5304db78d 100644 --- a/Src/CSharpier.VisualStudio/NullCSharpierProcess.cs +++ b/Src/CSharpier.VisualStudio/NullCSharpierProcess.cs @@ -2,6 +2,8 @@ namespace CSharpier.VisualStudio { public class NullCSharpierProcess : ICSharpierProcess { + public bool CanFormat => false; + public string FormatFile(string content, string fileName) { return null; diff --git a/Src/CSharpier.VisualStudio/ReformatWithCSharpier.cs b/Src/CSharpier.VisualStudio/ReformatWithCSharpier.cs index c0156f8fc..f16788efa 100644 --- a/Src/CSharpier.VisualStudio/ReformatWithCSharpier.cs +++ b/Src/CSharpier.VisualStudio/ReformatWithCSharpier.cs @@ -44,6 +44,7 @@ private void QueryStatus(object sender, EventArgs e) var button = (OleMenuCommand)sender; button.Visible = dte.ActiveDocument.Name.EndsWith(".cs"); + button.Enabled = this.formattingService.CanFormat; } public static ReformatWithCSharpier Instance { get; private set; } @@ -66,6 +67,10 @@ FormattingService formattingService private void Execute(object sender, EventArgs e) { ThreadHelper.ThrowIfNotOnUIThread(); + if (!this.formattingService.CanFormat) + { + return; + } this.formattingService.Format(this.dte.ActiveDocument); } } diff --git a/Src/CSharpier.VisualStudio/ReformatWithCSharpierOnSave.cs b/Src/CSharpier.VisualStudio/ReformatWithCSharpierOnSave.cs index 4a1e1163d..53949ac97 100644 --- a/Src/CSharpier.VisualStudio/ReformatWithCSharpierOnSave.cs +++ b/Src/CSharpier.VisualStudio/ReformatWithCSharpierOnSave.cs @@ -13,21 +13,25 @@ public class ReformatWithCSharpierOnSave : IVsRunningDocTableEvents3 private readonly DTE dte; private readonly RunningDocumentTable runningDocumentTable; private readonly FormattingService formattingService; + private readonly CSharpierOptionsPage csharpierOptionsPage; private ReformatWithCSharpierOnSave( DTE dte, RunningDocumentTable runningDocumentTable, - FormattingService formattingService + FormattingService formattingService, + CSharpierOptionsPage csharpierOptionsPage ) { this.dte = dte; this.runningDocumentTable = runningDocumentTable; this.formattingService = formattingService; + this.csharpierOptionsPage = csharpierOptionsPage; } public static async Task InitializeAsync( CSharpierPackage csharpierPackage, - FormattingService formattingService + FormattingService formattingService, + CSharpierOptionsPage cSharpierOptionsPage ) { var dte = await csharpierPackage.GetServiceAsync(typeof(DTE)) as DTE; @@ -35,14 +39,15 @@ FormattingService formattingService var reformatWithCSharpierOnSave = new ReformatWithCSharpierOnSave( dte, runningDocumentTable, - formattingService + formattingService, + cSharpierOptionsPage ); runningDocumentTable.Advise(reformatWithCSharpierOnSave); } public int OnBeforeSave(uint docCookie) { - if (!Settings.Instance.RunOnSave) + if (!this.csharpierOptionsPage.RunOnSave) { return VSConstants.S_OK; } diff --git a/Src/CSharpier.VisualStudio/Settings.cs b/Src/CSharpier.VisualStudio/Settings.cs deleted file mode 100644 index 3145215e0..000000000 --- a/Src/CSharpier.VisualStudio/Settings.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.VisualStudio.Settings; -using Microsoft.VisualStudio.Shell.Settings; - -namespace CSharpier.VisualStudio -{ - public class Settings - { - private readonly WritableSettingsStore userSettingsStore; - - private Settings(WritableSettingsStore userSettingsStore) - { - this.userSettingsStore = userSettingsStore; - } - - public static Settings Instance { get; private set; } - - public static Task InitializeAsync(CSharpierPackage package) - { - var settingsManager = new ShellSettingsManager(package); - var userSettingsStore = settingsManager.GetWritableSettingsStore( - SettingsScope.UserSettings - ); - if (!userSettingsStore.CollectionExists("csharpier")) - { - userSettingsStore.CreateCollection("csharpier"); - userSettingsStore.SetBoolean("csharpier", "RunOnSave", false); - } - - Instance = new Settings(userSettingsStore); - - return Task.CompletedTask; - } - - public bool RunOnSave - { - get => userSettingsStore.GetBoolean("csharpier", "RunOnSave"); - set => userSettingsStore.SetBoolean("csharpier", "RunOnSave", value); - } - } -}