├── .gitattributes ├── .gitignore ├── Art ├── .gitignore ├── CommandIcon.cdr └── CommandIcon_Small.cdr ├── Build ├── addin.zip ├── icon.png ├── screenshot.png └── version.json ├── Changelog.md ├── CommanderAddin.sln ├── CommanderAddin ├── 7z.dll ├── 7z.exe ├── Build.ps1 ├── CommanderAddin.cs ├── CommanderAddin.csproj ├── CommanderAddin.sln ├── CommanderWindow.xaml ├── CommanderWindow.xaml.cs ├── ErrorInConsole.png ├── Properties │ └── launchSettings.json ├── _classes │ ├── CommanderAddinConfiguration.cs │ ├── CommanderAddinModel.cs │ ├── CommanderCommand.cs │ └── ScriptParser.cs ├── hsizegrip.png ├── icon.png ├── icon_22.png ├── screenshot.png └── version.json ├── icon.png ├── readme.md └── screenshot2.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | *.obj 3 | *.exe 4 | *.pdb 5 | *.dll 6 | *.user 7 | *.aps 8 | *.pch 9 | *.vspscc 10 | *_i.c 11 | *_p.c 12 | *.ncb 13 | *.suo 14 | *.sln.docstates 15 | *.tlb 16 | *.tlh 17 | *.bak 18 | *.cache 19 | *.ilk 20 | *.log 21 | [Bb]in 22 | [Dd]ebug*/ 23 | .idea 24 | *.lib 25 | *.sbr 26 | *.nupkg 27 | ProductionInstall*.xml 28 | obj/ 29 | [Rr]elease*/ 30 | _ReSharper*/ 31 | [Tt]est[Rr]esult* 32 | *.vssscc 33 | $tf*/ 34 | packages*/ 35 | _preview.html 36 | 37 | /InstallerFiles/*.iw9 38 | /InstallerFiles/Upload.bat 39 | WebEssentials-Settings.json 40 | InstallerFiles/FilesToInstall/AppData/Html/_preview.json 41 | /MightyMarkdown/Install/Distribution 42 | /MightyMarkdown/Install/Builds 43 | /Install/Builds 44 | .vs 45 | /MarkdownMonster.sln.DotSettings 46 | /Install/Builds/CurrentRelease/MarkdownMonsterSetup.exe 47 | /Install/Distribution 48 | /Install/West Wind Markdown Monster 49 | /Install/Builds/CurrentRelease/MarkdownMonster_Version.xml 50 | /Install/West Wind Markdown Monster.iw9 51 | Output-Build.txt 52 | __README.htm 53 | __Todo.htm 54 | MarkdownMonsterWeb/website.publishproj 55 | MarkdownMonsterWeb/App_Data/PublishProfiles/Markdown Monster Web Site.pubxml 56 | MarkdownMonsterWeb/BugReport/telemetry.txt 57 | MarkdownMonster/Editor/scripts/Ace/theme-twilight - Copy.js 58 | /Install/Chocolatey/push.ps1 59 | *.sln.DotSettings 60 | /Build/Distribution 61 | -------------------------------------------------------------------------------- /Art/.gitignore: -------------------------------------------------------------------------------- 1 | Backup_*.* 2 | -------------------------------------------------------------------------------- /Art/CommandIcon.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/Art/CommandIcon.cdr -------------------------------------------------------------------------------- /Art/CommandIcon_Small.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/Art/CommandIcon_Small.cdr -------------------------------------------------------------------------------- /Build/addin.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/Build/addin.zip -------------------------------------------------------------------------------- /Build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/Build/icon.png -------------------------------------------------------------------------------- /Build/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/Build/screenshot.png -------------------------------------------------------------------------------- /Build/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Commander", 3 | "name": "Commander: C# Script Automation", 4 | "summary": "Create small C# based scripts to automate many operations in Markdown Monster. Shell out to external tools, pull in data from external source, update documents, merge data into the editor and more.", 5 | "description": "Use this addin to automate Markdown Monster using C# syntax. Automate operations inside of Markdown Monster and tie the script to a hotkey. You can access the active document, the editor, the main window and a host of support features to automate document tasks and even control the user interface. Some examples includes launching external applications, merging data into open documents, generate and load new documents, posting custom content to custom services and much more.\r\n\r\nWith full access to C# code, the sky's the limit with what you can accomplish.", 6 | "releaseNotes": null, 7 | "version": "0.3.2", 8 | "minVersion": "3.0.5", 9 | "maxVersion": "3.99", 10 | "author": "© Rick Strahl, West Wind Technologies 2017-2023", 11 | "updated": "2023-10-13T12:00:00Z" 12 | } 13 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Change Log Commander Addin for Markdown Monster 4 | 5 | ### 0.44.1 6 | February 8th, 2019 7 | 8 | * **UI Changes** 9 | Updated snippet behavior so that the main window status bar is not updated by default if the snippet succeeds. This allows your snippet to write to the status bar to provide feedback, rather than a generic snippet execution method. 10 | 11 | * **Add support for latest C# features** 12 | You can now use the latest C# features with the Snippet addin as snippets are now 13 | compiled using Roslyn. Snippets are also cached now. 14 | 15 | ### 0.3.15 16 | April 5th, 2018 17 | 18 | * **Add WindowStyle flag to ExecuteProcess Helper** 19 | Add flag to allow controlling window size and default to hidden, so processes run invisibly. 20 | 21 | * **ShortCut Parsing Improvements** 22 | Fix a number of edge cases for shortcut maps for commands. Only support single gestures (Ctrl-o, Ctrl-Shift-o, but not Ctrl-O,L) for example. 23 | 24 | * **Fix: Load Errors for Key Bindings** 25 | Fix the load errors for key bindings and continue loading bindings. 26 | 27 | ### 0.3 28 | May 14th, 2017 29 | 30 | * **Remember Last Command Used** 31 | The Addin now remembers which common was last selected and restores it when the dialog is restarted so you can more easily re-run your last command. 32 | 33 | * **Error Messages in main Status Bar** 34 | Command execution now shows a confirmation or failure message in the main window's status bar so you can see that something is happening when the Commander window is not open. 35 | 36 | * **Add default Scripts for HelloWorld and Push to Git** 37 | Added two default scripts that demonstrate basic operation of the addin, one to run a loop and output a string, one to execute a Git to stage, commit and push the current document to Git. 38 | 39 | * **Fix Console Output to scroll to Bottom** 40 | Change behavior of the console to always display the end of the console output by scrolling the textbox to the bottom. 41 | 42 | * **Fix: Execution off shortcut key without Window Open** 43 | This fixes an issue where you couldn't successfully execute an addin that used the Model when the Command Window wasn't open. Model data is now properly passed even if the Window has not been opened. 44 | 45 | ### 0.2 46 | February 22, 2017 47 | 48 | * **Console Output Display in Editor** 49 | When you test your scripts in the Addin editor, you now can use Console output to write to the captured console window on the form. The Console displays output from your code when writing to the Console, as well as displaying compiler and runtime error messages. 50 | 51 | * **Errors can now optionally open Compiled Source** 52 | When a compilation error occurs during script execution you can now optionally pop open a new Markdown Monster editor window with the full compiled source code. You can match error line numbers to code lines. 53 | 54 | ### 0.1 55 | February 12, 2017 56 | 57 | * **Add Keyboard Hotkey Support** 58 | You can now bind a keyboard hotkey to your Script so you can invoke it from within the editor. Hotkeys take the format of Alt-Shift-N, Ctrl+Alt+F1 etc. 59 | 60 | -------------------------------------------------------------------------------- /CommanderAddin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32505.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommanderAddin", "CommanderAddin\CommanderAddin.csproj", "{4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documents", "Documents", "{621390FC-6874-4229-88B7-6E80042D1629}" 9 | ProjectSection(SolutionItems) = preProject 10 | Changelog.md = Changelog.md 11 | readme.md = readme.md 12 | EndProjectSection 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1D02EA7B-03CA-477A-85B4-5F1BB91A15A9} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /CommanderAddin/7z.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/7z.dll -------------------------------------------------------------------------------- /CommanderAddin/7z.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/7z.exe -------------------------------------------------------------------------------- /CommanderAddin/Build.ps1: -------------------------------------------------------------------------------- 1 | # Build script that creates the \Build folder final output 2 | # that can be shared on GitHub and the public Markdown Monster Addins Repo 3 | 4 | cd "$PSScriptRoot" 5 | 6 | $src = "." 7 | $dlls = "$env:appdata\Markdown Monster\Addins\Commander" 8 | $tgt = "..\Build" 9 | $dist = "..\Build\Distribution" 10 | 11 | "Copying from: $dlls" 12 | 13 | "Cleaning up build files..." 14 | remove-item -recurse -force $tgt 15 | md $tgt 16 | md $dist 17 | 18 | "Copying files for zip file..." 19 | copy "$dlls\*.dll" $dist 20 | copy "$src\version.json" $dist 21 | 22 | del $dist\Microsoft.*.dll 23 | 24 | "Copying files for Build folder..." 25 | copy "$src\version.json" $tgt 26 | copy "$src\icon.png" $tgt 27 | copy "$src\screenshot.png" $tgt 28 | 29 | "Zipping up setup file..." 30 | .\7z.exe a -tzip $tgt\addin.zip $dist\*.* -------------------------------------------------------------------------------- /CommanderAddin/CommanderAddin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Threading; 11 | using FontAwesome6; 12 | using MarkdownMonster; 13 | using MarkdownMonster.AddIns; 14 | using MarkdownMonster.Windows; 15 | using Westwind.Utilities; 16 | 17 | 18 | namespace CommanderAddin 19 | { 20 | public class CommanderAddin : MarkdownMonsterAddin 21 | { 22 | 23 | private CommanderWindow commanderWindow; 24 | 25 | public CommanderAddinModel AddinModel { get; set; } 26 | 27 | public override async Task OnApplicationStart() 28 | { 29 | AddinModel = new CommanderAddinModel { 30 | AppModel = Model, 31 | AddinConfiguration = CommanderAddinConfiguration.Current, 32 | Addin = this 33 | }; 34 | 35 | await base.OnApplicationStart(); 36 | 37 | Id = "Commander"; 38 | 39 | // by passing in the add in you automatically 40 | // hook up OnExecute/OnExecuteConfiguration/OnCanExecute 41 | var menuItem = new AddInMenuItem(this) 42 | { 43 | Caption = "Commander C# Script Automation", 44 | FontawesomeIcon = EFontAwesomeIcon.Solid_Terminal, 45 | KeyboardShortcut = CommanderAddinConfiguration.Current.KeyboardShortcut 46 | }; 47 | try 48 | { 49 | menuItem.IconImageSource = new ImageSourceConverter() 50 | .ConvertFromString("pack://application:,,,/CommanderAddin;component/icon_22.png") as ImageSource; 51 | } 52 | catch { } 53 | 54 | 55 | // if you don't want to display config or main menu item clear handler 56 | //menuItem.ExecuteConfiguration = null; 57 | // Must add the menu to the collection to display menu and toolbar items 58 | MenuItems.Add(menuItem); 59 | } 60 | 61 | #region Addin Implementation Methods 62 | public override async Task OnExecute(object sender) 63 | { 64 | await base.OnExecute(sender); 65 | 66 | if(commanderWindow == null || !commanderWindow.IsLoaded) 67 | { 68 | InitializeAddinModel(); 69 | 70 | commanderWindow = new CommanderWindow(this); 71 | 72 | commanderWindow.Top = Model.Window.Top; 73 | commanderWindow.Left = Model.Window.Left + Model.Window.Width - 74 | Model.Configuration.WindowPosition.SplitterPosition; 75 | } 76 | commanderWindow.Show(); 77 | commanderWindow.Activate(); 78 | } 79 | 80 | private void InitializeAddinModel() 81 | { 82 | AddinModel.Window = Model.Window; 83 | AddinModel.AppModel = Model; 84 | } 85 | 86 | public override async Task OnExecuteConfiguration(object sender) 87 | { 88 | await OpenTab(Path.Combine(mmApp.Configuration.CommonFolder, "CommanderAddin.json")); 89 | } 90 | 91 | public override bool OnCanExecute(object sender) 92 | { 93 | return true; 94 | } 95 | 96 | 97 | public override Task OnWindowLoaded() 98 | { 99 | foreach (var command in CommanderAddinConfiguration.Current.Commands) 100 | { 101 | 102 | if (!string.IsNullOrEmpty(command.KeyboardShortcut)) 103 | { 104 | var ksc = command.KeyboardShortcut.ToLower().Replace("-", "+"); 105 | KeyBinding kb = new KeyBinding(); 106 | 107 | try 108 | { 109 | var gesture = new KeyGestureConverter().ConvertFromString(ksc) as InputGesture; 110 | var cmd = new CommandBase((s, e) => RunCommand(command).FireAndForget(), 111 | (s, e) => Model.IsEditorActive); 112 | 113 | kb.Gesture = gesture; 114 | 115 | // Whatever command you need to bind to 116 | kb.Command = cmd; 117 | 118 | Model.Window.InputBindings.Add(kb); 119 | 120 | } 121 | catch (Exception ex) 122 | { 123 | mmApp.Log("Commander Addin: Failed to create keyboard binding for: " + ksc + ". " + ex.Message); 124 | } 125 | } 126 | } 127 | 128 | return Task.CompletedTask; 129 | } 130 | 131 | public override Task OnApplicationShutdown() 132 | { 133 | commanderWindow?.Close(); 134 | return Task.CompletedTask; 135 | } 136 | #endregion 137 | 138 | 139 | public async Task RunCommand(CommanderCommand command) 140 | { 141 | 142 | if (AddinModel.AppModel == null) 143 | InitializeAddinModel(); 144 | 145 | string code = command.CommandText; 146 | ConsoleTextWriter consoleWriter = null; 147 | 148 | bool showConsole = commanderWindow != null && commanderWindow.Visibility == Visibility.Visible; 149 | if (showConsole) 150 | { 151 | var tbox = commanderWindow.TextConsole; 152 | tbox.Clear(); 153 | 154 | WindowUtilities.DoEvents(); 155 | 156 | consoleWriter = new ConsoleTextWriter() 157 | { 158 | tbox = tbox 159 | }; 160 | Console.SetOut(consoleWriter); 161 | } 162 | 163 | AddinModel.AppModel.Window.ShowStatusProgress("Executing Command '" + command.Name + "' started..."); 164 | 165 | var parser = new ScriptParser(); 166 | bool result = await parser.EvaluateScriptAsync(code, AddinModel); 167 | 168 | if (!result) 169 | { 170 | var msg = parser.ErrorMessage; 171 | if (parser.ScriptInstance.ErrorType == Westwind.Scripting.ExecutionErrorTypes.Compilation) 172 | msg = "Script compilation error."; 173 | 174 | AddinModel.AppModel.Window.ShowStatusError("Command '" + command.Name + "' execution failed: " + msg); 175 | if (showConsole) 176 | { 177 | if (parser.ScriptInstance.ErrorType == Westwind.Scripting.ExecutionErrorTypes.Compilation) 178 | { 179 | var adjusted = ScriptParser.FixupLineNumbersAndErrors(parser.ScriptInstance); 180 | parser.ErrorMessage = adjusted.UpdatedErrorMessage; 181 | Console.WriteLine($"*** Script Compilation Errors:\n{parser.ErrorMessage}\n"); 182 | 183 | Console.WriteLine("\n*** Generated Code:"); 184 | Console.WriteLine(parser.ScriptInstance.GeneratedClassCode); 185 | } 186 | else 187 | { 188 | Console.WriteLine($"*** Runtime Execution Error:\n{parser.ErrorMessage}\n"); 189 | } 190 | } 191 | } 192 | else 193 | { 194 | if (mmApp.Model.Window.StatusText.Text.StartsWith("Executing Command ")) 195 | AddinModel.AppModel.Window.ShowStatusSuccess("Command '" + command.Name + "' executed successfully"); 196 | } 197 | 198 | 199 | if (showConsole) 200 | { 201 | consoleWriter.Close(); 202 | commanderWindow.TextConsole.ScrollToHome(); 203 | StreamWriter standardOutput = new StreamWriter(Console.OpenStandardOutput()); 204 | standardOutput.AutoFlush = true; 205 | Console.SetOut(standardOutput); 206 | } 207 | } 208 | 209 | 210 | } 211 | 212 | public class ConsoleTextWriter : TextWriter 213 | { 214 | public TextBox tbox; 215 | private StringBuilder sb = new StringBuilder(); 216 | 217 | public override void Write(char value) 218 | { 219 | sb.Append(value); 220 | 221 | if (value == '\n') 222 | { 223 | tbox.Dispatcher.Invoke(() => tbox.Text = sb.ToString()); 224 | } 225 | } 226 | 227 | 228 | 229 | public override void Close() 230 | { 231 | tbox.Text = sb.ToString(); 232 | base.Close(); 233 | } 234 | 235 | public override Encoding Encoding 236 | { 237 | get { return Encoding.Default; } 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /CommanderAddin/CommanderAddin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 0.3.4 5 | net70-windows 6 | CommanderAddin 7 | true 8 | 9 | Rick Strahl, West Wind Technologies 10 | Markdown Monster Commander Scripting Addin 11 | Allows you to create scripts that automate Markdown Monster. 12 | 13 | $(AppData)\Markdown Monster\Addins\Commander 14 | 15 | 16 | 17 | 18 | 19 | 20 | false 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /CommanderAddin/CommanderAddin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26206.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommanderAddin", "CommanderAddin\CommanderAddin.csproj", "{4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {4FBD2864-0C5B-4D66-9301-4E759AB6CDF6}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /CommanderAddin/CommanderWindow.xaml: -------------------------------------------------------------------------------- 1 |  19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 60 | 61 | 72 | 73 | 74 | 75 | 83 | 94 | 95 | 96 | 97 | 98 | 99 | 110 | 111 | 112 | 113 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Command Name 142 | 143 | 144 | 145 | 146 | Keyboard Shortcut 147 | 149 | 150 | 151 | 152 | Command C# Code 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Console output 167 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 189 | 190 | 191 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | Ready 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /CommanderAddin/CommanderWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | using System.Windows.Threading; 10 | using FontAwesome6; 11 | using FontAwesome6.Shared.Extensions; 12 | using MarkdownMonster; 13 | using Westwind.Utilities; 14 | 15 | namespace CommanderAddin 16 | { 17 | /// 18 | /// Interaction logic for PasteHref.xaml 19 | /// 20 | public partial class CommanderWindow 21 | { 22 | public CommanderAddinModel Model { get; set; } 23 | 24 | private bool pageLoaded = false; 25 | 26 | public CommanderWindow(CommanderAddin addin) 27 | { 28 | InitializeComponent(); 29 | mmApp.SetThemeWindowOverride(this); 30 | 31 | Model = addin.AddinModel; 32 | Model.AddinWindow = this; 33 | 34 | 35 | if (Model.AddinConfiguration.Commands == null || Model.AddinConfiguration.Commands.Count < 1) 36 | { 37 | Model.AddinConfiguration.Commands = new ObservableCollection(); 38 | Model.AddinConfiguration.Commands.Add( 39 | new CommanderCommand 40 | { 41 | Name = "Console Test", 42 | CommandText = "for(int x = 1; x < 5; x++) {\n Console.WriteLine(\"Hello World \" + x.ToString());\n}\n\n// Demonstrate async functionality\nusing (var client = new WebClient())\n{\n var uri = new Uri(\"https://albumviewer.west-wind.com/api/album/37\");\n var json = await client.DownloadStringTaskAsync(uri);\n Console.WriteLine($\"\\n{json}\");\n}", 43 | }); 44 | 45 | Model.AddinConfiguration.Commands.Add( 46 | new CommanderCommand 47 | { 48 | Name = "Open in VsCode", 49 | CommandText = 50 | "var docFile = Model?.ActiveDocument?.Filename;\nif (string.IsNullOrEmpty(docFile))\n\treturn null;\n\nModel.Window.SaveFile();\n\nvar exe = @\"%ProgramFiles\\Microsoft VS Code\\Code.exe\";\nexe = Environment.ExpandEnvironmentVariables(exe);\n\nProcess pi = null;\ntry {\n\tvar lineNo = await Model.ActiveEditor.GetLineNumber();\n\tpi = Process.Start(exe,\"-g \\\"\" + docFile + $\":{lineNo + 1}\\\"\");\n}\ncatch(Exception ex) {\n\tModel.Window.ShowStatusError(\"Couldn't open editor: \" + ex.Message);\n\treturn null;\n}\n\nif (pi != null)\n Model.Window.ShowStatus($\"VS Code started with: {docFile}\",5000);\nelse\n Model.Window.ShowStatusError(\"Failed to start VS Code.\");\n", 51 | KeyboardShortcut = "Alt-Shift-V" 52 | }); 53 | } 54 | else 55 | { 56 | Model.AddinConfiguration.Commands = 57 | new ObservableCollection(Model.AddinConfiguration.Commands.OrderBy(snip => snip.Name)); 58 | if (Model.AddinConfiguration.Commands.Count > 0) 59 | Model.ActiveCommand = Model.AddinConfiguration.Commands[0]; 60 | } 61 | 62 | Loaded += CommandsWindow_Loaded; 63 | Unloaded += CommanderWindow_Unloaded; 64 | 65 | WebBrowserCommand.Visibility = Visibility.Hidden; 66 | 67 | DataContext = Model; 68 | 69 | } 70 | 71 | private MarkdownEditorSimple editor; 72 | 73 | private async void CommandsWindow_Loaded(object sender, RoutedEventArgs e) 74 | { 75 | await Task.Delay(1); 76 | 77 | string initialValue = null; 78 | 79 | if (Model.AddinConfiguration.Commands.Count > 0) 80 | { 81 | CommanderCommand selectedItem = null; 82 | if (!string.IsNullOrEmpty(CommanderAddinConfiguration.Current.LastCommand)) 83 | selectedItem = 84 | Model.AddinConfiguration.Commands.FirstOrDefault( 85 | cmd => cmd.Name == Model.AddinConfiguration.LastCommand); 86 | else 87 | selectedItem = Model.AddinConfiguration.Commands[0]; 88 | 89 | ListCommands.SelectedItem = selectedItem; 90 | if (selectedItem != null) 91 | initialValue = selectedItem.CommandText; 92 | } 93 | 94 | editor = new MarkdownEditorSimple(WebBrowserCommand, initialValue, "csharp"); 95 | editor.ShowLineNumbers = true; 96 | editor.IsDirtyAction = (isDirty, newText, oldText) => 97 | { 98 | if (newText != null && Model.ActiveCommand != null) 99 | Model.ActiveCommand.CommandText = newText; 100 | 101 | return newText != oldText; 102 | }; 103 | 104 | Dispatcher.Invoke(() => ListCommands.Focus(), DispatcherPriority.ApplicationIdle); 105 | 106 | pageLoaded = true; 107 | } 108 | 109 | 110 | private void CommanderWindow_Unloaded(object sender, RoutedEventArgs e) 111 | { 112 | CommanderAddinConfiguration.Current.Write(); 113 | } 114 | 115 | 116 | private async void ListCommands_OnMouseDoubleClick(object sender, MouseButtonEventArgs e) 117 | { 118 | var command = ListCommands.SelectedItem as CommanderCommand; 119 | if (command == null) 120 | return; 121 | 122 | await Model.Addin.RunCommand(command); 123 | } 124 | 125 | private async void ToolButtonNewCommand_Click(object sender, RoutedEventArgs e) 126 | { 127 | Model.AddinConfiguration.Commands.Insert(0,new CommanderCommand() {Name = "New Command", CommandText = NewCommandText}); 128 | ListCommands.SelectedItem = Model.AddinConfiguration.Commands[0]; 129 | 130 | await Dispatcher.Invoke(async () => 131 | { 132 | await editor.BrowserInterop.SetSelectionRange(0, 0, 9999999, 0); 133 | await editor.BrowserInterop.SetFocus(); 134 | }, System.Windows.Threading.DispatcherPriority.ApplicationIdle); 135 | } 136 | 137 | 138 | private void ToolButtonRemoveCommand_Click(object sender, RoutedEventArgs e) 139 | { 140 | var command = ListCommands.SelectedItem as CommanderCommand; 141 | if (command == null) 142 | return; 143 | CommanderAddinConfiguration.Current.Commands.Remove(command); 144 | } 145 | 146 | 147 | private async void ToolButtonRunCommand_Click(object sender, RoutedEventArgs e) 148 | { 149 | var command = ListCommands.SelectedItem as CommanderCommand; 150 | if (command == null) 151 | return; 152 | 153 | await Model.Addin.RunCommand(command); 154 | } 155 | 156 | 157 | private async void ListCommands_KeyUp(object sender, KeyEventArgs e) 158 | { 159 | 160 | if (e.Key == Key.Return || e.Key == Key.Space) 161 | { 162 | 163 | var command = ListCommands.SelectedItem as CommanderCommand; 164 | if (command != null) 165 | await Model.Addin.RunCommand(command); 166 | } 167 | } 168 | 169 | private async void ListCommands_SelectionChanged(object sender, SelectionChangedEventArgs e) 170 | { 171 | if (!pageLoaded) 172 | return; 173 | 174 | var command = ListCommands.SelectedItem as CommanderCommand; 175 | if (command != null) 176 | { 177 | try 178 | { 179 | await editor.BrowserInterop.SetValue(command.CommandText); 180 | } 181 | catch 182 | { } 183 | 184 | CommanderAddinConfiguration.Current.LastCommand = command.Name; 185 | } 186 | 187 | 188 | } 189 | 190 | 191 | #region StatusBar 192 | 193 | public void ShowStatus(string message = null, int milliSeconds = 0) 194 | { 195 | if (message == null) 196 | message = "Ready"; 197 | 198 | StatusText.Text = message; 199 | 200 | if (milliSeconds > 0) 201 | { 202 | var t = new Timer(new TimerCallback((object win) => 203 | { 204 | var window = win as CommanderWindow; 205 | if (window == null) 206 | return; 207 | 208 | window.Dispatcher.Invoke(() => { window.ShowStatus(null, 0); }); 209 | }), this, milliSeconds, Timeout.Infinite); 210 | } 211 | } 212 | 213 | /// 214 | /// Status the statusbar icon on the left bottom to some indicator 215 | /// 216 | /// 217 | /// 218 | /// 219 | public void SetStatusIcon(EFontAwesomeIcon icon, Color color, bool spin = false) 220 | { 221 | StatusIcon.Icon = icon; 222 | StatusIcon.Foreground = new SolidColorBrush(color); 223 | if (spin) 224 | StatusIcon.SpinDuration = 30; 225 | StatusIcon.Spin = spin; 226 | } 227 | 228 | /// 229 | /// Resets the Status bar icon on the left to its default green circle 230 | /// 231 | public void SetStatusIcon() 232 | { 233 | StatusIcon.Icon = EFontAwesomeIcon.Solid_Circle; 234 | StatusIcon.Foreground = new SolidColorBrush(Colors.Green); 235 | StatusIcon.Spin = false; 236 | StatusIcon.SpinDuration = 0; 237 | StatusIcon.StopSpin(); 238 | } 239 | #endregion 240 | 241 | private void btnClearConsole_Click(object sender, RoutedEventArgs e) 242 | { 243 | TextConsole.Text = string.Empty; 244 | ConsoleGridRow.Height = new GridLength(0); 245 | } 246 | 247 | private const string NewCommandText = @"// *** Snippet sample - use what you need or delete 248 | 249 | var doc = Model.ActiveDocument; 250 | var editor = Model.ActiveEditor; 251 | 252 | var filename = Model.ActiveDocument.Filename; 253 | var selection = await Model.ActiveEditor.GetSelection(); 254 | var lineNo = await Model.ActiveEditor.GetLineNumber(); 255 | var line = await Model.ActiveEditor.GetCurrentLine(); 256 | var firstLine = await Model.ActiveEditor.GetLine(0); 257 | 258 | var console = Model.AppModel.Console; // application console panel 259 | console.Clear(); 260 | console.WriteLine(""Editor is at line: "" + lineNo); 261 | console.WriteLine(""First line: "" + firstLine); 262 | console.WriteLine(""Current line: "" + line); 263 | 264 | Model.Window.ShowStatusSuccess(""Command complete.""); 265 | // Model.Window.ShowStatusError(""Command failed."");"; 266 | 267 | private void ToolButtonMoreInfo_Click(object sender, RoutedEventArgs e) 268 | { 269 | ShellUtils.OpenUrl("https://github.com/RickStrahl/Commander-MarkdownMonster-Addin"); 270 | } 271 | } 272 | 273 | } 274 | -------------------------------------------------------------------------------- /CommanderAddin/ErrorInConsole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/ErrorInConsole.png -------------------------------------------------------------------------------- /CommanderAddin/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CommanderAddin-MM-Debug": { 4 | "executablePath": "d:\\projects\\MarkdownMonsterCode\\MarkdownMonster\\bin\\Release\\net7.0-windows\\MarkdownMonster.exe", 5 | "commandLineArgs": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /CommanderAddin/_classes/CommanderAddinConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.CompilerServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using MarkdownMonster; 11 | using MarkdownMonster.Annotations; 12 | using Westwind.Utilities.Configuration; 13 | 14 | 15 | namespace CommanderAddin 16 | { 17 | public class CommanderAddinConfiguration : AppConfiguration, INotifyPropertyChanged 18 | { 19 | 20 | public static CommanderAddinConfiguration Current; 21 | 22 | static CommanderAddinConfiguration() 23 | { 24 | Current = new CommanderAddinConfiguration(); 25 | Current.Initialize(); 26 | } 27 | 28 | 29 | /// 30 | /// Keyboard shortcut for this addin. 31 | /// 32 | public string KeyboardShortcut 33 | { 34 | get { return _keyboardShortcut; } 35 | set 36 | { 37 | if (_keyboardShortcut == value) return; 38 | _keyboardShortcut = value; 39 | OnPropertyChanged(nameof(KeyboardShortcut)); 40 | } 41 | } 42 | private string _keyboardShortcut = string.Empty; 43 | 44 | 45 | /// 46 | /// if true opens the source code for a failed script in the editor 47 | /// 48 | public bool OpenSourceInEditorOnErrors 49 | { 50 | get { return _openSourceInEditorOnErrors; } 51 | set 52 | { 53 | if (_openSourceInEditorOnErrors == value) return; 54 | _openSourceInEditorOnErrors = value; 55 | OnPropertyChanged(nameof(OpenSourceInEditorOnErrors)); 56 | } 57 | } 58 | private bool _openSourceInEditorOnErrors; 59 | 60 | 61 | /// 62 | /// List of all the commands stored on this configuration 63 | /// 64 | public ObservableCollection Commands 65 | { 66 | get { return _commands; } 67 | set 68 | { 69 | if (Equals(value, _commands)) return; 70 | _commands = value; 71 | OnPropertyChanged(); 72 | } 73 | } 74 | private ObservableCollection _commands = new ObservableCollection(); 75 | 76 | 77 | 78 | /// 79 | /// Last command used so it can be restored when we restart 80 | /// 81 | public string LastCommand 82 | { 83 | get { return _lastCommand; } 84 | set 85 | { 86 | if (Equals(value, _lastCommand)) return; 87 | _lastCommand = value; 88 | OnPropertyChanged(nameof(LastCommand)); 89 | } 90 | } 91 | private string _lastCommand; 92 | 93 | #region INotifyPropertyChanged 94 | public event PropertyChangedEventHandler PropertyChanged; 95 | 96 | [NotifyPropertyChangedInvocator] 97 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 98 | { 99 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 100 | 101 | } 102 | #endregion 103 | 104 | 105 | #region AppConfiguration 106 | protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData) 107 | { 108 | var provider = new JsonFileConfigurationProvider() 109 | { 110 | JsonConfigurationFile = Path.Combine(mmApp.Configuration.CommonFolder, "CommanderAddin.json") 111 | }; 112 | 113 | if (!File.Exists(provider.JsonConfigurationFile)) 114 | { 115 | if (!Directory.Exists(Path.GetDirectoryName(provider.JsonConfigurationFile))) 116 | Directory.CreateDirectory(Path.GetDirectoryName(provider.JsonConfigurationFile)); 117 | } 118 | 119 | return provider; 120 | } 121 | #endregion 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /CommanderAddin/_classes/CommanderAddinModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using MarkdownMonster; 9 | using MarkdownMonster.Annotations; 10 | using Westwind.Scripting; 11 | 12 | 13 | namespace CommanderAddin 14 | { 15 | public class CommanderAddinModel : INotifyPropertyChanged 16 | { 17 | 18 | public CommanderAddinModel() 19 | { 20 | } 21 | 22 | /// 23 | /// Access to the main Markdown Monster applicaiton Model 24 | /// Some of the properties are duplicated on this model 25 | /// to make it easier to access in code. 26 | /// 27 | public AppModel AppModel 28 | { 29 | get { return _appModel; } 30 | set 31 | { 32 | if (Equals(value, _appModel)) return; 33 | _appModel = value; 34 | OnPropertyChanged(); 35 | } 36 | } 37 | private AppModel _appModel; 38 | 39 | 40 | /// 41 | /// Access to the Addin implementation 42 | /// 43 | public CommanderAddin Addin { get; set; } 44 | 45 | 46 | /// 47 | /// Access to Markdown Monster's Main Configuration 48 | /// 49 | public ApplicationConfiguration Configuration => AppModel.Configuration; 50 | 51 | 52 | /// 53 | /// Access to the Addin's Configuration 54 | /// 55 | public CommanderAddinConfiguration AddinConfiguration { get; set; } 56 | 57 | /// 58 | /// Instance of the Addin's Window object 59 | /// 60 | public CommanderWindow AddinWindow { get; set; } 61 | 62 | /// 63 | /// Access to the main Markdown Monster Window 64 | /// 65 | public MainWindow Window { get; set; } 66 | 67 | /// 68 | /// Holds the currently active command instance in the 69 | /// editor/viewer. 70 | /// 71 | public CommanderCommand ActiveCommand 72 | { 73 | get { return _activeCommand; } 74 | set 75 | { 76 | if (Equals(value, _activeCommand)) return; 77 | _activeCommand = value; 78 | OnPropertyChanged(); 79 | } 80 | } 81 | private CommanderCommand _activeCommand; 82 | 83 | 84 | /// 85 | /// Instance of the currently active Markdown Document object 86 | /// 87 | public MarkdownDocument ActiveDocument => AppModel?.ActiveDocument; 88 | 89 | 90 | /// 91 | /// Instance of the Markdown Editor browser and ACE Editor instance 92 | /// 93 | public MarkdownDocumentEditor ActiveEditor => AppModel?.ActiveEditor; 94 | 95 | 96 | /// 97 | /// List of all the open document objects in the editor 98 | /// 99 | public List OpenDocuments => AppModel?.OpenDocuments; 100 | 101 | // TODO: Add this post MM 1.15.4 (property added in that version) 102 | /// 103 | /// List of open editors in tabs 104 | /// 105 | //public List OpenEditors => AppModel?.OpenEditors; 106 | 107 | 108 | /// 109 | /// Executes a process with given command line parameters 110 | /// 111 | /// Executable to run. Full path or exe only if on system path. 112 | /// Command line arguments 113 | /// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait. 114 | /// Process exit code. 0 on success, anything else error. 1460 timed out 115 | public int ExecuteProcess(string executable, string arguments, int timeoutMs = 60000, ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden) 116 | { 117 | Process process; 118 | 119 | try 120 | { 121 | using (process = new Process()) 122 | { 123 | process.StartInfo.FileName = executable; 124 | process.StartInfo.Arguments = arguments; 125 | process.StartInfo.WindowStyle = windowStyle; 126 | if (windowStyle == ProcessWindowStyle.Hidden) 127 | process.StartInfo.CreateNoWindow = true; 128 | 129 | process.StartInfo.UseShellExecute = false; 130 | 131 | process.StartInfo.RedirectStandardOutput = true; 132 | process.StartInfo.RedirectStandardError = true; 133 | 134 | process.OutputDataReceived += (sender, args) => 135 | { 136 | Console.WriteLine(args.Data); 137 | }; 138 | process.ErrorDataReceived += (sender, args) => 139 | { 140 | Console.WriteLine(args.Data); 141 | }; 142 | 143 | process.Start(); 144 | 145 | if (timeoutMs < 0) 146 | timeoutMs = 99999999; // indefinitely 147 | 148 | if (timeoutMs > 0) 149 | { 150 | if (!process.WaitForExit(timeoutMs)) 151 | { 152 | Console.WriteLine("Process timed out."); 153 | return 1460; 154 | } 155 | } 156 | else 157 | return 0; 158 | 159 | return process.ExitCode; 160 | } 161 | } 162 | catch(Exception ex) 163 | { 164 | Console.WriteLine("Error executing process: " + ex.Message); 165 | return -1; // unhandled error 166 | } 167 | } 168 | 169 | 170 | 171 | #region INotifyPropertyChanged 172 | 173 | private ObservableCollection _commands = new ObservableCollection(); 174 | 175 | 176 | public event PropertyChangedEventHandler PropertyChanged; 177 | 178 | [NotifyPropertyChangedInvocator] 179 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 180 | { 181 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 182 | } 183 | #endregion 184 | } 185 | 186 | 187 | } 188 | -------------------------------------------------------------------------------- /CommanderAddin/_classes/CommanderCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | using MarkdownMonster.Annotations; 4 | using Westwind.Scripting; 5 | 6 | namespace CommanderAddin 7 | { 8 | public class CommanderCommand : INotifyPropertyChanged 9 | { 10 | public CommanderCommand() 11 | { 12 | 13 | } 14 | 15 | /// 16 | /// A display name for the snippet. 17 | /// 18 | public string Name 19 | { 20 | get { return _name; } 21 | set 22 | { 23 | if (value == _name) return; 24 | _name = value; 25 | OnPropertyChanged(); 26 | } 27 | } 28 | private string _name; 29 | 30 | 31 | /// 32 | /// The actual snippet text to embed and evaluate 33 | /// 34 | public string CommandText 35 | { 36 | get { return _commandText; } 37 | set 38 | { 39 | if (value == _commandText) return; 40 | _commandText = value; 41 | OnPropertyChanged(); 42 | } 43 | } 44 | private string _commandText; 45 | 46 | public string KeyboardShortcut 47 | { 48 | get { return _keyboardShortcut; } 49 | set 50 | { 51 | if (value == _keyboardShortcut) return; 52 | _keyboardShortcut = value; 53 | OnPropertyChanged(); 54 | } 55 | } 56 | private string _keyboardShortcut; 57 | 58 | 59 | public event PropertyChangedEventHandler PropertyChanged; 60 | 61 | [NotifyPropertyChangedInvocator] 62 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 63 | { 64 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /CommanderAddin/_classes/ScriptParser.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using MahApps.Metro.Controls; 6 | using Westwind.Scripting; 7 | using Westwind.Utilities; 8 | 9 | namespace CommanderAddin 10 | { 11 | 12 | /// 13 | /// A minimal Script Parser class that uses {{ C# Expressions }} to evaluate 14 | /// string based templates. 15 | /// 16 | /// 17 | /// string script = @"Hi {{Name}}! Time is {{DateTime.sNow.ToString(""MMM dd, yyyy HH:mm:ss"")}}..."; 18 | /// var parser = new ScriptParser(); 19 | /// string result = await parser.EvaluateScriptAsync(script, new Globals { Name = "Rick" }); 20 | /// 21 | public class ScriptParser 22 | { 23 | 24 | /// 25 | /// If a compilation error occurs this holds the compiler error message 26 | /// 27 | public string ErrorMessage { get; set; } 28 | 29 | /// 30 | /// Returns source code for the compiled script 31 | /// 32 | public string CompiledCode 33 | { 34 | get 35 | { 36 | if (ScriptInstance == null) return null; 37 | return ScriptInstance.GeneratedClassCodeWithLineNumbers; 38 | } 39 | } 40 | 41 | public CSharpScriptExecution ScriptInstance { get; set; } 42 | 43 | /// 44 | /// Evaluates the embedded script parsing out {{ C# Expression }} 45 | /// blocks and evaluating the expressions and embedding the string 46 | /// output into the result string. 47 | /// 48 | /// The code to execute 49 | /// Optional model data accessible in Expressions as `Model` 50 | /// 51 | public async Task EvaluateScriptAsync(string code, CommanderAddinModel model = null) 52 | { 53 | ScriptInstance = CreateScriptObject(); 54 | 55 | string oldPath = Environment.CurrentDirectory; 56 | code = "public async Task ExecuteScript(CommanderAddinModel Model)\n" + 57 | "{\n" + 58 | code + "\n" + 59 | "}"; 60 | //"return true;\n" + 61 | 62 | await ScriptInstance.ExecuteMethodAsyncVoid(code, "ExecuteScript", model); 63 | 64 | Directory.SetCurrentDirectory(oldPath); 65 | 66 | 67 | if (ScriptInstance.Error) 68 | { 69 | // fix error offsets so they match just the script code 70 | FixupLineNumbersAndErrors(ScriptInstance); 71 | ErrorMessage = ScriptInstance.ErrorMessage; 72 | } 73 | 74 | return !ScriptInstance.Error; 75 | } 76 | 77 | public static AdjustCodeLineNumberStatus FixupLineNumbersAndErrors(CSharpScriptExecution script) 78 | { 79 | var adjusted = new AdjustCodeLineNumberStatus(); 80 | 81 | var code = script.GeneratedClassCode; 82 | 83 | var find = "public async Task ExecuteScript(CommanderAddinModel Model)"; 84 | var lines = StringUtils.GetLines(code); 85 | int i = 0; 86 | for (i = 0; i < lines.Length; i++) 87 | { 88 | if (lines[i]?.Trim() == find) 89 | break; 90 | } 91 | 92 | if (i < lines.Length -1) 93 | adjusted.startLineNumber = i + 2; 94 | 95 | // `(24,1): error CS0246: The type or namespace name...` 96 | lines = StringUtils.GetLines(script.ErrorMessage); 97 | for (i = 0; i < lines.Length; i++) 98 | { 99 | var line = lines[i]; 100 | if (string.IsNullOrEmpty(line)) 101 | continue; 102 | 103 | var linePair = StringUtils.ExtractString(line, "(", "):", returnDelimiters: true); 104 | if (!linePair.Contains(",")) continue; 105 | 106 | var tokens = linePair.Split(','); 107 | 108 | var lineNo = StringUtils.ParseInt(tokens[0].Substring(1), 0); 109 | if (lineNo < adjusted.startLineNumber) continue; 110 | 111 | // update the line number 112 | var newError = "(" + (lineNo - adjusted.startLineNumber ) + "," + tokens[1] + line.Replace(linePair, ""); 113 | lines[i] = newError; 114 | } 115 | 116 | var updatedLines = string.Empty; 117 | foreach (var line in lines) 118 | updatedLines = updatedLines + line + "\n"; 119 | 120 | string.Join(",",lines); 121 | adjusted.UpdatedErrorMessage = updatedLines.Trim(); 122 | 123 | return adjusted; 124 | 125 | } 126 | 127 | /// 128 | /// Creates an instance of wwScripting for this parser 129 | /// with the appropriate assemblies and namespaces set 130 | /// 131 | /// 132 | private CSharpScriptExecution CreateScriptObject() 133 | { 134 | var scripting = new CSharpScriptExecution 135 | { 136 | GeneratedNamespace = "MarkdownMonster.Commander.Scripting", 137 | ThrowExceptions = false, 138 | AllowReferencesInCode = true 139 | }; 140 | 141 | // Use loaded references so **all of MM is available** 142 | scripting.AddLoadedReferences(); 143 | //.AddDefaultReferencesAndNamespaces(); 144 | 145 | scripting.AddAssembly(typeof(CommanderAddin)); 146 | scripting.AddAssembly(typeof(CSharpScriptExecution)); 147 | 148 | scripting.AddNamespaces("System", 149 | "System.Threading.Tasks", 150 | "System.IO", 151 | "System.Reflection", 152 | "System.Text", 153 | "System.Drawing", 154 | "System.Diagnostics", 155 | "System.Data", 156 | "System.Linq", 157 | "System.Windows", 158 | "System.Windows.Controls", 159 | "System.Collections.Generic", 160 | 161 | "Newtonsoft.Json", 162 | "Newtonsoft.Json.Linq", 163 | 164 | "MarkdownMonster", 165 | "MarkdownMonster.Windows", 166 | "Westwind.Utilities", 167 | "Westwind.Scripting", 168 | "CommanderAddin"); 169 | 170 | scripting.SaveGeneratedCode = true; 171 | 172 | return scripting; 173 | } 174 | } 175 | 176 | public class AdjustCodeLineNumberStatus 177 | { 178 | public string UpdatedErrorMessage { get; set; } 179 | public int startLineNumber { get; set; } 180 | } 181 | } -------------------------------------------------------------------------------- /CommanderAddin/hsizegrip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/hsizegrip.png -------------------------------------------------------------------------------- /CommanderAddin/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/icon.png -------------------------------------------------------------------------------- /CommanderAddin/icon_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/icon_22.png -------------------------------------------------------------------------------- /CommanderAddin/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/CommanderAddin/screenshot.png -------------------------------------------------------------------------------- /CommanderAddin/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Commander", 3 | "name": "Commander: C# Script Automation", 4 | "summary": "Create small C# based scripts to automate many operations in Markdown Monster. Shell out to external tools, pull in data from external source, update documents, merge data into the editor and more.", 5 | "description": "Use this addin to automate Markdown Monster using C# syntax. Automate operations inside of Markdown Monster and tie the script to a hotkey. You can access the active document, the editor, the main window and a host of support features to automate document tasks and even control the user interface. Some examples includes launching external applications, merging data into open documents, generate and load new documents, posting custom content to custom services and much more.\r\n\r\nWith full access to C# code, the sky's the limit with what you can accomplish.", 6 | "releaseNotes": null, 7 | "version": "0.3.2", 8 | "minVersion": "3.0.5", 9 | "maxVersion": "3.99", 10 | "author": "© Rick Strahl, West Wind Technologies 2017-2023", 11 | "updated": "2023-10-13T12:00:00Z" 12 | } 13 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/icon.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Markdown Monster Commander Addin 2 | 3 | #### A C# scripting addin for Markdown Monster 4 | 5 | 6 | 7 | This addin allows you to script [Markdown Monster](https://markdownmonster.west-wind.com) via C# script code that is compiled and executed dynamically. It's an easy way to add quick automation features to Markdown Monster without the complexities creating a [full Markdown Monster .NET Addin](http://markdownmonster.west-wind.com/docs/_4ne0s0qoi.htm). 8 | 9 | ![](CommanderAddin/screenshot.png) 10 | 11 | Some things you can do: 12 | 13 | * Access the current document 14 | * Modify the active or any other open document 15 | * Launch a new process 16 | * Open documents or folders in an external Program 17 | * Load data from a database and embed it in a document 18 | * Open current document in another editor at line number 19 | * Open all related documents 20 | * Open a Git Client in the right folder context 21 | * Launch an image optimizer to optimize all images 22 | * Create pre-filled documents into the editor 23 | *(you can also do this with the [Snippets addin](https://markdownmonster.west-wind.com/docs/_5gs0uc49h.htm))* 24 | * etc. 25 | 26 | Using the Command Addin you can access the [Markdown Monster Application Model](https://markdownmonster.west-wind.com/docs/_67613zlo8.htm) (via `Model.AppModel`) to access many of the core Markdown Document and UI features that let you automate and act upon open or selected documents in the editor. 27 | 28 | Snippets can be manually executed and debugged, or you can can tie a keyboard shortcut to a Commander script and invoke it directly without the Snippet Form being active. 29 | 30 | ### Features 31 | Commander has the following useful features: 32 | 33 | * C# Scripting - full C# language access 34 | * Access to Markdown Monster's Model 35 | * Access to Document 36 | * Access to Editor 37 | * Access to Main Window and the entire WPF UI 38 | * Console Output 39 | * Language Features 40 | * Latest C# compiler features 41 | * Async support (required for most editor access now) 42 | * Compilation and Runtime Error reporting 43 | * Source Code display 44 | 45 | ### C# Script Execution 46 | Scripts are executed as C# code that is compiled dynamically as 'in memory' assemblies. The addin caches generated assemblies so that multiple executions don't keep generating new assemblies on repeated use. 47 | 48 | You can execute any .NET code as long as you can reference the required assemblies you need for your code. 49 | 50 | #### Script Execution 51 | Scripts are turned into a C# class that is then compiled into an assembly and executed. Assemblies are created dynamically and cached once generated based on the code snippet's content (plus the compiler mode). If you execute the same script repeatedly, one assembly is generated and used repeatedly. Each new or modified snippet generates a new assembly when it is executed for the first time - subsequent invocations are cached and thus faster. 52 | 53 | Scripts include default assemblies and namespaces that are used in Markdown Monster , so most features that are used in Markdown Monster are already in scope and accessible without adding explicit `#r` assembly references or `using` statements. Add references and namespaces only if you get compilation errors to that effect. 54 | 55 | Scripts execute in the context of a class method in the following format: 56 | 57 | ```cs 58 | public async Task ExecuteScript(CommanderAddinModel Model) 59 | { 60 | // YOUR CODE GOES BELOW 61 | 62 | // #r Westwind.Utilities.dll 63 | // using System.Net; 64 | 65 | var s = StringUtils.ExtractString("testing","te","ng"); 66 | Console.WriteLine(s); 67 | 68 | for(int x = 1; x++) { 69 | Console.sWriteLine( x.ToString() + "..."); 70 | if (x < 3) 71 | await Task.Delay(1000); // async allowed 72 | } 73 | 74 | 75 | using (var client = new WebClient()) 76 | { 77 | var uri = new Uri("https://albumviewer.west-wind.com/api/album/37"); 78 | Console.WriteLine(await client.DownloadStringTaskAsync(uri)); 79 | } 80 | 81 | // END OF YOUR CODE 82 | 83 | return "OK"; // generated to produce a result value 84 | } 85 | ``` 86 | 87 | The method is passed a `CommanderAddinModel` instance which is made available as a `Model` variable. This type exposes most of the common top level objects in Markdown Monster plus MM's main application model: 88 | 89 | * **[AppModel](https://markdownmonster.west-wind.com/docs/_67613zlo8.htm)** 90 | Markdown Monster's Main Application Model that gives access to configuration, window, document, editor, addins and much more. A subset of the properties on this object are exposed in this AddinModel reference for easier access. 91 | * **[Window](https://markdownmonster.west-wind.com/docs/_67613zmkj.htm)** 92 | The main Markdown Monster UI Window which includes access to open tabs, the folder browser and more. 93 | * **[ActiveDocument](https://markdownmonster.west-wind.com/docs/_67613zn74.htm)** 94 | The document in the active tab. Contains the content, filename, load and save operations etc. 95 | * **[ActiveEditor](https://markdownmonster.west-wind.com/docs/_67613znkb.htm)** 96 | provides access to the active editor instance, for manipulating the document's content in the editor. 97 | 98 | * **[Window.FolderBrowser](https://markdownmonster.west-wind.com/docs/_67613zhzo.htm)** 99 | lets you access the open folder browser or open a new folder in the folder browser. 100 | 101 | Methods are executed as code snippets so you don't need to return a value. However, if you need to exit early from a snippet use `return null` or `return someValue`. 102 | 103 | > #### Early Exit via `return` 104 | > If you need to exit the script early using `return` make sure that return some value as the wrapper method signature requires. The code returns `return false` although the return value of the script is irrelevant and ignored. But some value has to be returned. 105 | > 106 | > A return value is **not required** if you don't exit early as the wrapper method has a `return null` at the end. 107 | 108 | #### Assembly Reference and Namespace Dependencies 109 | In order to execute code, the generated assembly has to explicitly load referenced assemblies. The addin runs in the context of Markdown Monster so all of Markdown Monster's references are already preloaded. The addin also pre-loads most of the common namespaces into the generated class. Ideally don't add references or namespaces unless you get a compilation error to that effect. You can look at the source code to see what namespaces are auto-generated. 110 | 111 | If you do need to load additional assemblies you can do so using special reference syntax: 112 | 113 | ```cs 114 | #r System.Windows.Forms.dll 115 | using System.Windows.Forms; 116 | 117 | // Your method code here 118 | MessageBox.Show("Got it!"); 119 | ``` 120 | 121 | ##### #r 122 | The `#r` directive is used to reference an assembly by filename. Assemblies should be referenced as `.dll` file names and cannot contain a path. Assemblies referenced have to either be a GAC installed assembly or they must live in Markdown Monster's startup code to be found. 123 | 124 | > #### No external Assemblies allowed 125 | > We don't allow referencing of pathed assemblies. All assemblies should be referenced just by their .dll file name (ie. `mydll.dll`). 126 | > 127 | > You can only load assemblies located in the GAC, the Markdown Monster Root Folder or the `Addins` folder and below (any sub-folder). These folders are restricted on a normal install and require admin rights to add files, so random files cannot be copied there easily and maliciously executed. 128 | > 129 | > If you need external assemblies in your Scripts or Snippets we recommend you create a folder below the Addins folder like `SupportAssemblies` and put your referenced assemblies there.. 130 | 131 | ##### using 132 | This allows adding namespace references to your scripts the same way you'd use a using statement in a typical .NET class. Make sure any Assemblies you need are loaded. The Command Addin pre-references many common references and namespaces. 133 | 134 | Both of these commands have to be specified at the top of the script text as they are parsed out and added back when code is generated and compiled. 135 | 136 | > #### No Support for Using Aliases 137 | > This addin **does not support C# using aliases**. The following does not work and will result in a compilation error: 138 | > 139 | > ```cs 140 | > using MyBuilder = System.Text.StringBuilder; 141 | > ``` 142 | 143 | #### Error Handling 144 | The Commander Addin has support for capturing and displaying Compiler and Runtime errors in the Console Output of the interface. 145 | 146 | ![](CommanderAddin/ErrorInConsole.png) 147 | 148 | Each script is generated into a self-contained CSharp class that is compiled into its own assembly and then loaded and executed from the generated in-memory assembly. 149 | 150 | If there are source code errors that cause a compilation error, the Addin displays the compiler error messages along with the line number where errors occurred which are adjusted for your code and match the line numbers in the script editor. 151 | 152 | Runtime errors capture only the error as it occurs in the code. Unfortunately no 153 | 154 | the last Call Stack information and provide the last executing line of code that caused the error. This may not always represent the real source of the error since you are executing generated code, but often it does provide some insight into code generated. 155 | 156 | 157 | ### Simple Examples 158 | The following script in the figure retrieves the active document's filename, then shows a directory listing of Markdown Files in the same folder in the Console, and then asks if you want to open the listed files in the editor: 159 | 160 | ![](CommanderAddin/screenshot.png) 161 | 162 | Here are a few more useful examples that you can cut and paste right into the Commander Addin for your own use: 163 | 164 | #### Open in Vs Code 165 | Here's a simple example that opens the currently open document in VS Code. The following code first saves any pending changes, then opens VS Code via `Process.Start()` at the current line number. 166 | 167 | ```cs 168 | var docFile = Model.ActiveDocument.Filename; 169 | if (string.IsNullOrEmpty(docFile)) 170 | return null; 171 | 172 | Model.Window.SaveFile(); 173 | 174 | var exe = @"C:\Program Files\Microsoft VS Code\Code.exe"; 175 | 176 | Process pi = null; 177 | try { 178 | var lineNo = await Model.ActiveEditor.GetLineNumber(); 179 | pi = Process.Start(exe,"-g \"" + docFile + $":{lineNo}\""); 180 | } 181 | catch(Exception ex) { 182 | Model.Window.ShowStatusError("Couldn't open editor: " + ex.Message); 183 | return null; 184 | } 185 | 186 | if (pi != null) 187 | Model.Window.ShowStatusSuccess($"VS Code started with: {docFile}"); 188 | else 189 | Model.Window.ShowStatusError("Failed to start VS Code."); 190 | 191 | ``` 192 | 193 | #### Open All Documents in a Folder in MM 194 | The following retrieves the active document's filename and based on that gets a directory listing for all other `.md` files and optionally opens them all in the editor: 195 | 196 | ```cs 197 | #r Westwind.Utilities.dll 198 | using Westwind.Utilities; 199 | 200 | using System.Windows; 201 | using System.Windows.Threading; 202 | 203 | 204 | var doc = Model.ActiveDocument.Filename; 205 | var docPath = Path.GetDirectoryName(doc); 206 | 207 | Console.WriteLine("Markdown Files in: " + docPath); 208 | 209 | foreach(var file in Directory.GetFiles(docPath,"*.md")) 210 | { 211 | Console.WriteLine(" - " + file + " - " + 212 | StringUtils.FromCamelCase(Path.GetFileName(file))); 213 | } 214 | 215 | if(MessageBox.Show("Do you want to open these Markdown Files?", 216 | "Open All Markdown Files", 217 | MessageBoxButton.YesNo, 218 | MessageBoxImage.Information) == MessageBoxResult.Yes) 219 | { 220 | TabItem tab = null; 221 | foreach(var file in Directory.GetFiles(docPath,"*.md")) 222 | { 223 | tab = await Model.Window.OpenTab(file, batchOpen: true); 224 | await Task.Delay(80); 225 | } 226 | if (tab != null) 227 | await Model.Window.ActivateTab(tab); 228 | } 229 | ``` 230 | 231 | Note that the `Westwind.Utilities` assembly and namespace definitions are actually optional since they are automatically included in the default list of added assemblies and namespaces - they serve mainly for demonstration purposes. 232 | 233 | For security reasons you can load assemblies only from the GAC or from assemblies that are located in the startup folder of Markdown Monster. 234 | 235 | #### Commit Active File to Git Origin 236 | Demonstrates committing the current document to Git locally and then pushing changes to a Git host using the `ExecuteHelper()` function: 237 | 238 | ```cs 239 | // ASSSUMES: Git is in your system path. Otherwise add path here 240 | var gitExe = "git.exe"; 241 | 242 | var doc = Model.ActiveDocument.Filename; 243 | var docFile = Path.GetFileName(doc); 244 | var docPath = Path.GetDirectoryName(doc); 245 | 246 | // MM will reset path when script is complete 247 | Directory.SetCurrentDirectory(docPath); 248 | 249 | Console.WriteLine("Staging " + docFile); 250 | int result = Model.ExecuteProcess(gitExe,"add --force -- \"" + docFile + "\""); 251 | Console.WriteLine(result == 0 ? "Success" : "Nothing to stage. Exit Code: " + result); 252 | 253 | Console.WriteLine("Committing..."); 254 | result = Model.ExecuteProcess(gitExe,"commit -m \"Updating documentation for " + docFile +"\""); 255 | Console.WriteLine(result == 0 ? "Success" : "Nothing to commit. Exit Code: " + result); 256 | 257 | if (result != 0) 258 | return null; 259 | 260 | Console.WriteLine("Pushing..."); 261 | result = Model.ExecuteProcess(gitExe,"push --porcelain --progress --recurse-submodules=check origin refs/heads/master:refs/heads/master"); 262 | Console.WriteLine(result == 0 ? "Success" : "Nothing to push. Exit Code: " + result); 263 | ``` 264 | 265 | #### Open Folder Browser File in VS Code 266 | Here's a command that opens the active FolderBrowser item in Visual Studio Code. It uses the FolderBrowser's `GetSelectedPathItem()` method to retrieve the Path item from and extracts the filename then tries to find VS Code to open the file 267 | 268 | ```cs 269 | // Select a file or folder or the empty space for the current folder 270 | var folderBrowser = Model.Window?.FolderBrowser as FolderBrowerSidebar; 271 | 272 | string docFile = null; 273 | 274 | var item = folderBrowser.GetSelectedPathItem(); 275 | docFile = item?.FullPath; 276 | 277 | Console.WriteLine(docFile); 278 | 279 | if (string.IsNullOrEmpty(docFile) || docFile == ".." || 280 | (!File.Exists(docFile) && !Directory.Exists(docFile) )) 281 | { 282 | docFile = Model?.ActiveDocument?.Filename; 283 | if (docFile == null) 284 | return null; 285 | } 286 | 287 | var exe = @"C:\Program Files\Microsoft VS Code\Code.exe"; 288 | var pi = Process.Start(exe,"\"" + docFile + "\""); 289 | if (pi != null) 290 | Model.Window.ShowStatusSuccess($"VS Code started with: {docFile}"); 291 | else 292 | Model.Window.ShowStatusError("Failed to start VS Code."); 293 | ``` 294 | 295 | 296 | #### 297 | 298 | ```cs 299 | #r Microsoft.VisualBasic.dll 300 | using Microsoft.VisualBasic; 301 | using System.IO; 302 | 303 | var doc = Model.ActiveDocument; 304 | var editor = Model.ActiveEditor; 305 | var currentLineNbr = await editor.GetLineNumber(); 306 | 307 | int lineNbr = 0; 308 | bool found = false; 309 | string token = "grateful or anticipating:"; 310 | string bullets = ""; 311 | 312 | DateTime today = DateTime.Today; 313 | 314 | string userDate = Interaction.InputBox("Enter DateTime (yyyy-mm-dd)","DateTime", today.ToString("yyyy-MM-dd") , 600, 300); 315 | 316 | string dt = Convert.ToDateTime(userDate).AddDays(-7).ToString("yyyy-MM-dd"); 317 | 318 | while (lineNbr < currentLineNbr) 319 | { 320 | string line = await editor.GetLine(lineNbr); 321 | if (line.Trim().ToLower() == "## today " + dt) 322 | { 323 | found = true; 324 | } 325 | if (found) 326 | { 327 | if (line.Trim().ToLower().StartsWith(token)) 328 | { 329 | bullets += " * " + line.Trim().Substring(token.Length) + "\r\n"; 330 | } 331 | } 332 | lineNbr++; 333 | } 334 | 335 | string wupdate = @"Weekly Update " + userDate + @" 336 | ======================== 337 | Work Highlights 338 | --------------- 339 | * 340 | 341 | Personal Highlights 342 | ------------------- 343 | * 344 | * Last week's anticipating/gratitudes 345 | " + bullets + @" 346 | 347 | Week Starting " + userDate + @" 348 | ======================== 349 | * 350 | "; 351 | 352 | if(MessageBox.Show(wupdate,"Embed this into Document?",MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK) 353 | { 354 | await editor.SetSelection(wupdate); 355 | await editor.SetCursorPosition(3, currentLineNbr + 5 ); 356 | 357 | await Model.ActiveEditor.SetEditorFocus(); 358 | } 359 | ``` 360 | 361 | -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickStrahl/Commander-MarkdownMonster-Addin/aa7dab3e1f25579ca2d03fadc30049fd7e7b232c/screenshot2.png --------------------------------------------------------------------------------