using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading; using ICSharpCode.AvalonEdit.Highlighting; using InABox.Core; using InABox.Scripting; using InABox.WPF; using InABox.Wpf; using Microsoft.CodeAnalysis; using RoslynPad.Editor; using RoslynPad.Roslyn; using Syncfusion.Data.Extensions; using Image = System.Windows.Controls.Image; namespace InABox.DynamicGrid { /// /// Interaction logic for ScriptEditor.xaml /// public partial class ScriptEditor : ThemableWindow { public static RoutedCommand SaveCommand = new(); private bool _bChanged; private string _editorTitle = "ScriptEditor"; public string EditorTitle { get => _editorTitle; set { _editorTitle = value; UpdateTitle(); } } private RoslynHost _host; private readonly SyntaxLanguage _language = SyntaxLanguage.CSharp; private readonly string _script = ""; private Dictionary _snippets = new(); private bool bCompiled; private readonly Dictionary compile = SetupImage(Properties.Resources.tick); private readonly Dictionary copy = SetupImage(Properties.Resources.copy); private readonly Dictionary cut = SetupImage(Properties.Resources.cut); private readonly Dictionary paste = SetupImage(Properties.Resources.paste); private readonly Dictionary print = SetupImage(Properties.Resources.print); private readonly Dictionary redo = SetupImage(Properties.Resources.redo); private readonly Dictionary run = SetupImage(Properties.Resources.run); private readonly Dictionary save = SetupImage(Properties.Resources.disk); private readonly Dictionary undo = SetupImage(Properties.Resources.undo); static ScriptEditor() { SaveCommand.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control)); } public ScriptEditor(string script, SyntaxLanguage language = SyntaxLanguage.CSharp, string scriptTitle = "ScriptEditor") { _language = language; _script = script; EditorTitle = scriptTitle; InitializeComponent(); // Not Sure if we need Roslyn for XAML - need to research this if (language == SyntaxLanguage.CSharp || language == SyntaxLanguage.XAML) try { if (ScriptDocument.Host == null) ScriptDocument.Initialize(); } catch (Exception e) { MessageBox.Show("Unable to initialize Script Editor!\nPlease try again.\n\n" + e.Message); } Roslyn.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition( language == SyntaxLanguage.HTML ? "HTML" : language == SyntaxLanguage.XAML ? "XML" : language == SyntaxLanguage.CSS ? "CSS" : "C#" ); Roslyn.TextArea.TextEntering += TextArea_TextEntering; CheckButton(SaveButton, true, save); CheckButton(CopyButton, false, copy); CheckButton(CutButton, false, cut); CheckButton(PasteButton, false, paste); CheckButton(UndoButton, false, undo); CheckButton(RedoButton, false, redo); CheckButton(PrintButton, true, print); CheckButton(CompileButton, language == SyntaxLanguage.CSharp || language == SyntaxLanguage.XAML, compile); CheckButton(RunButton, false, run); var timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(500); timer.Tick += Timer_Tick; timer.Start(); bChanged = false; } public string Script => Roslyn.Text; public Dictionary Snippets { get => _snippets; set { _snippets = value; if (_snippets != null && _snippets.Any()) { SnippetPanel.Visibility = Visibility.Visible; SnippetSection.ItemsSource = _snippets.Keys; SnippetSection.SelectedValue = _snippets.Keys.First(); } else { SnippetPanel.Visibility = Visibility.Collapsed; } } } private bool bChanged { get => _bChanged; set { _bChanged = value; UpdateTitle(); } } private static Dictionary SetupImage(Bitmap bitmap) { return new Dictionary { { true, new Image { Source = bitmap.AsBitmapImage(), MaxHeight = 32, MaxWidth = 32 } }, { false, new Image { Source = bitmap.AsGrayScale().AsBitmapImage(), MaxHeight = 32, MaxWidth = 32 } } }; } private event EventHandler _onSave; public event EventHandler OnSave { add { _onSave += value; SaveButton.ToolTip = "Save"; } remove { _onSave -= value; if (_onSave == null) SaveButton.ToolTip = "Save & Close"; } } private event EventHandler _onCompile; public event EventHandler OnCompile { add { _onCompile += value; CheckButton(CompileButton, true, compile); } remove => _onCompile -= value; } private void CheckButton(Button button, bool condition, Dictionary images) { if (button.Visibility != Visibility.Visible) button.Visibility = Visibility.Visible; if (condition != button.IsEnabled) { button.IsEnabled = condition; button.Content = images[condition]; } } private void UpdateTitle() { if (bChanged) { Title = _editorTitle + "*"; } else { Title = _editorTitle; } } #region Public Interface public ScriptEditor ClearErrors() { Errors.Items.Clear(); return this; } public ScriptEditor AddError(string error) { Errors.Items.Add(error); return this; } public void Save() { bChanged = false; if (_onSave != null) { _onSave?.Invoke(this, EventArgs.Empty); } else { DialogResult = true; Close(); } } #endregion #region Event Handlers private void TextArea_TextEntering(object sender, TextCompositionEventArgs e) { if (string.Equals(e.Text, "\r")) e.Handled = true; } private void OnLoaded(object sender, RoutedEventArgs e) { if (_language == SyntaxLanguage.CSharp || _language == SyntaxLanguage.XAML) { Roslyn.DataContext = new ScriptDocument(_script); } else { Roslyn.Text = _script; bChanged = false; } //Roslyn.Text = _script; //Changed = DateTime.MinValue; } private void Roslyn_Loaded(object sender, RoutedEventArgs e) { var editor = (RoslynCodeEditor)sender; editor.Focus(); if (_language == SyntaxLanguage.CSharp || _language == SyntaxLanguage.XAML) { var changed = bChanged; var script = (ScriptDocument)editor.DataContext; script.Id = editor.Initialize( ScriptDocument.Host, new ClassificationHighlightColors(), Directory.GetCurrentDirectory(), _script, SourceCodeKind.Regular ); bChanged = changed; } } private void Roslyn_PreviewKeyDown(object sender, KeyEventArgs e) { if (bCompiled) { bCompiled = false; CheckButton(RunButton, false, run); } } private void Roslyn_TextChanged(object sender, EventArgs e) { bChanged = true; if (bCompiled) { bCompiled = false; CheckButton(RunButton, false, run); } } private void Timer_Tick(object sender, EventArgs e) { CheckButton(CopyButton, !string.IsNullOrEmpty(Roslyn.SelectedText), copy); CheckButton(CutButton, !string.IsNullOrEmpty(Roslyn.SelectedText), cut); CheckButton(PasteButton, Clipboard.ContainsText(), paste); CheckButton(UndoButton, Roslyn.CanUndo, undo); CheckButton(RedoButton, Roslyn.CanRedo, redo); } private void OKButton_Click(object sender, RoutedEventArgs e) { DialogResult = true; Close(); } private void CancelButton_Click(object sender, RoutedEventArgs e) { DialogResult = false; Close(); } private void CompileButton_Click(object sender, RoutedEventArgs e) { CheckButton(RunButton, false, run); if (_onCompile != null) { _onCompile.Invoke(this, EventArgs.Empty); } else { var viewModel = (ScriptDocument)Roslyn.DataContext; viewModel.Text = Roslyn.Text; Errors.Items.Clear(); Errors.Items.Add("Compiling Script... "); Task.Run(() => { bCompiled = viewModel.Compile(); Dispatcher.Invoke(() => { Errors.Items.Clear(); if (bCompiled) { Errors.Items.Add("Script Compiled Successfully!"); } else { var errors = viewModel.Result.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); errors.ForEach(x => Errors.Items.Add(x)); } CheckButton(RunButton, bCompiled, run); }); }); } } private void RunButton_Click(object sender, RoutedEventArgs e) { var viewModel = (ScriptDocument)Roslyn.DataContext; try { var result = viewModel.Execute(); } catch (Exception err) { MessageBox.Show(CoreUtils.FormatException(err)); } } private void GotoLine(string line) { if (line.StartsWith("(")) { var sLine = line.Substring(1).Split(',')[0]; var iLine = int.Parse(sLine); Roslyn.ScrollToLine(iLine); } } private void Errors_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.AddedItems.Count == 1) GotoLine(e.AddedItems[0].ToString()); } private void Errors_PreviewMouseDown(object sender, MouseButtonEventArgs e) { if (Errors.SelectedItems.Count == 1) GotoLine(Errors.SelectedItems[0].ToString()); } private void Window_Closing(object sender, CancelEventArgs e) { if (bChanged) { var bRes = MessageBox.Show("Script has changed. Do you wish to save this script before closing?", "Save Changes", MessageBoxButton.YesNoCancel); if (bRes == MessageBoxResult.Cancel) { e.Cancel = true; return; } if (bRes == MessageBoxResult.Yes) _onSave?.Invoke(this, EventArgs.Empty); try { DialogResult = bRes == MessageBoxResult.Yes; } catch (InvalidOperationException) { // Do nothing, just avoids a crash when closing a script editor which was opened by Show(); } } } private void SaveButton_Click(object sender, RoutedEventArgs e) { Save(); } private void CopyButton_Click(object sender, RoutedEventArgs e) { Roslyn.Copy(); } private void CutButton_Click(object sender, RoutedEventArgs e) { Roslyn.Cut(); } private void PasteButton_Click(object sender, RoutedEventArgs e) { Roslyn.Paste(); } private void UndoButton_Click(object sender, RoutedEventArgs e) { Roslyn.Undo(); } private void RedoButton_Click(object sender, RoutedEventArgs e) { Roslyn.Redo(); } private void PrintButton_Click(object sender, RoutedEventArgs e) { //Syncfusion.Windows.Edit.EditCommands.PrintPreview.Execute(null, Edit1); } private void Snippets_MouseDoubleClick(object sender, MouseButtonEventArgs e) { Roslyn.SelectedText = SnippetList.SelectedValue.ToString(); } private void SnippetSection_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.AddedItems.Count == 1) SnippetList.ItemsSource = _snippets[e.AddedItems[0].ToString()]; else SnippetList.ItemsSource = null; } private void SaveCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { if (_onSave != null) { bChanged = false; _onSave?.Invoke(this, EventArgs.Empty); } } private void SaveCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = _onSave != null; } #endregion } }