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 { /// /// UWP Windows Runtime Component wrapper for the Monaco CodeEditor /// https://microsoft.github.io/monaco-editor/ /// [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; /// /// Template Property used during loading to prevent blank control visibility when it's still loading WebView. /// 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; } } /// /// Construct a new IStandAloneCodeEditor. /// 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(); Markers = new ObservableVector(); _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(script, member, file, line); } internal async Task SendScriptAsync(string script, [CallerMemberName] string member = null, [CallerFilePath] string file = null, [CallerLineNumber] int line = 0) { if (_initialized) { try { return await this._view.RunScriptAsync(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(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(method, args, serialize, member, file, line); } internal async Task InvokeScriptAsync( string method, object arg, bool serialize = true, [CallerMemberName] string member = null, [CallerFilePath] string file = null, [CallerLineNumber] int line = 0) { return await this.InvokeScriptAsync(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) { if (_initialized) { try { return await this._view.InvokeScriptAsync(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)); } } } }