using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Xml;
using Tango.Core.Commands;
using Tango.Scripting;
using Tango.SharedUI;
using Tango.SharedUI.Helpers;
namespace Tango.SharedUI.Controls
{
///
/// Represents a C# script editor control.
///
///
///
public partial class ScriptEditorControl : UserControl
{
#region Completion
///
/// Represents an auto complete item.
///
///
internal class CompletionData : ICompletionData
{
private String _description;
///
/// Gets or sets the icon source.
///
public BitmapSource Source { get; set; }
///
/// Initializes a new instance of the class.
///
/// The text.
/// The description.
public CompletionData(string text, String description)
{
this.Text = text;
_description = description;
}
///
/// Gets the image.
///
public System.Windows.Media.ImageSource Image
{
get { return Source; }
}
///
/// Gets the text. This property is used to filter the list of visible elements.
///
public string Text { get; private set; }
// Use this property if you want to show a fancy UIElement in the drop down list.
public object Content
{
get { return this.Text; }
}
///
/// Gets the description.
///
public object Description
{
get { return _description; }
}
///
/// Gets the priority. This property is used in the selection logic. You can use it to prefer selecting those items
/// which the user is accessing most frequently.
///
public double Priority { get { return 0; } }
///
/// Perform the completion.
///
/// The text area on which completion is performed.
/// The text segment that was used by the completion window if
/// the user types (segment between CompletionWindow.StartOffset and CompletionWindow.EndOffset).
/// The EventArgs used for the insertion request.
/// These can be TextCompositionEventArgs, KeyEventArgs, MouseEventArgs, depending on how
/// the insertion was triggered.
public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs)
{
textArea.Document.Replace(completionSegment, this.Text);
}
///
/// Returns a that represents this instance.
///
///
/// A that represents this instance.
///
public override string ToString()
{
return Text;
}
}
#endregion
private CompletionWindow completionWindow; //Holds the auto-complete window instance.
#region Constructors
///
/// Initializes a new instance of the class.
///
public ScriptEditorControl()
{
InitializeComponent();
textEditor.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.CSharp.CSharpIndentationStrategy();
textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering;
textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
this.Loaded += ScriptEditorControl_Loaded;
}
#endregion
#region Event Handlers
///
/// Handles the TextEntered event of the textEditor_TextArea control.
///
/// The source of the event.
/// The instance containing the event data.
private void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
if (e.Text == ".")
{
String keyword = textEditor.TextArea.GetJustCurrentWord();
if (keyword != null)
{
completionWindow = new CompletionWindow(textEditor.TextArea);
completionWindow.WindowStyle = WindowStyle.None;
completionWindow.AllowsTransparency = true;
completionWindow.ResizeMode = ResizeMode.NoResize;
IList data = completionWindow.CompletionList.CompletionData;
bool ok = false;
List> types = new List>();
types.AddRange(IntellisenseTypes);
if (IntellisenseTypes != null)
{
ScriptParser parser = new ScriptParser();
try
{
var variables = parser.ParseScript(textEditor.Text);
foreach (var v in variables)
{
var hT = IntellisenseTypes.SingleOrDefault(x => x.Key == v.Type);
if (hT.Value != null)
{
types.Add(new KeyValuePair(v.Name, hT.Value));
}
}
}
catch { }
}
KeyValuePair type = types.LastOrDefault(x => keyword == x.Key);
if (type.Key != null)
{
ok = true;
FillType(type.Value, data);
}
if (ok)
{
completionWindow.Show();
completionWindow.Closed += delegate
{
completionWindow = null;
};
}
}
}
}
///
/// Handles the TextEntering event of the textEditor_TextArea control.
///
/// The source of the event.
/// The instance containing the event data.
private void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (e.Text.Length > 0 && completionWindow != null)
{
if (!char.IsLetterOrDigit(e.Text[0]))
{
// Whenever a non-letter is typed while the completion window is open,
// insert the currently selected element.
completionWindow.CompletionList.RequestInsertion(e);
}
}
}
///
/// Handles the TextChanged event of the textEditor control.
///
/// The source of the event.
/// The instance containing the event data.
private void textEditor_TextChanged(object sender, EventArgs e)
{
Text = textEditor.Text;
}
private void ScriptEditorControl_Loaded(object sender, RoutedEventArgs e)
{
if (HighlightTypes != null)
{
Stream xshd_stream = typeof(ScriptEditorControl).Assembly.GetManifestResourceStream("Tango.SharedUI.CSharp-Mode.xshd");
String text = String.Empty;
using (StreamReader reader = new StreamReader(xshd_stream))
{
text = reader.ReadToEnd();
}
String code = String.Empty;
foreach (var name in HighlightTypes.Select(x => x.Key))
{
code += String.Format("{0}", name) + Environment.NewLine;
}
text = text.Replace("@CUSTOM_TYPES@", code);
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)))
{
XmlTextReader xshd_reader = new XmlTextReader(ms);
textEditor.SyntaxHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.HighlightingLoader.Load(xshd_reader, ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance);
xshd_reader.Close();
}
}
}
#endregion
#region Private Methods
///
/// Fills the type.
///
/// The type.
/// The data.
private void FillType(Type type, IList data)
{
List items = new List();
foreach (var method in type.GetMethods().Where(x => x.IsPublic && !x.IsSpecialName))
{
String desc = method.ReturnType.Name + " " + method.Name + "(" + String.Join(", ", method.GetParameters().Select(x => x.ParameterType.Name + " " + x.Name).ToArray()) + ")";
items.Add(new CompletionData(method.Name, desc) { Source = ResourceHelper.GetImageFromResources("Images/pubmethod.gif") });
}
foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
String desc = property.PropertyType.Name + " " + property.Name;
items.Add(new CompletionData(property.Name, desc) { Source = ResourceHelper.GetImageFromResources("Images/pubproperty.gif") });
}
foreach (var ev in type.GetEvents(BindingFlags.Instance | BindingFlags.Public))
{
try
{
String desc = ev.Name + " " + "(" + String.Join(", ", ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType.Name + " " + x.Name).ToArray()) + ")";
items.Add(new CompletionData(ev.Name, desc) { Source = ResourceHelper.GetImageFromResources("Images/pubevent.gif") });
}
catch { }
}
foreach (var item in items.OrderBy(x => x.Text))
{
data.Add(item);
}
}
///
/// Fills the assembly.
///
/// The asm.
/// The data.
private void FillAssembly(Assembly asm, IList data)
{
var q = from t in asm.GetTypes()
where t.IsClass
select t;
foreach (var type in q)
{
data.Add(new CompletionData(type.Name, "Class") { Source = ResourceHelper.GetImageFromResources("Images/pubclass.gif") });
}
}
#endregion
#region Properties
///
/// Gets or sets the text.
///
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(ScriptEditorControl), new PropertyMetadata(null, (d, e) => (d as ScriptEditorControl).OnTextChanged()));
///
/// Gets or sets the highlight types.
///
public ObservableCollection> HighlightTypes
{
get { return (ObservableCollection>)GetValue(HighlightTypesProperty); }
set { SetValue(HighlightTypesProperty, value); }
}
public static readonly DependencyProperty HighlightTypesProperty =
DependencyProperty.Register("HighlightTypes", typeof(ObservableCollection>), typeof(ScriptEditorControl), new PropertyMetadata(null));
///
/// Gets or sets the intellisense types.
///
public ObservableCollection> IntellisenseTypes
{
get { return (ObservableCollection>)GetValue(IntellisenseTypesProperty); }
set { SetValue(IntellisenseTypesProperty, value); }
}
public static readonly DependencyProperty IntellisenseTypesProperty =
DependencyProperty.Register("IntellisenseTypes", typeof(ObservableCollection>), typeof(ScriptEditorControl), new PropertyMetadata(null));
#endregion
#region Virtual Methods
///
/// Called when the text has changed.
///
protected virtual void OnTextChanged()
{
if (textEditor.Text != Text)
{
textEditor.Text = Text;
}
}
///
/// Called when the insert script command has changed.
///
protected virtual void OnInsertScriptCommandChanged()
{
if (InsertSnippetCommand != null)
{
InsertSnippetCommand.Executed += (x, snippet) =>
{
textEditor.Document.Insert(textEditor.TextArea.Caret.Offset, snippet);
};
}
}
#endregion
#region Commands
///
/// Gets or sets the run command.
///
public RelayCommand RunCommand
{
get { return (RelayCommand)GetValue(RunCommandProperty); }
set { SetValue(RunCommandProperty, value); }
}
public static readonly DependencyProperty RunCommandProperty =
DependencyProperty.Register("RunCommand", typeof(RelayCommand), typeof(ScriptEditorControl), new PropertyMetadata(null));
///
/// Gets or sets the stop command.
///
public RelayCommand StopCommand
{
get { return (RelayCommand)GetValue(StopCommandProperty); }
set { SetValue(StopCommandProperty, value); }
}
public static readonly DependencyProperty StopCommandProperty =
DependencyProperty.Register("StopCommand", typeof(RelayCommand), typeof(ScriptEditorControl), new PropertyMetadata(null));
///
/// Gets or sets the save command.
///
public RelayCommand SaveCommand
{
get { return (RelayCommand)GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
public static readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register("SaveCommand", typeof(RelayCommand), typeof(ScriptEditorControl), new PropertyMetadata(null));
///
/// Gets or sets the insert snippet command.
///
public RelayCommand InsertSnippetCommand
{
get { return (RelayCommand)GetValue(InsertSnippetCommandProperty); }
set { SetValue(InsertSnippetCommandProperty, value); }
}
public static readonly DependencyProperty InsertSnippetCommandProperty =
DependencyProperty.Register("InsertSnippetCommand", typeof(RelayCommand), typeof(ScriptEditorControl), new PropertyMetadata(null, (d, e) => (d as ScriptEditorControl).OnInsertScriptCommandChanged()));
#endregion
}
internal static class DocumentUtils
{
private static Regex _wordRegex = new Regex(@"[^\W\d][\w]*(?<=\w)", RegexOptions.Compiled);
public static string GetJustCurrentWord(this TextArea textArea)
{
try
{
DocumentLine line = textArea.Document.GetLineByNumber(textArea.Caret.Line);
if (line.Length == 0)
return null;
int lineCaretPosition = textArea.Caret.Offset - line.Offset;
String l = textArea.Document.GetText(line);
String trimmed = l.Remove(lineCaretPosition, l.Length - lineCaretPosition);
return SplitToWords(trimmed).LastOrDefault(x => !String.IsNullOrWhiteSpace(x));
}
catch
{
return null;
}
}
public static List SplitToWords(String text)
{
text = text.Replace(".", " ");
text = text.Replace("(", " ");
text = text.Replace(")", " ");
text = text.Replace(",", " ");
var punctuation = text.Where(Char.IsPunctuation).Distinct().ToArray();
var words = text.Split().Select(x => x.Trim(punctuation));
return words.ToList();
}
}
}