object in the app's web page that will show the Monaco Editor.
+///
+[HtmlElement("div")]
+public sealed partial class CodeEditorPresenter : Control, ICodeEditorPresenter, IJSObject
+{
+ private static readonly string UNO_BOOTSTRAP_APP_BASE = Environment.GetEnvironmentVariable(nameof(UNO_BOOTSTRAP_APP_BASE));
+ private static readonly string UNO_BOOTSTRAP_WEBAPP_BASE_PATH = Environment.GetEnvironmentVariable(nameof(UNO_BOOTSTRAP_WEBAPP_BASE_PATH)) ?? "";
+
+ private readonly JSObjectHandle _handle;
+
+ public CodeEditorPresenter()
+ {
+ _handle = JSObjectHandle.Create(this);
+ }
+
+ ///
+ JSObjectHandle IJSObject.Handle => _handle;
+
+ ///
+ public event TypedEventHandler
? NewWindowRequested;
+
+ ///
+ public event TypedEventHandler? NavigationStarting;
+
+ ///
+ public event TypedEventHandler? DOMContentLoaded;
+
+ ///
+ public event TypedEventHandler? NavigationCompleted;
+
+ public async Task LaunchAsync()
+ {
+ Task domContentLoadedEventTask
+ = DispatcherQueue.RunOnUIThreadAsync(
+ DispatcherQueuePriority.Low,
+ () => DOMContentLoaded?.Invoke(this, new CoreWebView2DOMContentLoadedEventArgs()));
+
+ Assembly assembly = GetType().GetTypeInfo().Assembly;
+ using Stream stream = assembly.GetManifestResourceStream("DevToys.MonacoEditor.CodeEditor.Wasm.ts")!;
+ using var streamReader = new StreamReader(stream);
+
+ string monacoEditorJavaScriptEntryPoint = await streamReader.ReadToEndAsync();
+ Guard.IsNotNullOrWhiteSpace(monacoEditorJavaScriptEntryPoint);
+
+ monacoEditorJavaScriptEntryPoint
+ = monacoEditorJavaScriptEntryPoint
+ .Replace($"{{{nameof(UNO_BOOTSTRAP_WEBAPP_BASE_PATH)}}}", UNO_BOOTSTRAP_WEBAPP_BASE_PATH)
+ .Replace($"{{{nameof(UNO_BOOTSTRAP_APP_BASE)}}}", UNO_BOOTSTRAP_APP_BASE);
+
+ await domContentLoadedEventTask;
+
+ await DispatcherQueue.RunOnUIThreadAsync(
+ DispatcherQueuePriority.Low,
+ () => NavigationStarting?.Invoke(this, new CoreWebView2NavigationStartingEventArgs()));
+
+ await this.ExecuteJavascriptAsync(monacoEditorJavaScriptEntryPoint);
+
+ await DispatcherQueue.RunOnUIThreadAsync(
+ DispatcherQueuePriority.Low,
+ () => NavigationCompleted?.Invoke(this, new CoreWebView2NavigationCompletedEventArgs()));
+ }
+}
+
+#endif
diff --git a/src/app/dev/DevToys.MonacoEditor/CodeEditor/CodeEditorPresenter.Windows.cs b/src/app/dev/DevToys.MonacoEditor/CodeEditor/CodeEditorPresenter.Windows.cs
new file mode 100644
index 0000000000..b848f438c9
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/CodeEditor/CodeEditorPresenter.Windows.cs
@@ -0,0 +1,63 @@
+#if __WINDOWS__
+
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.Web.WebView2.Core;
+using Windows.Foundation;
+using Windows.Storage;
+
+namespace DevToys.MonacoEditor;
+
+///
+/// Provides a WebView that displays the Monaco Editor.
+///
+public sealed partial class CodeEditorPresenter : UserControl, ICodeEditorPresenter
+{
+ private readonly WebView2 _webView = new();
+
+ public CodeEditorPresenter()
+ {
+ Content = _webView;
+
+ _webView.CoreWebView2Initialized += WebView_CoreWebView2Initialized;
+ }
+
+ ///
+ public event TypedEventHandler? NewWindowRequested;
+
+ ///
+ public event TypedEventHandler? NavigationStarting;
+
+ ///
+ public event TypedEventHandler? DOMContentLoaded;
+
+ ///
+ public event TypedEventHandler? NavigationCompleted;
+
+ public async Task LaunchAsync()
+ {
+ await _webView.EnsureCoreWebView2Async();
+
+ StorageFile storageFile
+ = await StorageFile.GetFileFromApplicationUriAsync(
+ new Uri("ms-appx:///DevToys.MonacoEditor/CodeEditor/CodeEditor.Windows.html"));
+
+ string rootDirectory = Directory.GetParent(storageFile.Path)!.Parent!.FullName;
+
+ _webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
+ hostName: "devtoys.local",
+ folderPath: rootDirectory,
+ CoreWebView2HostResourceAccessKind.Allow);
+
+ _webView.Source = new Uri("https://devtoys.local/CodeEditor/CodeEditor.Windows.html");
+ }
+
+ private void WebView_CoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
+ {
+ _webView.CoreWebView2.NewWindowRequested += (wv, args) => NewWindowRequested?.Invoke(this, args);
+ _webView.CoreWebView2.NavigationStarting += (wv, args) => NavigationStarting?.Invoke(this, args);
+ _webView.CoreWebView2.DOMContentLoaded += (wv, args) => DOMContentLoaded?.Invoke(this, args);
+ _webView.CoreWebView2.NavigationCompleted += (wv, args) => NavigationCompleted?.Invoke(this, args);
+ }
+}
+
+#endif
diff --git a/src/app/dev/DevToys.MonacoEditor/CodeEditor/ICodeEditorPresenter.cs b/src/app/dev/DevToys.MonacoEditor/CodeEditor/ICodeEditorPresenter.cs
new file mode 100644
index 0000000000..31e6add81c
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/CodeEditor/ICodeEditorPresenter.cs
@@ -0,0 +1,32 @@
+using Microsoft.Web.WebView2.Core;
+using Windows.Foundation;
+
+namespace DevToys.MonacoEditor;
+
+public interface ICodeEditorPresenter
+{
+ ///
+ /// Occurs when a user performs an action in a WebView that causes content to be opened in a new window.
+ ///
+ event TypedEventHandler? NewWindowRequested;
+
+ ///
+ /// Occurs before the WebView navigates to new content.
+ ///
+ event TypedEventHandler? NavigationStarting;
+
+ ///
+ /// Occurs when the WebView has finished parsing the current HTML content.
+ ///
+ event TypedEventHandler? DOMContentLoaded;
+
+ ///
+ /// Occurs when the WebView has finished loading the current content or if navigation has failed.
+ ///
+ event TypedEventHandler? NavigationCompleted;
+
+ ///
+ /// Launch the web element of Monaco editor.
+ ///
+ Task LaunchAsync();
+}
diff --git a/src/app/dev/DevToys.MonacoEditor/DevToys.MonacoEditor.csproj b/src/app/dev/DevToys.MonacoEditor/DevToys.MonacoEditor.csproj
new file mode 100644
index 0000000000..78bdcaf0dd
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/DevToys.MonacoEditor.csproj
@@ -0,0 +1,81 @@
+
+
+ $(NetCore);$(NetCoreWindows)
+
+ true
+
+ false
+ false
+ true
+
+
+
+ true
+ $(DefineConstants);__WINDOWS__
+
+
+
+ true
+ $(DefineConstants);__WASM__
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(Filename).ts
+
+
+
+
+
+
+
+
+
+
+ DevToys.MonacoEditor.CodeEditor.Wasm.ts
+
+
+
+
+ %(Filename)
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/Themes/Generic.xaml b/src/app/dev/DevToys.MonacoEditor/Themes/Generic.xaml
new file mode 100644
index 0000000000..b7028c633c
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/Themes/Generic.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/Monaco.Helpers.DebugLogger.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/Monaco.Helpers.DebugLogger.ts
new file mode 100644
index 0000000000..5ce135fa09
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/Monaco.Helpers.DebugLogger.ts
@@ -0,0 +1,5 @@
+//namespace Monaco.Helpers {
+ interface DebugLogger {
+ log(message: string);
+ }
+//}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/Monaco.Helpers.KeyboardListener.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/Monaco.Helpers.KeyboardListener.ts
new file mode 100644
index 0000000000..2ec5e0341e
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/Monaco.Helpers.KeyboardListener.ts
@@ -0,0 +1,5 @@
+//namespace Monaco.Helpers {
+interface KeyboardListener {
+ keyDown(keycode: number, ctrl: boolean, shift: boolean, alt: boolean, meta: boolean): boolean;
+}
+//}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/otherScriptsToBeOrganized.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/otherScriptsToBeOrganized.ts
new file mode 100644
index 0000000000..9a52eef179
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/otherScriptsToBeOrganized.ts
@@ -0,0 +1,134 @@
+///
+declare var Parent: ParentAccessor;
+declare var Keyboard: KeyboardListener;
+
+declare var editor: monaco.editor.IStandaloneCodeEditor;
+declare var model: monaco.editor.ITextModel;
+declare var contexts: { [index: string]: monaco.editor.IContextKey };//{};
+declare var decorations: string[];
+declare var modifingSelection:boolean; // Supress updates to selection when making edits.
+
+var registerHoverProvider = function (languageId: string) {
+ return monaco.languages.registerHoverProvider(languageId, {
+ provideHover: function (model, position) {
+ return callParentEventAsync("HoverProvider" + languageId, [JSON.stringify(position)]).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ });
+ }
+ });
+};
+
+var addAction = function (action: monaco.editor.IActionDescriptor) {
+ action.run = function (ed) {
+ Parent.callAction("Action" + action.id)
+ };
+
+ editor.addAction(action);
+};
+
+var addCommand = function (keybindingStr, handlerName, context) {
+ return editor.addCommand(parseInt(keybindingStr),
+ function() {
+ let objs = [];
+ if (arguments
+ ) { // Use arguments as Monaco will pass each as it's own parameter, so we don't know how many that may be.
+ for (let i = 1; i < arguments.length; i++) { // Skip first one as that's the sender?
+ objs.push(arguments[i]);
+ }
+ }
+
+ return callParentActionWithParameters(handlerName, objs);
+ },
+ context
+ );
+};
+
+var createContext = function (context) {
+ if (context) {
+ contexts[context.key] = editor.createContextKey(context.key, context.defaultValue);
+ }
+};
+
+var updateContext = function (key, value) {
+ contexts[key].set(value);
+}
+
+var updateContent = function (content) {
+ // Need to ignore updates from us notifying of a change
+ if (content != model.getValue()) {
+ model.setValue(content);
+ }
+};
+
+
+
+
+
+
+
+var updateDecorations = function (newHighlights) {
+ if (newHighlights) {
+ decorations = editor.deltaDecorations(decorations, newHighlights);
+ } else {
+ decorations = editor.deltaDecorations(decorations, []);
+ }
+};
+
+var updateStyle = function (innerStyle) {
+ var style = document.getElementById("dynamic");
+ style.innerHTML = innerStyle;
+};
+
+var getOptions = function (): monaco.editor.IEditorOptions {
+ let opt = null;
+ try {
+ var jopt = getParentJsonValue("Options");
+ // console.log('Options: ' + jopt);
+ opt = JSON.parse(jopt);
+ } finally {
+
+ }
+
+ if (opt != null && typeof opt === "object") {
+ return opt;
+ }
+
+ return {};
+};
+
+var updateOptions = function (opt: monaco.editor.IEditorOptions) {
+ if (opt != null && typeof opt === "object") {
+ editor.updateOptions(opt);
+ }
+};
+
+var updateLanguage = function (language) {
+ monaco.editor.setModelLanguage(model, language);
+};
+
+var changeTheme = function (theme: string, highcontrast) {
+ var newTheme = 'vs';
+ if (highcontrast == "True" || highcontrast == "true") {
+ newTheme = 'hc-black';
+ } else if (theme == "Dark") {
+ newTheme = 'vs-dark';
+ }
+
+ monaco.editor.setTheme(newTheme);
+};
+
+
+
+var keyDown = function (event) {
+ //Debug.log("Key Down:" + event.keyCode + " " + event.ctrlKey);
+ var result = Keyboard.keyDown(event.keyCode, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey);
+ if (result) {
+ event.cancelBubble = true;
+ event.preventDefault();
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+ return false;
+ }
+};
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerCodeLensProvider.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerCodeLensProvider.ts
new file mode 100644
index 0000000000..2c0fc6ae42
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerCodeLensProvider.ts
@@ -0,0 +1,25 @@
+///
+declare var Parent: ParentAccessor;
+
+var registerCodeLensProvider = function (languageId) {
+ return monaco.languages.registerCodeLensProvider(languageId, {
+ provideCodeLenses: function (model, token) {
+ return callParentEventAsync("ProvideCodeLenses" + languageId, null).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ return null;
+
+ });
+ },
+ resolveCodeLens: function (model, codeLens, token) {
+ return callParentEventAsync("ResolveCodeLens" + languageId, [JSON.stringify(codeLens)]).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ return null;
+ });
+ }
+ // TODO: onDidChange, don't know what this does.
+ });
+}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerColorProvider.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerColorProvider.ts
new file mode 100644
index 0000000000..a85a7648f0
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerColorProvider.ts
@@ -0,0 +1,21 @@
+///
+declare var Parent: ParentAccessor;
+
+var registerColorProvider = function (languageId) {
+ return monaco.languages.registerColorProvider(languageId, {
+ provideColorPresentations: function (model, colorInfo, token) {
+ return callParentEventAsync("ProvideColorPresentations" + languageId, [JSON.stringify(colorInfo)]).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ });
+ },
+ provideDocumentColors: function (model, token) {
+ return callParentEventAsync("ProvideDocumentColors" + languageId, []).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ });
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerCompletionItemProvider.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerCompletionItemProvider.ts
new file mode 100644
index 0000000000..d10aa849a4
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/registerCompletionItemProvider.ts
@@ -0,0 +1,23 @@
+///
+declare var Parent: ParentAccessor;
+
+var registerCompletionItemProvider = (languageId, characters) =>
+ monaco.languages.registerCompletionItemProvider(languageId,
+ {
+ triggerCharacters: characters,
+ provideCompletionItems: (model, position, context, token) => callParentEventAsync(
+ "CompletionItemProvider" + languageId,
+ [JSON.stringify(position), JSON.stringify(context)]).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ return null;
+ }),
+ resolveCompletionItem: (item, token) => callParentEventAsync("CompletionItemRequested" + languageId,
+ [JSON.stringify(item)]).then(result => {
+ if (result) {
+ return JSON.parse(result);
+ }
+ return null;
+ })
+ });
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/Monaco.Helpers.ParentAccessor.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/Monaco.Helpers.ParentAccessor.ts
new file mode 100644
index 0000000000..4e672df567
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/Monaco.Helpers.ParentAccessor.ts
@@ -0,0 +1,15 @@
+//namespace Monaco.Helpers {
+ interface ParentAccessor {
+ callAction(name: string): boolean;
+ callActionWithParameters(name: string, parameter1: string, parameter2: string): boolean;
+ callEvent(name: string, callbackMethod: string, parameter1: string, parameter2: string);
+ close();
+ getChildValue(name: string, child: string): any;
+ getJsonValue(name: string, returnId: string);
+ //getValue(name: string, returnId: string);
+ //setValue(name: string, value: any);
+ //setValue(name: string, value: string, type: string);
+ setValue(name: string, value: string);
+ setValueWithType(name: string, value: string, type: string);
+ }
+//}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/Monaco.Helpers.ThemeAccessor.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/Monaco.Helpers.ThemeAccessor.ts
new file mode 100644
index 0000000000..a827cf0817
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/Monaco.Helpers.ThemeAccessor.ts
@@ -0,0 +1,6 @@
+//namespace Monaco.Helpers {
+interface ThemeAccessor {
+ getCurrentThemeName(returnId: string);
+ getIsHighContrast(returnId: string);
+ }
+//}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/asyncCallbackHelpers.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/asyncCallbackHelpers.ts
new file mode 100644
index 0000000000..6f05e51791
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/asyncCallbackHelpers.ts
@@ -0,0 +1,130 @@
+declare var Parent: ParentAccessor;
+declare var Theme: ThemeAccessor;
+
+type MethodWithReturnId = (parameter: string) => void;
+type NumberCallback = (parameter: any) => void;
+declare var asyncCallbackMap: { [promiseId: string]: NumberCallback };
+declare var nextAsync: number;
+
+nextAsync = 1;
+asyncCallbackMap = {};
+
+declare var returnValueCallbackMap: { [returnId: string]: string };
+declare var nextReturn: number;
+
+nextReturn = 1;
+returnValueCallbackMap = {};
+
+const asyncCallback = (promiseId: string, parameter: string) => {
+ const promise = asyncCallbackMap[promiseId];
+ if (promise) {
+ //console.log('Async response: ' + parameter);
+ promise(parameter);
+ }
+}
+
+const returnValueCallback = (returnId: string, returnValue: string) => {
+ //console.log('Return value for id ' + returnId + ' is ' + returnValue);
+ returnValueCallbackMap[returnId] = returnValue;
+}
+
+const invokeAsyncMethod = (syncMethod: NumberCallback): Promise => {
+ if (nextAsync==null) {
+ nextAsync = 0;
+ }
+ if (asyncCallbackMap==null) {
+ asyncCallbackMap = {};
+ }
+ const promise = new Promise((resolve, reject) => {
+ var nextId = nextAsync++;
+ asyncCallbackMap[nextId] = resolve;
+ syncMethod(`${nextId}`);
+ });
+ return promise;
+}
+
+const replaceAll = (str: string, find: string, rep: string): string => {
+ if (find == "\\")
+ {
+ find = "\\\\";
+ }
+ return (`${str}`).replace(new RegExp(find, "g"), rep);
+}
+
+const sanitize = (jsonString: string): string => {
+ if (jsonString == null) {
+ //console.log('Sanitized is null');
+ return null;
+ }
+
+ const replacements = "%&\\\"'{}:,";
+ for (let i = 0; i < replacements.length; i++) {
+ jsonString = replaceAll(jsonString, replacements.charAt(i), `%${replacements.charCodeAt(i)}`);
+ }
+ //console.log('Sanitized: ' + jsonString);
+ return jsonString;
+}
+
+const desantize = (parameter: string): string => {
+ //System.Diagnostics.Debug.WriteLine($"Encoded String: {parameter}");
+ if (parameter == null) return parameter;
+ const replacements = "&\\\"'{}:,%";
+ //System.Diagnostics.Debug.WriteLine($"Replacements: >{replacements}<");
+ for (let i = 0; i < replacements.length; i++)
+ {
+ //console.log("Replacing: >%" + replacements.charCodeAt(i) + "< with >" + replacements.charAt(i) + "< ");
+ parameter = replaceAll(parameter, "%" + replacements.charCodeAt(i), replacements.charAt(i));
+ }
+
+ //console.log("Decoded String: " + parameter );
+ return parameter;
+}
+
+const stringifyForMarshalling = (value: any): string => sanitize(value)
+
+const invokeWithReturnValue = (methodToInvoke: MethodWithReturnId): string => {
+ const nextId = nextReturn++;
+ methodToInvoke(nextId + '');
+ var json = returnValueCallbackMap[nextId];
+ //console.log('Return json ' + json);
+ json = desantize(json);
+ return json;
+}
+
+const getParentValue = (name: string): any => {
+ const jsonString = invokeWithReturnValue((returnId) => Parent.getJsonValue(name, returnId));
+ const obj = JSON.parse(jsonString);
+ return obj;
+}
+
+const getParentJsonValue = (name: string): string =>
+ invokeWithReturnValue((returnId) => Parent.getJsonValue(name, returnId))
+
+const getThemeIsHighContrast = (): boolean =>
+ invokeWithReturnValue((returnId) => Theme.getIsHighContrast(returnId)) == "true";
+
+const getThemeCurrentThemeName = (): string =>
+ invokeWithReturnValue((returnId) => Theme.getCurrentThemeName(returnId));
+
+
+const callParentEventAsync = (name: string, parameters: string[]): Promise =>
+ invokeAsyncMethod(async (promiseId) => {
+ let result = await Parent.callEvent(name,
+ promiseId,
+ parameters != null && parameters.length > 0 ? stringifyForMarshalling(parameters[0]) : null,
+ parameters != null && parameters.length > 1 ? stringifyForMarshalling(parameters[1]) : null);
+ if (result) {
+ //console.log('Parent event result: ' + name + ' - ' + result);
+ result = desantize(result);
+ //console.log('Desanitized: ' + name + ' - ' + result);
+ } else {
+ //console.log('No Parent event result for ' + name);
+ }
+
+ return result;
+ });
+
+const callParentActionWithParameters = (name: string, parameters: string[]): boolean =>
+ Parent.callActionWithParameters(name,
+ parameters != null && parameters.length > 0 ? stringifyForMarshalling(parameters[0]) : null,
+ parameters != null && parameters.length > 1 ? stringifyForMarshalling(parameters[1]) : null);
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/tsconfig.json b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/tsconfig.json
new file mode 100644
index 0000000000..d7cbf4076e
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Wasm/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compileOnSave": true,
+ "compilerOptions": {
+ "emitBOM": false,
+ "target": "ES2015",
+ "lib": [ "ES2015", "DOM" ],
+ "sourceMap": false,
+ "outFile": "..\\WasmScripts\\uno-monaco-helpers.js"
+ },
+ "include": [
+ ".\\",
+ "..\\ts-helpermethods"
+ ],
+ "exclude": [
+ "..\\ts-helpermethods\\ts-helpermethods-uwp"
+ ]
+}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Windows/Monaco.Helpers.ParentAccessor.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Windows/Monaco.Helpers.ParentAccessor.ts
new file mode 100644
index 0000000000..3082610cb4
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Windows/Monaco.Helpers.ParentAccessor.ts
@@ -0,0 +1,14 @@
+//namespace Monaco.Helpers {
+ interface ParentAccessor {
+ callAction(name: string): boolean;
+ callActionWithParameters(name: string, parameters: string[]): boolean;
+ callEvent(name: string, parameters: string[]): Promise
+ close();
+ getChildValue(name: string, child: string): any;
+ getJsonValue(name: string): string;
+ getValue(name: string): any;
+ setValue(name: string, value: any);
+ setValue(name: string, value: string, type: string);
+ }
+
+//}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Windows/asyncCallbackHelpers.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Windows/asyncCallbackHelpers.ts
new file mode 100644
index 0000000000..32bddd1344
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/ts-helpermethods-Windows/asyncCallbackHelpers.ts
@@ -0,0 +1,14 @@
+var callParentEventAsync = function (name: string, parameters: string[]): Promise {
+ return Parent.callEvent(name, parameters);
+}
+
+var callParentActionWithParameters = function (name: string, parameters: string[]): boolean {
+ return Parent.callActionWithParameters(name, parameters);
+}
+
+var getParentJsonValue = function (name: string): string {
+ return Parent.getJsonValue(name);
+}
+
+
+
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/tsconfig.json b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/tsconfig.json
new file mode 100644
index 0000000000..6c7547f82c
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compileOnSave": true,
+ "compilerOptions": {
+ "target": "ES2015",
+ "sourceMap": false
+ }
+}
\ No newline at end of file
diff --git a/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/updateSelectedContent.ts b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/updateSelectedContent.ts
new file mode 100644
index 0000000000..e742a81aa4
--- /dev/null
+++ b/src/app/dev/DevToys.MonacoEditor/ts-helpermethods/updateSelectedContent.ts
@@ -0,0 +1,48 @@
+///
+declare var Parent: ParentAccessor;
+declare var Keyboard: KeyboardListener;
+
+declare var editor: monaco.editor.IStandaloneCodeEditor;
+declare var model: monaco.editor.ITextModel;
+declare var contexts: { [index: string]: monaco.editor.IContextKey };//{};
+declare var decorations: string[];
+declare var modifingSelection: boolean; // Supress updates to selection when making edits.
+
+var updateSelectedContent = function (content) {
+ let selection = editor.getSelection();
+
+ // Need to ignore updates from us notifying of a change
+ if (content != model.getValueInRange(selection)) {
+ modifingSelection = true;
+ let range = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn);
+ let op = { identifier: { major: 1, minor: 1 }, range, text: content, forceMoveMarkers: true };
+
+ // Make change to selection
+ //TODO how to properly fix this code?
+ //model.pushEditOperations([], [op]);
+ model.pushEditOperations([], [op], null);
+
+ // Update selection to new text.
+ var newEndLineNumber = selection.startLineNumber + content.split('\r').length - 1; // TODO: Not sure if line end is situational/platform specific... investigate more.
+ var newEndColumn = (selection.startLineNumber === selection.endLineNumber)
+ ? selection.startColumn + content.length
+ : content.length - content.lastIndexOf('\r');
+
+ selection = selection.setEndPosition(newEndLineNumber, newEndColumn);
+
+ // Update other selection bound for direction.
+
+ //TODO how to properly fix this code?
+ selection = selection.setEndPosition(selection.endLineNumber, selection.endColumn);
+ //if (selection.getDirection() == monaco.SelectionDirection.LTR) {
+ // selection.positionColumn = selection.endColumn;
+ // selection.positionLineNumber = selection.endLineNumber;
+ //} else {
+ // selection.selectionStartColumn = selection.endColumn;
+ // selection.selectionStartLineNumber = selection.endLineNumber;
+ //}
+
+ modifingSelection = false;
+ editor.setSelection(selection);
+ }
+};
\ No newline at end of file
diff --git a/src/app/dev/DevToys.UI/App.xaml.cs b/src/app/dev/DevToys.UI/App.xaml.cs
index 282bab59e9..6b19c40dbd 100644
--- a/src/app/dev/DevToys.UI/App.xaml.cs
+++ b/src/app/dev/DevToys.UI/App.xaml.cs
@@ -145,32 +145,35 @@ private static void InitializeLogging()
builder.AddFilter("Windows", LogLevel.Warning);
builder.AddFilter("Microsoft", LogLevel.Warning);
- // Generic Xaml events
- // builder.AddFilter("Windows.UI.Xaml", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.VisualStateGroup", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.StateTriggerBase", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.UIElement", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.FrameworkElement", LogLevel.Trace );
+ if (Debugger.IsAttached)
+ {
+ // Generic Xaml events
+ builder.AddFilter("Windows.UI.Xaml", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.VisualStateGroup", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.StateTriggerBase", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.UIElement", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.FrameworkElement", LogLevel.Trace);
- // Layouter specific messages
- // builder.AddFilter("Windows.UI.Xaml.Controls", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.Controls.Layouter", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.Controls.Panel", LogLevel.Debug );
+ // Layouter specific messages
+ builder.AddFilter("Windows.UI.Xaml.Controls", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.Controls.Layouter", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.Controls.Panel", LogLevel.Debug);
- // builder.AddFilter("Windows.Storage", LogLevel.Debug );
+ builder.AddFilter("Windows.Storage", LogLevel.Debug);
- // Binding related messages
- // builder.AddFilter("Windows.UI.Xaml.Data", LogLevel.Debug );
- // builder.AddFilter("Windows.UI.Xaml.Data", LogLevel.Debug );
+ // Binding related messages
+ builder.AddFilter("Windows.UI.Xaml.Data", LogLevel.Debug);
+ builder.AddFilter("Windows.UI.Xaml.Data", LogLevel.Debug);
- // Binder memory references tracking
- // builder.AddFilter("Uno.UI.DataBinding.BinderReferenceHolder", LogLevel.Debug );
+ // Binder memory references tracking
+ builder.AddFilter("Uno.UI.DataBinding.BinderReferenceHolder", LogLevel.Debug);
- // RemoteControl and HotReload related
- // builder.AddFilter("Uno.UI.RemoteControl", LogLevel.Information);
+ // RemoteControl and HotReload related
+ builder.AddFilter("Uno.UI.RemoteControl", LogLevel.Information);
- // Debug JS interop
- // builder.AddFilter("Uno.Foundation.WebAssemblyRuntime", LogLevel.Debug );
+ // Debug JS interop
+ builder.AddFilter("Uno.Foundation.WebAssemblyRuntime", LogLevel.Debug);
+ }
});
global::Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory = factory;
diff --git a/src/app/dev/DevToys.UI/Core/Threading/TaskExtension.cs b/src/app/dev/DevToys.UI/Core/Threading/TaskExtension.cs
new file mode 100644
index 0000000000..395972bdda
--- /dev/null
+++ b/src/app/dev/DevToys.UI/Core/Threading/TaskExtension.cs
@@ -0,0 +1,54 @@
+using Windows.Foundation;
+using System.Threading.Tasks;
+
+namespace DevToys.ComponentModel.Threading;
+
+///
+/// Provides a set of helper method to play around with threads.
+///
+internal static class TaskExtension
+{
+ ///
+ /// Runs a task without waiting for its result.
+ ///
+ internal static void Forget(this Task _)
+ {
+ }
+ ///
+ /// Runs a task without waiting for its result.
+ ///
+ internal static void Forget(this IAsyncAction _)
+ {
+ }
+
+ ///
+ /// Runs a task without waiting for its result.
+ ///
+ internal static void Forget(this Task _)
+ {
+ }
+
+ ///
+ /// Runs a task without waiting for its result. Swallows or handle any exception caused by the task.
+ ///
+ /// The action to run when an exception is caught.
+ internal static async void ForgetSafely(this Task task, Action? errorHandler = null)
+ {
+ try
+ {
+ await task.ConfigureAwait(true);
+ }
+ catch (Exception ex)
+ {
+ errorHandler?.Invoke(ex);
+ }
+ }
+
+ ///
+ /// Gets the result of the task synchronously, on the current thread.
+ ///
+ internal static T CompleteOnCurrentThread(this Task task)
+ {
+ return task.GetAwaiter().GetResult();
+ }
+}
diff --git a/src/app/dev/DevToys.UI/Core/Threading/ThreadHelper.cs b/src/app/dev/DevToys.UI/Core/Threading/ThreadHelper.cs
new file mode 100644
index 0000000000..31306e164d
--- /dev/null
+++ b/src/app/dev/DevToys.UI/Core/Threading/ThreadHelper.cs
@@ -0,0 +1,188 @@
+using Microsoft.UI.Dispatching;
+using System.Threading.Tasks;
+
+namespace DevToys.ComponentModel.Threading;
+
+internal static class ThreadHelper
+{
+ internal static void ThrowIfNotOnUIThread(this DispatcherQueue dispatcherQueue)
+ {
+ if (!dispatcherQueue.HasThreadAccess)
+ {
+ throw new Exception("The UI thread is expected, but the current call stack is running on another thread.");
+ }
+ }
+
+ internal static void ThrowIfOnUIThread(this DispatcherQueue dispatcherQueue)
+ {
+ if (dispatcherQueue.HasThreadAccess)
+ {
+ throw new Exception("The UI thread is not expected, but the current call stack is running on UI thread.");
+ }
+ }
+
+ internal static Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, Action action)
+ {
+ return RunOnUIThreadAsync(dispatcherQueue, DispatcherQueuePriority.Normal, action);
+ }
+
+ internal static Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority, Action action)
+ {
+ if (action is null)
+ {
+ return Task.CompletedTask;
+ }
+
+ if (dispatcherQueue.HasThreadAccess && priority == DispatcherQueuePriority.Normal)
+ {
+ action();
+ return Task.CompletedTask;
+ }
+ else
+ {
+ var tcs = new TaskCompletionSource(0);
+ dispatcherQueue.TryEnqueue(
+ priority,
+ () =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetException(ex);
+ }
+ finally
+ {
+ tcs.TrySetResult(0);
+ }
+ });
+ return tcs.Task;
+ }
+ }
+
+ internal static Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, Func func)
+ {
+ return RunOnUIThreadAsync(dispatcherQueue, DispatcherQueuePriority.Normal, func);
+ }
+
+ internal static Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority, Func func)
+ {
+ if (func is null)
+ {
+ return Task.FromResult(default!);
+ }
+
+ if (dispatcherQueue.HasThreadAccess && priority == DispatcherQueuePriority.Normal)
+ {
+ return Task.FromResult(func());
+ }
+ else
+ {
+ var tcs = new TaskCompletionSource();
+ dispatcherQueue.TryEnqueue(
+ priority,
+ () =>
+ {
+ T result = default!;
+ try
+ {
+ result = func();
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetException(ex);
+ }
+ finally
+ {
+ tcs.TrySetResult(result);
+ }
+ });
+ return tcs.Task;
+ }
+ }
+
+ internal static Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, Func action)
+ {
+ return RunOnUIThreadAsync(dispatcherQueue, DispatcherQueuePriority.Normal, action);
+ }
+
+ internal static async Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority, Func action)
+ {
+ if (action is null)
+ {
+ return;
+ }
+
+ if (dispatcherQueue.HasThreadAccess && priority == DispatcherQueuePriority.Normal)
+ {
+ await action().ConfigureAwait(true);
+ }
+ else
+ {
+ var tcs = new TaskCompletionSource(0);
+ dispatcherQueue.TryEnqueue(
+ priority,
+ async () =>
+ {
+ try
+ {
+ ThrowIfNotOnUIThread(dispatcherQueue);
+ await action().ConfigureAwait(true);
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetException(ex);
+ }
+ finally
+ {
+ tcs.TrySetResult(0);
+ }
+ });
+
+ await tcs.Task.ConfigureAwait(false);
+ }
+ }
+
+ internal static Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, Func> action)
+ {
+ return RunOnUIThreadAsync(dispatcherQueue, DispatcherQueuePriority.Normal, action);
+ }
+
+ internal static async Task RunOnUIThreadAsync(this DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority, Func> action)
+ {
+ Guard.IsNotNull(action);
+
+ if (dispatcherQueue.HasThreadAccess && priority == DispatcherQueuePriority.Normal)
+ {
+ return await action().ConfigureAwait(true);
+ }
+ else
+ {
+ T result = default!;
+ var tcs = new TaskCompletionSource(0);
+ dispatcherQueue.TryEnqueue(
+ priority,
+ async () =>
+ {
+ try
+ {
+ ThrowIfNotOnUIThread(dispatcherQueue);
+ result = await action().ConfigureAwait(true);
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetException(ex);
+ }
+ finally
+ {
+ tcs.TrySetResult(0);
+ }
+ });
+
+ await tcs.Task.ConfigureAwait(false);
+ return result!;
+ }
+ }
+}
diff --git a/src/app/dev/DevToys.UI/DevToys.UI.projitems b/src/app/dev/DevToys.UI/DevToys.UI.projitems
index 366ddd6758..db4430a31b 100644
--- a/src/app/dev/DevToys.UI/DevToys.UI.projitems
+++ b/src/app/dev/DevToys.UI/DevToys.UI.projitems
@@ -18,6 +18,8 @@
App.xaml
+
+
@@ -33,26 +35,20 @@
MSBuild:Compile
-
<_Globbed_Compile Include="$(MSBuildThisFileDirectory)**/*.xaml.cs" Exclude="@(Compile)">
%(Filename)
<_Globbed_Compile Include="$(MSBuildThisFileDirectory)**/*.cs" Exclude="@(Compile);@(_Globbed_Compile)" />
-
<_Globbed_PRIResource Include="$(MSBuildThisFileDirectory)**/*.resw" Exclude="@(PRIResource)" />
-
<_Globbed_Content Include="$(MSBuildThisFileDirectory)Assets/**/*.*" Exclude="@(Content)" />
-
-
-
<_Globbed_Embedded_Resource Include="$(MSBuildThisFileDirectory)*.json" Exclude="@(EmbeddedResource)" />
diff --git a/src/app/dev/DevToys.UI/DevToys.UI.shproj b/src/app/dev/DevToys.UI/DevToys.UI.shproj
index 48de812ae6..c80c807277 100644
--- a/src/app/dev/DevToys.UI/DevToys.UI.shproj
+++ b/src/app/dev/DevToys.UI/DevToys.UI.shproj
@@ -1,13 +1,17 @@
-
- 6279c845-92f8-4333-ab99-3d213163593c
- 14.0
-
-
-
-
-
-
-
-
+
+ 6279c845-92f8-4333-ab99-3d213163593c
+ 14.0
+
+
+
+
+
+
+
+
+ <_Globbed_Compile Remove="Core\Threading\TaskExtension.cs" />
+ <_Globbed_Compile Remove="Core\Threading\ThreadHelper.cs" />
+
+
\ No newline at end of file
diff --git a/src/app/dev/DevToys.UI/Views/MainPage.xaml b/src/app/dev/DevToys.UI/Views/MainPage.xaml
index 050d6386cc..db013445d6 100644
--- a/src/app/dev/DevToys.UI/Views/MainPage.xaml
+++ b/src/app/dev/DevToys.UI/Views/MainPage.xaml
@@ -5,10 +5,28 @@
xmlns:local="using:DevToys.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:monaco="using:DevToys.MonacoEditor"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/dev/platforms/DevToys.Wasm/DevToys.Wasm.csproj b/src/app/dev/platforms/DevToys.Wasm/DevToys.Wasm.csproj
index 8fa8c6d991..940da7c32a 100644
--- a/src/app/dev/platforms/DevToys.Wasm/DevToys.Wasm.csproj
+++ b/src/app/dev/platforms/DevToys.Wasm/DevToys.Wasm.csproj
@@ -24,16 +24,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -52,6 +42,14 @@
+
+
+
+
@@ -66,6 +64,7 @@
+
\ No newline at end of file
diff --git a/src/app/dev/platforms/DevToys.Windows/DevToys.Windows.csproj b/src/app/dev/platforms/DevToys.Windows/DevToys.Windows.csproj
index 7784dcb2fd..7e09d572a3 100644
--- a/src/app/dev/platforms/DevToys.Windows/DevToys.Windows.csproj
+++ b/src/app/dev/platforms/DevToys.Windows/DevToys.Windows.csproj
@@ -10,6 +10,7 @@
win10-$(Platform).pubxml
true
true
+ true
@@ -40,6 +41,7 @@
+
@@ -52,8 +54,8 @@
the "Microsoft.Windows.SDK.BuildTools" package above, and the "revision" version number
must be the highest found in https://www.nuget.org/packages/Microsoft.Windows.SDK.NET.Ref.
-->
-
-
+
+
diff --git a/src/app/dev/shared/SharedAssemblyInfo.cs b/src/app/dev/shared/SharedAssemblyInfo.cs
index 2486d5d821..c0a5180dac 100644
--- a/src/app/dev/shared/SharedAssemblyInfo.cs
+++ b/src/app/dev/shared/SharedAssemblyInfo.cs
@@ -10,7 +10,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
-#if NET6_0_OR_GREATER && WINDOWS && !HAS_UNO
+#if (NET6_0_OR_GREATER && WINDOWS && !HAS_UNO) || (__WINDOWS__)
using System.Runtime.Versioning;
#endif
@@ -25,7 +25,7 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-#if NET6_0_OR_GREATER && WINDOWS && !HAS_UNO
+#if (NET6_0_OR_GREATER && WINDOWS && !HAS_UNO) || (__WINDOWS__)
[assembly: SupportedOSPlatform("windows10.0.17763.0")]
#endif
diff --git a/tools/Restore-MonacoEditor.ps1 b/tools/Restore-MonacoEditor.ps1
new file mode 100644
index 0000000000..e002e3aa39
--- /dev/null
+++ b/tools/Restore-MonacoEditor.ps1
@@ -0,0 +1,50 @@
+[CmdletBinding()]
+Param(
+ [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
+ [string]$RootFolder
+)
+
+Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
+
+if (-not (Test-Path -Path "$RootFolder\\.gitignore"))
+{
+ Write-Error "Please run this script from the repository's root folder"
+ return
+}
+
+# Reference to Monaco Version to Use in the Package
+$monaco_version = "0.34.1"
+
+# ------------------------
+$monaco_tgz_url = "https://registry.npmjs.org/monaco-editor/-/monaco-editor-$monaco_version.tgz"
+$temp_dir_name = ".temp"
+$script_dir = "$RootFolder\tools"
+
+Push-Location $script_dir
+
+# Remove Old Dependency
+Remove-Item "..\src\app\dev\DevToys.MonacoEditor\monaco-editor" -Force -Recurse -ErrorAction SilentlyContinue
+
+# Clean-up Temp Dir, if already exist
+Remove-Item $temp_dir_name -Force -Recurse -ErrorAction SilentlyContinue
+
+# Create Temp Directory and Output
+New-Item -Name $temp_dir_name -ItemType Directory | Out-Null
+New-Item -Name "..\src\app\dev\DevToys.MonacoEditor\monaco-editor" -ItemType Directory | Out-Null
+
+Write-Host "Downloading Monaco"
+
+[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
+Invoke-WebRequest -Uri $monaco_tgz_url -OutFile ".\$temp_dir_name\monaco.tgz"
+
+Write-Host "Extracting..."
+
+mkdir "$script_dir\$temp_dir_name\monaco"
+tar -zxf "$script_dir\$temp_dir_name\monaco.tgz" -C "$script_dir\$temp_dir_name\monaco"
+
+Copy-Item -Path ".\$temp_dir_name\monaco\package\*" -Destination "..\src\app\dev\DevToys.MonacoEditor\monaco-editor" -Recurse
+
+# Clean-up Temp Dir
+Remove-Item $temp_dir_name -Force -Recurse -ErrorAction SilentlyContinue
+
+Pop-Location
\ No newline at end of file
diff --git a/tools/Restore-MonacoEditor.sh b/tools/Restore-MonacoEditor.sh
new file mode 100644
index 0000000000..d34fbc2fb5
--- /dev/null
+++ b/tools/Restore-MonacoEditor.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+bash --version 2>&1 | head -n 1
+
+set -eo pipefail
+
+if ! [[ -f "$SCRIPT_DIR//.gitignore" ]]; then
+ echo "Please run this script from the repository's root folder"
+ exit -1
+fi
+
+# Reference to Monaco Version to Use in the Package
+MONACO_VERSION="0.34.1"
+
+# ------------------------
+MONACO_TGZ_URL="https://registry.npmjs.org/monaco-editor/-/monaco-editor-$MONACO_VERSION.tgz"
+TEMP_DIR_NAME=".temp"
+SCRIPT_DIR="$1/tools"
+
+cd $SCRIPT_DIR
+
+# Remove Old Dependency
+rm -rf "../src/app/dev/DevToys.MonacoEditor/monaco-editor"
+
+# Clean-up Temp Dir, if already exist
+rm -rf "$SCRIPT_DIR/$TEMP_DIR_NAME"
+
+# Create Temp Directory and Output
+mkdir "$SCRIPT_DIR/$TEMP_DIR_NAME"
+mkdir "../src/app/dev/DevToys.MonacoEditor/monaco-editor"
+
+echo "Downloading Monaco"
+
+curl -Lsfo "$SCRIPT_DIR/$TEMP_DIR_NAME/monaco.tgz" $MONACO_TGZ_URL
+
+echo "Extracting..."
+
+mkdir "$SCRIPT_DIR/$TEMP_DIR_NAME/monaco"
+tar -zxf "$SCRIPT_DIR/$TEMP_DIR_NAME/monaco.tgz" -C "$SCRIPT_DIR/$TEMP_DIR_NAME/monaco"
+
+cp -r "$SCRIPT_DIR/$TEMP_DIR_NAME/monaco/package/" "../src/app/dev/DevToys.MonacoEditor/monaco-editor"
+
+# Clean-up Temp Dir
+rm -rf "$SCRIPT_DIR/$TEMP_DIR_NAME"
+
+cd $1
\ No newline at end of file