
955 changed files with 590574 additions and 17 deletions
@ -0,0 +1,164 @@
|
||||
using Monaco.Helpers; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
using System.Reflection; |
||||
using Windows.Foundation; |
||||
using Windows.UI.Xaml; |
||||
using Windows.UI.Xaml.Controls; |
||||
using Windows.UI.Xaml.Input; |
||||
|
||||
namespace Monaco |
||||
{ |
||||
public partial class CodeEditor |
||||
{ |
||||
// Override default Loaded/Loading event so we can make sure we've initialized our WebView contents with the CodeEditor. |
||||
|
||||
/// <summary> |
||||
/// When Editor is Loading, it is ready to receive commands to the Monaco Engine. |
||||
/// </summary> |
||||
public new event RoutedEventHandler Loading; |
||||
|
||||
/// <summary> |
||||
/// When Editor is Loaded, it has been rendered and is ready to be displayed. |
||||
/// </summary> |
||||
public new event RoutedEventHandler Loaded; |
||||
|
||||
/// <summary> |
||||
/// Called when a link is Ctrl+Clicked on in the editor, set Handled to true to prevent opening. |
||||
/// </summary> |
||||
public event TypedEventHandler<WebView, WebViewNewWindowRequestedEventArgs> OpenLinkRequested; |
||||
|
||||
/// <summary> |
||||
/// Called when an internal exception is encountered while executing a command. (for testing/reporting issues) |
||||
/// </summary> |
||||
public event TypedEventHandler<CodeEditor, Exception> InternalException; |
||||
|
||||
/// <summary> |
||||
/// Custom Keyboard Handler. |
||||
/// </summary> |
||||
public new event WebKeyEventHandler KeyDown; |
||||
|
||||
private ThemeListener _themeListener; |
||||
|
||||
private void WebView_DOMContentLoaded(WebView sender, WebViewDOMContentLoadedEventArgs args) |
||||
{ |
||||
#if DEBUG |
||||
Debug.WriteLine("DOM Content Loaded"); |
||||
#endif |
||||
this._initialized = true; |
||||
} |
||||
|
||||
private async void WebView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args) |
||||
{ |
||||
this.IsLoaded = true; |
||||
|
||||
// Make sure inner editor is focused |
||||
await SendScriptAsync("editor.focus();"); |
||||
|
||||
// If we're supposed to have focus, make sure we try and refocus on our now loaded webview. |
||||
if (FocusManager.GetFocusedElement() == this) |
||||
{ |
||||
this._view.Focus(FocusState.Programmatic); |
||||
} |
||||
|
||||
Loaded?.Invoke(this, new RoutedEventArgs()); |
||||
} |
||||
|
||||
internal ParentAccessor _parentAccessor; |
||||
private KeyboardListener _keyboardListener; |
||||
private long _themeToken; |
||||
|
||||
private void WebView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args) |
||||
{ |
||||
#if DEBUG |
||||
Debug.WriteLine("Navigation Starting"); |
||||
#endif |
||||
_parentAccessor = new ParentAccessor(this); |
||||
_parentAccessor.AddAssemblyForTypeLookup(typeof(Range).GetTypeInfo().Assembly); |
||||
_parentAccessor.RegisterAction("Loaded", CodeEditorLoaded); |
||||
|
||||
_themeListener = new ThemeListener(); |
||||
_themeListener.ThemeChanged += _themeListener_ThemeChanged; |
||||
_themeToken = RegisterPropertyChangedCallback(RequestedThemeProperty, RequestedTheme_PropertyChanged); |
||||
|
||||
_keyboardListener = new KeyboardListener(this); |
||||
|
||||
this._view.AddWebAllowedObject("Debug", new DebugLogger()); |
||||
this._view.AddWebAllowedObject("Parent", _parentAccessor); |
||||
this._view.AddWebAllowedObject("Theme", _themeListener); |
||||
this._view.AddWebAllowedObject("Keyboard", _keyboardListener); |
||||
} |
||||
|
||||
private async void CodeEditorLoaded() |
||||
{ |
||||
if (Decorations != null && Decorations.Count > 0) |
||||
{ |
||||
// Need to retrigger highlights after load if they were set before load. |
||||
await DeltaDecorationsHelperAsync(Decorations.ToArray()); |
||||
} |
||||
|
||||
// Now we're done loading |
||||
Loading?.Invoke(this, new RoutedEventArgs()); |
||||
} |
||||
|
||||
private void WebView_NewWindowRequested(WebView sender, WebViewNewWindowRequestedEventArgs args) |
||||
{ |
||||
// TODO: Should probably create own event args here as we don't want to expose the referrer to our internal page? |
||||
OpenLinkRequested?.Invoke(sender, args); |
||||
} |
||||
|
||||
private async void RequestedTheme_PropertyChanged(DependencyObject obj, DependencyProperty property) |
||||
{ |
||||
var editor = obj as CodeEditor; |
||||
var theme = editor.RequestedTheme; |
||||
var tstr = string.Empty; |
||||
|
||||
if (theme == ElementTheme.Default) |
||||
{ |
||||
tstr = _themeListener.CurrentThemeName; |
||||
} |
||||
else |
||||
{ |
||||
tstr = theme.ToString(); |
||||
} |
||||
|
||||
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => |
||||
{ |
||||
await this.InvokeScriptAsync("changeTheme", new string[] { tstr, _themeListener.IsHighContrast.ToString() }); |
||||
}); |
||||
} |
||||
|
||||
private async void _themeListener_ThemeChanged(ThemeListener sender) |
||||
{ |
||||
if (RequestedTheme == ElementTheme.Default) |
||||
{ |
||||
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => |
||||
{ |
||||
await this.InvokeScriptAsync("changeTheme", args: new string[] { sender.CurrentTheme.ToString(), sender.IsHighContrast.ToString() }); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
internal bool TriggerKeyDown(WebKeyEventArgs args) |
||||
{ |
||||
this.KeyDown?.Invoke(this, args); |
||||
|
||||
return args.Handled; |
||||
} |
||||
|
||||
protected override void OnGotFocus(RoutedEventArgs e) |
||||
{ |
||||
base.OnGotFocus(e); |
||||
|
||||
if (this._view != null && FocusManager.GetFocusedElement() == this) |
||||
{ |
||||
// Forward Focus onto our inner WebView |
||||
this._view.Focus(FocusState.Programmatic); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,178 @@
|
||||
using Monaco.Editor; |
||||
using Monaco.Helpers; |
||||
using Newtonsoft.Json; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Runtime.InteropServices.WindowsRuntime; |
||||
using Windows.Data.Json; |
||||
using Windows.Foundation; |
||||
|
||||
namespace Monaco |
||||
{ |
||||
/// <summary> |
||||
/// Action delegate for <see cref="CodeEditor.AddCommandAsync(int, CommandHandler)"/> and <see cref="CodeEditor.AddCommandAsync(int, CommandHandler, string)"/>. |
||||
/// </summary> |
||||
public delegate void CommandHandler(); |
||||
|
||||
/// <summary> |
||||
/// This file contains Monaco IEditor method implementations we can call on our control. |
||||
/// https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.ieditor.html |
||||
/// https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.icommoncodeeditor.html |
||||
/// </summary> |
||||
#pragma warning disable CS1591 |
||||
public partial class CodeEditor |
||||
{ |
||||
#region Reveal Methods |
||||
public IAsyncAction RevealLineAsync(uint lineNumber) |
||||
{ |
||||
return SendScriptAsync("editor.revealLine(" + lineNumber + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealLineInCenterAsync(uint lineNumber) |
||||
{ |
||||
return SendScriptAsync("editor.revealLineInCenter(" + lineNumber + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealLineInCenterIfOutsideViewportAsync(uint lineNumber) |
||||
{ |
||||
return SendScriptAsync("editor.revealLineInCenterIfOutsideViewport(" + lineNumber + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealLinesAsync(uint startLineNumber, uint endLineNumber) |
||||
{ |
||||
return SendScriptAsync("editor.revealLines(" + startLineNumber + ", " + endLineNumber + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealLinesInCenterAsync(uint startLineNumber, uint endLineNumber) |
||||
{ |
||||
return SendScriptAsync("editor.revealLinesInCenter(" + startLineNumber + ", " + endLineNumber + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealLinesInCenterIfOutsideViewportAsync(uint startLineNumber, uint endLineNumber) |
||||
{ |
||||
return SendScriptAsync("editor.revealLinesInCenterIfOutsideViewport(" + startLineNumber + ", " + endLineNumber + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealPositionAsync(IPosition position) |
||||
{ |
||||
return RevealPositionAsync(position, false, false); |
||||
} |
||||
|
||||
public IAsyncAction RevealPositionAsync(IPosition position, bool revealVerticalInCenter) |
||||
{ |
||||
return RevealPositionAsync(position, revealVerticalInCenter, false); |
||||
} |
||||
|
||||
public IAsyncAction RevealPositionAsync(IPosition position, bool revealVerticalInCenter, bool revealHorizontal) |
||||
{ |
||||
return SendScriptAsync("editor.revealPosition(JSON.parse('" + position.ToJson() + "'), " + JsonConvert.ToString(revealVerticalInCenter) + ", " + JsonConvert.ToString(revealHorizontal) + ")").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealPositionInCenterAsync(IPosition position) |
||||
{ |
||||
return SendScriptAsync("editor.revealPositionInCenter(JSON.parse('" + position.ToJson() + "'))").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealPositionInCenterIfOutsideViewportAsync(IPosition position) |
||||
{ |
||||
return SendScriptAsync("editor.revealPositionInCenterIfOutsideViewport(JSON.parse('" + position.ToJson() + "'))").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealRangeAsync(IRange range) |
||||
{ |
||||
return SendScriptAsync("editor.revealRange(JSON.parse('" + range.ToJson() + "'))").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealRangeAtTopAsync(IRange range) |
||||
{ |
||||
return SendScriptAsync("editor.revealRangeAtTop(JSON.parse('" + range.ToJson() + "'))").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealRangeInCenterAsync(IRange range) |
||||
{ |
||||
return SendScriptAsync("editor.revealRangeInCenter(JSON.parse('" + range.ToJson() + "'))").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncAction RevealRangeInCenterIfOutsideViewportAsync(IRange range) |
||||
{ |
||||
return SendScriptAsync("editor.revealRangeInCenterIfOutsideViewport(JSON.parse('" + range.ToJson() + "'))").AsAsyncAction(); |
||||
} |
||||
#endregion |
||||
|
||||
public IAsyncAction AddActionAsync(IActionDescriptor action) |
||||
{ |
||||
var wref = new WeakReference<CodeEditor>(this); |
||||
_parentAccessor.RegisterAction("Action" + action.Id, new Action(() => { if (wref.TryGetTarget(out CodeEditor editor)) { action?.Run(editor); }})); |
||||
return InvokeScriptAsync("addAction", action).AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncOperation<string> AddCommandAsync(int keybinding, CommandHandler handler) |
||||
{ |
||||
return AddCommandAsync(keybinding, handler, string.Empty); |
||||
} |
||||
|
||||
public IAsyncOperation<string> AddCommandAsync(int keybinding, CommandHandler handler, string context) |
||||
{ |
||||
var name = "Command" + keybinding; |
||||
_parentAccessor.RegisterAction(name, new Action(() => { handler?.Invoke(); })); |
||||
return InvokeScriptAsync<string>("addCommand", new object[] { keybinding, name, context }).AsAsyncOperation(); |
||||
} |
||||
|
||||
public IAsyncOperation<ContextKey> CreateContextKeyAsync(string key, bool defaultValue) |
||||
{ |
||||
var ck = new ContextKey(this, key, defaultValue); |
||||
|
||||
return InvokeScriptAsync("createContext", ck).ContinueWith((noop) => |
||||
{ |
||||
return ck; |
||||
}).AsAsyncOperation(); |
||||
} |
||||
|
||||
public IModel GetModel() |
||||
{ |
||||
return _model; |
||||
} |
||||
|
||||
public IAsyncOperation<IEnumerable<Marker>> GetModelMarkersAsync() // TODO: Filter (string? owner, Uri? resource, int? take) |
||||
{ |
||||
return SendScriptAsync<IEnumerable<Marker>>("monaco.editor.getModelMarkers();").AsAsyncOperation(); |
||||
} |
||||
|
||||
public IAsyncAction SetModelMarkersAsync(string owner, [ReadOnlyArray] IMarkerData[] markers) |
||||
{ |
||||
return SendScriptAsync("monaco.editor.setModelMarkers(model, " + JsonConvert.ToString(owner) + ", " + JsonConvert.SerializeObject(markers) + ");").AsAsyncAction(); |
||||
} |
||||
|
||||
public IAsyncOperation<Position> GetPositionAsync() |
||||
{ |
||||
return SendScriptAsync<Position>("editor.getPosition();").AsAsyncOperation(); |
||||
} |
||||
|
||||
public IAsyncAction SetPositionAsync(IPosition position) |
||||
{ |
||||
return SendScriptAsync("editor.setPosition(" + JsonConvert.SerializeObject(position) + ");").AsAsyncAction(); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.icommoncodeeditor.html#deltadecorations |
||||
/// |
||||
/// Using <see cref="Decorations"/> Property to manipulate decorations instead of calling this directly. |
||||
/// </summary> |
||||
/// <param name="newDecorations"></param> |
||||
/// <returns></returns> |
||||
private IAsyncAction DeltaDecorationsHelperAsync([ReadOnlyArray] IModelDeltaDecoration[] newDecorations) |
||||
{ |
||||
var newDecorationsAdjust = newDecorations ?? Array.Empty<IModelDeltaDecoration>(); |
||||
|
||||
// Update Styles |
||||
return InvokeScriptAsync("updateStyle", CssStyleBroker.Instance.GetStyles()).ContinueWith((noop) => |
||||
{ |
||||
// Send Command to Modify Decorations |
||||
// IMPORTANT: Need to cast to object here as we want this to be a single array object passed as a parameter, not a list of parameters to expand. |
||||
return InvokeScriptAsync("updateDecorations", (object)newDecorationsAdjust); |
||||
}).AsAsyncAction(); |
||||
} |
||||
} |
||||
#pragma warning restore CS1591 |
||||
} |
@ -0,0 +1,311 @@
|
||||
using Monaco.Editor; |
||||
using Monaco.Helpers; |
||||
using Nito.AsyncEx; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
using Windows.Foundation.Collections; |
||||
using Windows.UI.Xaml; |
||||
|
||||
namespace Monaco |
||||
{ |
||||
partial class CodeEditor : IParentAccessorAcceptor |
||||
{ |
||||
public bool IsSettingValue { get; set; } |
||||
|
||||
/// <summary> |
||||
/// Get or Set the CodeEditor Text. |
||||
/// </summary> |
||||
public string Text |
||||
{ |
||||
get { return (string)GetValue(TextProperty); } |
||||
set { SetValue(TextProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for HorizontalLayout. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty TextPropertyField = |
||||
DependencyProperty.Register("Text", typeof(string), typeof(CodeEditor), new PropertyMetadata(string.Empty, (d, e) => { |
||||
if (!(d as CodeEditor).IsSettingValue) |
||||
{ |
||||
(d as CodeEditor)?.InvokeScriptAsync("updateContent", e.NewValue.ToString()); |
||||
} |
||||
})); |
||||
|
||||
public static DependencyProperty TextProperty |
||||
{ |
||||
get |
||||
{ |
||||
return TextPropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Get the current Primary Selected CodeEditor Text. |
||||
/// </summary> |
||||
public string SelectedText |
||||
{ |
||||
get { return (string)GetValue(SelectedTextProperty); } |
||||
set { SetValue(SelectedTextProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for HorizontalLayout. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty SelectedTextPropertyField = |
||||
DependencyProperty.Register(nameof(SelectedText), typeof(string), typeof(CodeEditor), new PropertyMetadata(string.Empty, (d, e) => { |
||||
if (!(d as CodeEditor).IsSettingValue) |
||||
{ |
||||
(d as CodeEditor)?.InvokeScriptAsync("updateSelectedContent", e.NewValue.ToString()); |
||||
} |
||||
})); |
||||
|
||||
public static DependencyProperty SelectedTextProperty |
||||
{ |
||||
get |
||||
{ |
||||
return SelectedTextPropertyField; |
||||
} |
||||
} |
||||
|
||||
public Selection SelectedRange |
||||
{ |
||||
get { return (Selection)GetValue(SelectedRangeProperty); } |
||||
set { SetValue(SelectedRangeProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for SelectedRange. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty SelectedRangePropertyField = |
||||
DependencyProperty.Register(nameof(SelectedRange), typeof(Selection), typeof(CodeEditor), new PropertyMetadata(null)); |
||||
|
||||
public static DependencyProperty SelectedRangeProperty |
||||
{ |
||||
get |
||||
{ |
||||
return SelectedRangePropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Set the Syntax Language for the Code CodeEditor. |
||||
/// |
||||
/// Note: Most likely to change or move location. |
||||
/// </summary> |
||||
public string CodeLanguage |
||||
{ |
||||
get { return (string)GetValue(CodeLanguageProperty); } |
||||
set { SetValue(CodeLanguageProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for HorizontalLayout. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty CodeLanguagePropertyField = |
||||
DependencyProperty.Register("CodeLanguage", typeof(string), typeof(CodeEditor), new PropertyMetadata("xml", (d, e) => { |
||||
var editor = d as CodeEditor; |
||||
|
||||
if (editor.Options != null) |
||||
{ |
||||
// Will trigger its own update of Options, but need this for initialization changes. |
||||
editor.Options.Language = e.NewValue.ToString(); |
||||
} |
||||
|
||||
// TODO: Push this to Options property change check instead... |
||||
// Changes to Language are ignored in Updated Options. |
||||
// https://microsoft.github.io/monaco-editor/api/modules/monaco.editor.html#setmodellanguage. |
||||
(d as CodeEditor)?.InvokeScriptAsync("updateLanguage", e.NewValue.ToString()); |
||||
})); |
||||
|
||||
internal static DependencyProperty CodeLanguageProperty |
||||
{ |
||||
get |
||||
{ |
||||
return CodeLanguagePropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Get or set the CodeEditor Options. |
||||
/// </summary> |
||||
public IEditorConstructionOptions Options |
||||
{ |
||||
get { return (IEditorConstructionOptions)GetValue(OptionsProperty); } |
||||
set { SetValue(OptionsProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for Options. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty OptionsPropertyField = |
||||
DependencyProperty.Register("Options", typeof(IEditorConstructionOptions), typeof(CodeEditor), new PropertyMetadata(new IEditorConstructionOptions(), (d, e) => { |
||||
var value = e.NewValue as IEditorConstructionOptions; |
||||
if (value != null) |
||||
{ |
||||
var editor = d as CodeEditor; |
||||
if (editor != null) |
||||
{ |
||||
editor.InvokeScriptAsync("updateOptions", value.ToJson()); |
||||
|
||||
// Register for sub-property changes on new object |
||||
value.PropertyChanged -= editor.Options_PropertyChanged; |
||||
value.PropertyChanged += editor.Options_PropertyChanged; |
||||
} |
||||
} |
||||
})); |
||||
|
||||
public static DependencyProperty OptionsProperty |
||||
{ |
||||
get |
||||
{ |
||||
return OptionsPropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Get or Set the CodeEditor Text. |
||||
/// </summary> |
||||
public bool HasGlyphMargin |
||||
{ |
||||
get { return (bool)GetValue(HasGlyphMarginProperty); } |
||||
set { SetValue(HasGlyphMarginProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for HorizontalLayout. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty HasGlyphMarginPropertyField = |
||||
DependencyProperty.Register("HasGlyphMargin", typeof(bool), typeof(CodeEditor), new PropertyMetadata(false, (d, e) => { |
||||
(d as CodeEditor).Options.GlyphMargin = e.NewValue as bool?; |
||||
})); |
||||
|
||||
public static DependencyProperty HasGlyphMarginProperty |
||||
{ |
||||
get |
||||
{ |
||||
return HasGlyphMarginPropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Gets or sets text Decorations. |
||||
/// </summary> |
||||
public IObservableVector<IModelDeltaDecoration> Decorations |
||||
{ |
||||
get { return (IObservableVector<IModelDeltaDecoration>)GetValue(DecorationsProperty); } |
||||
set { SetValue(DecorationsProperty, value); } |
||||
} |
||||
|
||||
private AsyncLock _mutexLineDecorations = new AsyncLock(); |
||||
|
||||
// Using a DependencyProperty as the backing store for Options. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty DecorationsPropertyField = |
||||
DependencyProperty.Register("Decorations", typeof(IModelDeltaDecoration), typeof(CodeEditor), new PropertyMetadata(null, async (d, e) => { |
||||
var editor = d as CodeEditor; |
||||
if (editor != null) |
||||
{ |
||||
// We only want to do this one at a time per editor. |
||||
using (await editor._mutexLineDecorations.LockAsync()) |
||||
{ |
||||
var old = e.OldValue as IObservableVector<IModelDeltaDecoration>; |
||||
// Clear out the old line decorations if we're replacing them or setting back to null |
||||
if ((old != null && old.Count > 0) || |
||||
e.NewValue == null) |
||||
{ |
||||
await editor.DeltaDecorationsHelperAsync(null); |
||||
} |
||||
var value = e.NewValue as IObservableVector<IModelDeltaDecoration>; |
||||
|
||||
if (value != null) |
||||
{ |
||||
if (value.Count > 0) |
||||
{ |
||||
await editor.DeltaDecorationsHelperAsync(value.ToArray()); |
||||
} |
||||
|
||||
value.VectorChanged -= editor.Decorations_VectorChanged; |
||||
value.VectorChanged += editor.Decorations_VectorChanged; |
||||
} |
||||
} |
||||
} |
||||
})); |
||||
|
||||
private async void Decorations_VectorChanged(IObservableVector<IModelDeltaDecoration> sender, IVectorChangedEventArgs @event) |
||||
{ |
||||
if (sender != null) |
||||
{ |
||||
// Need to recall mutex as this is called from outside of this initial callback setting it up. |
||||
using (await _mutexLineDecorations.LockAsync()) |
||||
{ |
||||
await DeltaDecorationsHelperAsync(sender.ToArray()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static DependencyProperty DecorationsProperty |
||||
{ |
||||
get |
||||
{ |
||||
return DecorationsPropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Gets or sets the hint Markers. |
||||
/// Note: This property is a helper for <see cref="SetModelMarkersAsync(string, IMarkerData[])"/>; use this property or the method, not both. |
||||
/// </summary> |
||||
public IObservableVector<IMarkerData> Markers |
||||
{ |
||||
get { return (IObservableVector<IMarkerData>)GetValue(MarkersProperty); } |
||||
set { SetValue(MarkersProperty, value); } |
||||
} |
||||
|
||||
private AsyncLock _mutexMarkers = new AsyncLock(); |
||||
|
||||
// Using a DependencyProperty as the backing store for Options. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty MarkersPropertyField = |
||||
DependencyProperty.Register("Markers", typeof(IMarkerData), typeof(CodeEditor), new PropertyMetadata(null, async (d, e) => { |
||||
var editor = d as CodeEditor; |
||||
if (editor != null) |
||||
{ |
||||
// We only want to do this one at a time per editor. |
||||
using (await editor._mutexMarkers.LockAsync()) |
||||
{ |
||||
var old = e.OldValue as IObservableVector<IMarkerData>; |
||||
// Clear out the old markers if we're replacing them or setting back to null |
||||
if ((old != null && old.Count > 0) || |
||||
e.NewValue == null) |
||||
{ |
||||
// TODO: Can I simplify this in this case? |
||||
await editor.SetModelMarkersAsync("CodeEditor", Array.Empty<IMarkerData>()); |
||||
} |
||||
var value = e.NewValue as IObservableVector<IMarkerData>; |
||||
|
||||
if (value != null) |
||||
{ |
||||
if (value.Count > 0) |
||||
{ |
||||
await editor.SetModelMarkersAsync("CodeEditor", value.ToArray()); |
||||
} |
||||
|
||||
value.VectorChanged -= editor.Markers_VectorChanged; |
||||
value.VectorChanged += editor.Markers_VectorChanged; |
||||
} |
||||
} |
||||
} |
||||
})); |
||||
|
||||
private async void Markers_VectorChanged(IObservableVector<IMarkerData> sender, IVectorChangedEventArgs @event) |
||||
{ |
||||
if (sender != null) |
||||
{ |
||||
// Need to recall mutex as this is called from outside of this initial callback setting it up. |
||||
using (await _mutexMarkers.LockAsync()) |
||||
{ |
||||
await SetModelMarkersAsync("CodeEditor", sender.ToArray()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static DependencyProperty MarkersProperty |
||||
{ |
||||
get |
||||
{ |
||||
return MarkersPropertyField; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,279 @@
|
||||
using Collections.Generic; |
||||
using Monaco.Editor; |
||||
using Monaco.Extensions; |
||||
using Monaco.Helpers; |
||||
using System; |
||||
using System.Collections.ObjectModel; |
||||
using System.ComponentModel; |
||||
using System.Diagnostics; |
||||
using System.Reflection; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Threading.Tasks; |
||||
using Windows.Foundation; |
||||
using Windows.UI.Xaml; |
||||
using Windows.UI.Xaml.Controls; |
||||
|
||||
// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235 |
||||
|
||||
namespace Monaco |
||||
{ |
||||
/// <summary> |
||||
/// UWP Windows Runtime Component wrapper for the Monaco CodeEditor |
||||
/// https://microsoft.github.io/monaco-editor/ |
||||
/// </summary> |
||||
[TemplatePart(Name = "View", Type = typeof(WebView))] |
||||
public sealed partial class CodeEditor : Control, INotifyPropertyChanged |
||||
{ |
||||
private bool _initialized; |
||||
private WebView _view; |
||||
private ModelHelper _model; |
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged; |
||||
|
||||
/// <summary> |
||||
/// Template Property used during loading to prevent blank control visibility when it's still loading WebView. |
||||
/// </summary> |
||||
public bool IsLoaded |
||||
{ |
||||
get { return (bool)GetValue(IsLoadedProperty); } |
||||
private set { SetValue(IsLoadedProperty, value); } |
||||
} |
||||
|
||||
// Using a DependencyProperty as the backing store for HorizontalLayout. This enables animation, styling, binding, etc... |
||||
private static readonly DependencyProperty IsLoadedPropertyField = |
||||
DependencyProperty.Register("IsLoaded", typeof(string), typeof(CodeEditor), new PropertyMetadata(false)); |
||||
|
||||
public static DependencyProperty IsLoadedProperty |
||||
{ |
||||
get |
||||
{ |
||||
return IsLoadedPropertyField; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Construct a new IStandAloneCodeEditor. |
||||
/// </summary> |
||||
public CodeEditor() |
||||
{ |
||||
DefaultStyleKey = typeof(CodeEditor); |
||||
|
||||
if (Options != null) |
||||
{ |
||||
// Set Pass-Thru Properties |
||||
Options.GlyphMargin = HasGlyphMargin; |
||||
Options.Language = CodeLanguage; |
||||
|
||||
// Register for changes |
||||
Options.PropertyChanged += Options_PropertyChanged; |
||||
} |
||||
|
||||
// Initialize this here so property changed event will fire and register collection changed event. |
||||
Decorations = new ObservableVector<IModelDeltaDecoration>(); |
||||
Markers = new ObservableVector<IMarkerData>(); |
||||
_model = new ModelHelper(this); |
||||
|
||||
base.Loaded += CodeEditor_Loaded; |
||||
Unloaded += CodeEditor_Unloaded; |
||||
} |
||||
|
||||
private async void Options_PropertyChanged(object sender, PropertyChangedEventArgs e) |
||||
{ |
||||
// TODO: Check for Language property and call other method instead? |
||||
await InvokeScriptAsync("updateOptions", sender); |
||||
} |
||||
|
||||
private void CodeEditor_Loaded(object sender, RoutedEventArgs e) |
||||
{ |
||||
// Do this the 2nd time around. |
||||
if (_model == null && _view != null) |
||||
{ |
||||
_model = new ModelHelper(this); |
||||
|
||||
_parentAccessor = new ParentAccessor(this); |
||||
_parentAccessor.AddAssemblyForTypeLookup(typeof(Range).GetTypeInfo().Assembly); |
||||
_parentAccessor.RegisterAction("Loaded", CodeEditorLoaded); |
||||
|
||||
_themeListener = new ThemeListener(); |
||||
_themeListener.ThemeChanged += _themeListener_ThemeChanged; |
||||
_themeToken = RegisterPropertyChangedCallback(RequestedThemeProperty, RequestedTheme_PropertyChanged); |
||||
|
||||
_keyboardListener = new KeyboardListener(this); |
||||
|
||||
_view.AddWebAllowedObject("Parent", _parentAccessor); |
||||
_view.AddWebAllowedObject("Theme", _themeListener); |
||||
_view.AddWebAllowedObject("Keyboard", _keyboardListener); |
||||
|
||||
Options.PropertyChanged += Options_PropertyChanged; |
||||
|
||||
Decorations.VectorChanged += Decorations_VectorChanged; |
||||
Markers.VectorChanged += Markers_VectorChanged; |
||||
|
||||
_view.NewWindowRequested += WebView_NewWindowRequested; |
||||
|
||||
_initialized = true; |
||||
|
||||
Loading?.Invoke(this, new RoutedEventArgs()); |
||||
|
||||
Unloaded += CodeEditor_Unloaded; |
||||
|
||||
Loaded?.Invoke(this, new RoutedEventArgs()); |
||||
} |
||||
} |
||||
|
||||
private void CodeEditor_Unloaded(object sender, RoutedEventArgs e) |
||||
{ |
||||
Unloaded -= CodeEditor_Unloaded; |
||||
|
||||
if (_view != null) |
||||
{ |
||||
_view.NavigationStarting -= WebView_NavigationStarting; |
||||
_view.DOMContentLoaded -= WebView_DOMContentLoaded; |
||||
_view.NavigationCompleted -= WebView_NavigationCompleted; |
||||
_view.NewWindowRequested -= WebView_NewWindowRequested; |
||||
_initialized = false; |
||||
} |
||||
|
||||
Decorations.VectorChanged -= Decorations_VectorChanged; |
||||
Markers.VectorChanged -= Markers_VectorChanged; |
||||
|
||||
_parentAccessor?.Dispose(); |
||||
_parentAccessor = null; |
||||
Options.PropertyChanged -= Options_PropertyChanged; |
||||
_themeListener.ThemeChanged -= _themeListener_ThemeChanged; |
||||
_themeListener = null; |
||||
UnregisterPropertyChangedCallback(RequestedThemeProperty, _themeToken); |
||||
_keyboardListener = null; |
||||
_model = null; |
||||
} |
||||
|
||||
protected override void OnApplyTemplate() |
||||
{ |
||||
if (_view != null) |
||||
{ |
||||
_view.NavigationStarting -= WebView_NavigationStarting; |
||||
_view.DOMContentLoaded -= WebView_DOMContentLoaded; |
||||
_view.NavigationCompleted -= WebView_NavigationCompleted; |
||||
_view.NewWindowRequested -= WebView_NewWindowRequested; |
||||
_initialized = false; |
||||
} |
||||
|
||||
_view = (WebView)GetTemplateChild("View"); |
||||
|
||||
if (_view != null) |
||||
{ |
||||
_view.NavigationStarting += WebView_NavigationStarting; |
||||
_view.DOMContentLoaded += WebView_DOMContentLoaded; |
||||
_view.NavigationCompleted += WebView_NavigationCompleted; |
||||
_view.NewWindowRequested += WebView_NewWindowRequested; |
||||
_view.Source = new Uri("ms-appx-web:///Monaco/MonacoEditor.html"); |
||||
} |
||||
|
||||
base.OnApplyTemplate(); |
||||
} |
||||
|
||||
internal async Task SendScriptAsync(string script, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
await SendScriptAsync<object>(script, member, file, line); |
||||
} |
||||
|
||||
internal async Task<T> SendScriptAsync<T>(string script, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
if (_initialized) |
||||
{ |
||||
try |
||||
{ |
||||
return await this._view.RunScriptAsync<T>(script, member, file, line); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
InternalException?.Invoke(this, e); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
#if DEBUG |
||||
Debug.WriteLine("WARNING: Tried to call '" + script + "' before initialized."); |
||||
#endif |
||||
} |
||||
|
||||
return default(T); |
||||
} |
||||
|
||||
internal async Task InvokeScriptAsync( |
||||
string method, |
||||
object arg, |
||||
bool serialize = true, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
await this.InvokeScriptAsync<object>(method, new object[] { arg }, serialize, member, file, line); |
||||
} |
||||
|
||||
internal async Task InvokeScriptAsync( |
||||
string method, |
||||
object[] args, |
||||
bool serialize = true, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
await this.InvokeScriptAsync<object>(method, args, serialize, member, file, line); |
||||
} |
||||
|
||||
internal async Task<T> InvokeScriptAsync<T>( |
||||
string method, |
||||
object arg, |
||||
bool serialize = true, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
return await this.InvokeScriptAsync<T>(method, new object[] { arg }, serialize, member, file, line); |
||||
} |
||||
|
||||
internal async Task<T> InvokeScriptAsync<T>( |
||||
string method, |
||||
object[] args, |
||||
bool serialize = true, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
if (_initialized) |
||||
{ |
||||
try |
||||
{ |
||||
return await this._view.InvokeScriptAsync<T>(method, args, serialize, member, file, line); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
InternalException?.Invoke(this, e); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
#if DEBUG |
||||
Debug.WriteLine("WARNING: Tried to call " + method + " before initialized."); |
||||
#endif |
||||
} |
||||
|
||||
return default(T); |
||||
} |
||||
|
||||
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") |
||||
{ |
||||
if (PropertyChanged != null) |
||||
{ |
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,206 @@
|
||||
using Monaco.Helpers; |
||||
using Newtonsoft.Json; |
||||
using Newtonsoft.Json.Serialization; |
||||
using System; |
||||
using System.Linq; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Runtime.Serialization; |
||||
using System.Threading.Tasks; |
||||
using Windows.Data.Json; |
||||
using Windows.UI.Xaml.Controls; |
||||
|
||||
namespace Monaco.Extensions |
||||
{ |
||||
internal static class WebViewExtensions |
||||
{ |
||||
public static async Task RunScriptAsync( |
||||
this WebView _view, |
||||
string script, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
await _view.RunScriptAsync<object>(script, member, file, line); |
||||
} |
||||
|
||||
public static async Task<T> RunScriptAsync<T>( |
||||
this WebView _view, |
||||
string script, |
||||
[CallerMemberName] string member = null, |
||||
[CallerFilePath] string file = null, |
||||
[CallerLineNumber] int line = 0) |
||||
{ |
||||
var start = "try {\n"; |
||||
if (typeof(T) != typeof(object)) |
||||
{ |
||||
script = script.Trim(';'); |
||||
start += "JSON.stringify(" + script + ");"; |
||||
} |
||||
else |
||||
{ |
||||
start += script; |
||||
} |
||||
var fullscript = start + |
||||
"\n} catch (err) { JSON.stringify({ wv_internal_error: true, message: err.message, description: err.description, number: err.number, stack: err.stack }); }"; |
||||
|
||||
if (_view.Dispatcher.HasThreadAccess) |
||||
{ |
||||
try |
||||
{ |
||||
return await RunScriptHelperAsync<T>(_view, fullscript); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
throw new JavaScriptExecutionException(member, file, line, script, e); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
return await _view.Dispatcher.RunTaskAsync(async () => |
||||
{ |
||||
try |
||||
{ |
||||
return await RunScriptHelperAsync<T>(_view, fullscript); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
throw new JavaScriptExecutionException(member, file, line, script, e); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
private static async Task<T> RunScriptHelperAsync<T>(WebView _view, string script) |
||||
{ |
||||
var returnstring = await _view.InvokeScriptAsync("eval", new string[] { script }); |
||||
|
||||
if (JsonObject.TryParse(returnstring, out JsonObject result)) |
||||
{ |
||||
if (result.ContainsKey("wv_internal_error") && result["wv_internal_error"].ValueType == JsonValueType.Boolean && result["wv_internal_error"].GetBoolean()) |
||||
{ |
||||
throw new JavaScriptInnerException(result["message"].GetString(), result["stack"].GetString()); |
||||
} |
||||
} |
||||
|
||||
if (returnstring != null && returnstring != "null") |
||||
{ |
||||
return JsonConvert.DeserializeObject<T>(returnstring); |
||||
} |
||||
|
||||
return default(T); |
||||
} |
||||