├── .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 | 
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 | 
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 | 
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
--------------------------------------------------------------------------------