using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; 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.Controls.Primitives; 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; using Tango.Core.Commands; using Tango.Scripting.Core; using Tango.Scripting.Editors.CodeCompletion; using Tango.Scripting.Editors.Document; using Tango.Scripting.Editors.Editing; using Tango.Scripting.Editors.Folding; using Tango.Scripting.Editors.Highlighting; using Tango.Scripting.Editors.Highlighting.Xshd; using Tango.Scripting.Editors.Intellisense; using Tango.Scripting.Editors.Popups; using Tango.Scripting.Editors.Rendering; using Tango.Scripting.Parsing; namespace Tango.Scripting.Editors { public class ScriptEditor : TextEditor { private char[] word_separators = { ' ', '\t', '\n', '.', '(', ',', '-', '*', '/', '+', '$', '=', '<', '>' }; private string[] _blocking_type_words = { "class", "void" }; private DispatcherTimer _update_timer; private Popup _popup; private FoldingManager foldingManager; private BraceFoldingStrategy foldingStrategy; private CompletionWindow completionWindow; private ScriptParser _parser; private List _current_usings; private List _knownTypes; private List _declaredTypes; private bool _isLoadingTypes; private static JsonSerializerSettings _jsonSettings; private static Dictionary _knownTypesCache; private static String KNOWN_TYPES_CACHE_FOLDER; private static List _cachedAssemblies; private static List _cachedUsings; private static bool _isLoadingCachedAssemblies; private static bool _isCacheAssembliesLoaded; private static object _loadUsingsLock = new object(); public static event EventHandler> LoadingSymbolsProgress; public static event EventHandler LoadingSymbolsStarted; public static event EventHandler LoadingSymbolsCompleted; #region Mini Classes private class ScriptClass { public String Name { get; set; } public int Index { get; set; } } private class ConstructionSession { public KnownType Type { get; set; } public int ParameterIndex { get; set; } public List TypeArguments { get; set; } } private class MethodSession { public KnownType Type { get; set; } public String MethodName { get; set; } public int ParameterIndex { get; set; } } private class DeclaredMethodSession { public ScriptType Type { get; set; } public ScriptSymbol Method { get; set; } } #endregion #region Properties public static List BlockedUsingsCache { get; set; } /// /// Gets or sets a value indicating whether to enable folding. /// public bool EnableFolding { get { return (bool)GetValue(EnableFoldingProperty); } set { SetValue(EnableFoldingProperty, value); } } public static readonly DependencyProperty EnableFoldingProperty = DependencyProperty.Register("EnableFolding", typeof(bool), typeof(ScriptEditor), new PropertyMetadata(true, (d, e) => (d as ScriptEditor).OnEnableFoldingChanged())); /// /// /// public RelayCommand IndentCommand { get { return (RelayCommand)GetValue(IndentCommandProperty); } set { SetValue(IndentCommandProperty, value); } } public static readonly DependencyProperty IndentCommandProperty = DependencyProperty.Register("IndentCommand", typeof(RelayCommand), typeof(ScriptEditor), new PropertyMetadata(null)); /// /// Gets or sets the reference assemblies. /// public ObservableCollection ReferenceAssemblies { get { return (ObservableCollection)GetValue(ReferenceAssembliesProperty); } set { SetValue(ReferenceAssembliesProperty, value); } } public static readonly DependencyProperty ReferenceAssembliesProperty = DependencyProperty.Register("ReferenceAssemblies", typeof(ObservableCollection), typeof(ScriptEditor), new PropertyMetadata(null, (d, e) => (d as ScriptEditor).OnReferenceAssembliesChanged())); public Object CurrentPopupContent { get { return (Object)GetValue(CurrentPopupContentProperty); } set { SetValue(CurrentPopupContentProperty, value); } } public static readonly DependencyProperty CurrentPopupContentProperty = DependencyProperty.Register("CurrentPopupContent", typeof(Object), typeof(ScriptEditor), new PropertyMetadata(null)); public String Code { get { return (String)GetValue(CodeProperty); } set { SetValue(CodeProperty, value); } } public static readonly DependencyProperty CodeProperty = DependencyProperty.Register("Code", typeof(String), typeof(ScriptEditor), new PropertyMetadata(null, (d, e) => (d as ScriptEditor).OnCodeChanged())); public ObservableCollection AdditionalScripts { get { return (ObservableCollection)GetValue(AdditionalScriptsProperty); } set { SetValue(AdditionalScriptsProperty, value); } } public static readonly DependencyProperty AdditionalScriptsProperty = DependencyProperty.Register("AdditionalScripts", typeof(ObservableCollection), typeof(ScriptEditor), new PropertyMetadata(null)); #endregion #region Constructors /// /// Initializes the class. /// static ScriptEditor() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ScriptEditor), new FrameworkPropertyMetadata(typeof(ScriptEditor))); BlockedUsingsCache = new List(); if (KNOWN_TYPES_CACHE_FOLDER == null) { KNOWN_TYPES_CACHE_FOLDER = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Scripting", "Cache"); Directory.CreateDirectory(KNOWN_TYPES_CACHE_FOLDER); } _jsonSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto, PreserveReferencesHandling = PreserveReferencesHandling.All }; _knownTypesCache = new Dictionary(); _cachedAssemblies = new List(); _cachedUsings = new List(); } /// /// Initializes a new instance of the class. /// public ScriptEditor() { _declaredTypes = new List(); _current_usings = new List(); //ReferenceAssemblies = new ObservableCollection(); ////Add basic assemblies... //ReferenceAssemblies.Add(new ReferenceAssembly(typeof(String))); //mscorelib //ReferenceAssemblies.Add(new ReferenceAssembly(typeof(Enumerable))); //System.Core _knownTypes = new List(); _parser = new ScriptParser(); TextArea.IndentationStrategy = new Indentation.CSharp.CSharpIndentationStrategy(Options); foldingStrategy = new BraceFoldingStrategy(); _update_timer = new DispatcherTimer(); _update_timer.Interval = TimeSpan.FromSeconds(2); _update_timer.Tick += UpdateTimer_Tick; _update_timer.Start(); IndentCommand = new RelayCommand(IndentCode); TextArea.TextEntered += TextArea_TextEntered; completionWindow = new CompletionWindow(TextArea); completionWindow.WindowStyle = WindowStyle.None; completionWindow.AllowsTransparency = true; completionWindow.ResizeMode = ResizeMode.NoResize; completionWindow.InsertionRequest += CompletionWindow_InsertionRequest; TextChanged += ScriptEditor_TextChanged; } private bool preventCodeUpdate; private void ScriptEditor_TextChanged(object sender, EventArgs e) { if (!preventCodeUpdate) { preventCodeUpdate = true; Code = Text; preventCodeUpdate = false; } } private void OnCodeChanged() { if (!preventCodeUpdate) { preventCodeUpdate = true; Text = Code; preventCodeUpdate = false; } } #endregion #region Update Time Tick /// /// Handles the Tick event of the UpdateTimer control. /// /// The source of the event. /// The instance containing the event data. private void UpdateTimer_Tick(object sender, EventArgs e) { InvalidateFolding(); InvalidateUsings(); InvalidateScriptTypesHighlightings(); } #endregion #region Properties Changes private void OnEnableFoldingChanged() { if (EnableFolding) { foldingManager = FoldingManager.Install(TextArea); } else { FoldingManager.Uninstall(foldingManager); } } #endregion #region Override Methods /// /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// /// The that contains the event data. protected override void OnPreviewKeyDown(KeyEventArgs e) { if (CurrentPopupContent is MethodPopup && (e.Key == Key.Down || e.Key == Key.Up)) { e.Handled = true; if (e.Key == Key.Down) { (CurrentPopupContent as MethodPopup).IncrementMethod(); } else if (e.Key == Key.Up) { (CurrentPopupContent as MethodPopup).DecrementMethod(); } return; } base.OnPreviewKeyDown(e); HidePopup(); HandleKeyCombinations(e); } #endregion #region Apply Template public override void OnApplyTemplate() { base.OnApplyTemplate(); _popup = GetTemplateChild("PART_popup") as Popup; } #endregion #region Key Combination Handling /// /// Handles the key combinations. /// /// The instance containing the event data. private void HandleKeyCombinations(KeyEventArgs e) { if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.K) && Keyboard.IsKeyDown(Key.D)) { try { int index = CaretOffset; Document.BeginUpdate(); IndentCode(); Document.EndUpdate(); e.Handled = true; CaretOffset = index; } catch { Debug.WriteLine("Error indenting code."); } } else if (e.Key == Key.Oem2) { //int offset = CaretOffset; //var line = Document.GetLineByOffset(offset); //String text = GetCurrentLineText(); //if (text.TrimStart('\t', ' ').StartsWith("//")) //{ // Document.BeginUpdate(); // Document.Replace(line, "/// \n/// \n/// "); // Document.EndUpdate(); // e.Handled = true; // CaretOffset = Document.GetLineByNumber(line.LineNumber + 1).EndOffset; //} } else if (e.Key == Key.End || e.Key == Key.Home) { HideCompletionWindow(); } } #endregion #region Text Entered private void TextArea_TextEntered(object sender, TextCompositionEventArgs e) { try { List items = new List(); HidePopup(); var lineText = GetCurrentLineText(); var previousWords = GetPreviousWords(); var previousWordsLast = previousWords.LastOrDefault(); String currentWord = previousWordsLast != null ? previousWordsLast.Replace("\t", "") : String.Empty; String currentWordIncludingParenthesis = currentWord.Split('(').LastOrDefault(); if (previousWords.Count > 0 && previousWords.First().Trim().StartsWith("//")) return; if (e.Text == " " && previousWords.Count > 2 && previousWords[previousWords.Count - 2] == "=") { var expression = previousWords.First(); var knownType = GetKnownTypeFromExpression(expression + "."); if (knownType != null && knownType.Type.IsEnum) { completionWindow.HideCompletion(); IList data = new List(); foreach (var field in knownType.Fields) { data.Add(new FieldCompletionItem() { Class = knownType.FriendlyName, Name = knownType.FriendlyName + "." + field.Name, Type = field.ReturnTypeFriendlyName, Description = field.Summary, }); } ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord()); } } else if (e.Text == " " && GetPreviousWord() == "new") { var s = _parser.GetExpressionFirst(GetCurrentLineText()); if (s != null) { String type = s.Declaration.Type.ToString(); IList data = new List(); data.Add(new ClassCompletionItem() { Name = type, Description = "Auto generate assignment...", }); ShowCompletionWindow(data, type); } } else if (e.Text == ";" || e.Text == " ") { HideCompletionWindow(); } else if (e.Text == ".") { var knownType = GetCurrentKnownType(); IList data = new List(); if (knownType != null) { completionWindow.HideCompletion(); if (!knownType.Type.IsEnum) { var typeMembers = knownType.Members.ToList(); foreach (var methodGroup in typeMembers.OfType().Where(x => !x.IsStatic).GroupBy(x => x.NameWithTypeArguments)) { var method = methodGroup.First(); data.Add(new MethodCompletionItem() { Class = knownType.FriendlyName, Name = method.NameWithTypeArguments, ReturnType = method.ReturnTypeFriendlyName, Description = method.Summary, Parameters = method.Parameters, Overloads = methodGroup.Count() - 1, }); } foreach (var ev in typeMembers.OfType()) { data.Add(new EventCompletionItem() { Class = knownType.FriendlyName, Name = ev.Name, Description = ev.Summary, }); } foreach (var methodGroup in typeMembers.Where(x => x.GetType() != typeof(KnownTypeMethod) && x.GetType() != typeof(KnownTypeEvent)).GroupBy(x => x.Name)) { var member = methodGroup.First(); data.Add(new PropertyCompletionItem() { Class = knownType.FriendlyName, Name = member.Name, Type = member.ReturnTypeFriendlyName, Description = member.Summary, }); } } else { foreach (var field in knownType.Fields) { data.Add(new FieldCompletionItem() { Class = knownType.FriendlyName, Name = field.Name, Type = field.ReturnTypeFriendlyName, Description = field.Summary, }); } } ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord()); } else { var declaredType = GetCurrentDeclaredType(); if (declaredType != null) { completionWindow.HideCompletion(); var typeMembers = declaredType.Symbols.ToList(); foreach (var methodGroup in typeMembers.GroupBy(x => x.Name)) { var member = methodGroup.First(); if (member.Kind == SymbolKind.Method) { var methodCompletion = new MethodCompletionItem() { Class = declaredType.Name, Name = member.Name, ReturnType = member.Type, Description = member.Summary, Overloads = methodGroup.Count() - 1, }; for (int i = 0; i < member.Parameters.Count; i++) { var pair = member.Parameters[i]; methodCompletion.Parameters.Add(new KnownTypeMethodParameter() { Type = pair.Key, Name = pair.Value, IsLast = (i == member.Parameters.Count - 1) }); } data.Add(methodCompletion); } else if (member.Kind == SymbolKind.Property) { data.Add(new PropertyCompletionItem() { Class = declaredType.Name, Name = member.Name, Type = member.Type, Description = member.Summary, }); } else if (member.Kind == SymbolKind.Field) { data.Add(new FieldCompletionItem() { Class = declaredType.Name, Name = member.Name, Type = member.Type, Description = member.Summary, }); } } ShowCompletionWindow(data, GetCurrentWord()); } else { //Maybe static ... var typeText = GetPreviousWord(); knownType = _knownTypes.FirstOrDefault(x => x.FriendlyName == typeText || x.Alias == typeText); if (knownType != null) { var typeMembers = knownType.Members.ToList(); foreach (var methodGroup in typeMembers.OfType().Where(x => x.IsStatic).GroupBy(x => x.NameWithTypeArguments)) { var method = methodGroup.First(); data.Add(new MethodCompletionItem() { Class = knownType.FriendlyName, Name = method.NameWithTypeArguments, ReturnType = method.ReturnTypeFriendlyName, Description = method.Summary, Parameters = method.Parameters, Overloads = methodGroup.Count() - 1, }); } ShowCompletionWindow(data.OrderBy(x => x.Text).ToList(), GetCurrentWord()); } } } } else if (e.Text == "(" || e.Text == ",") { completionWindow.HideCompletion(); try { var session = GetConstructionSession(); if (session != null) { var content = CreateConstructionSessionPopupContent(session); if (content.Methods.Count > 0) { ShowPopup(content); return; } } var methodSession = GetMethodSession(); if (methodSession != null) { var content = CreateMethodSessionPopupContent(methodSession); if (content.Methods.Count > 0) { ShowPopup(content); return; } } var declaredMethodSession = GetDeclaredMethodSession(); if (declaredMethodSession != null) { var content = CreateDeclaredMethodSessionPopupContent(declaredMethodSession); if (content.Methods.Count > 0) { ShowPopup(content); return; } } var staticMethodSession = GetStaticMethodSession(); if (staticMethodSession != null) { var content = CreateMethodSessionPopupContent(staticMethodSession); if (content.Methods.Count > 0) { ShowPopup(content); return; } } } catch (Exception ex) { Debug.WriteLine(ex); } } else if (lineText.StartsWith("using") && e.Text != "\n") { if (completionWindow.IsVisible) { completionWindow.UpdatePositionFix(); return; } IList data = new List(); foreach (var asm in ReferenceAssemblies) { foreach (var ns in asm.GetTypes().Select(x => x.Namespace).Distinct().Where(x => x != null)) { data.Add(new NamespaceCompletionItem() { Name = ns, Assembly = asm.GetName().Name, }); } } data = data.DistinctBy(x => x.Text).ToList(); ShowCompletionWindow(data, GetCurrentWord()); } else if (e.Text == "{") { int parentesisCount = lineText.TakeWhile(x => x != '{').Count(x => x == '\"'); if (parentesisCount % 2 == 0) { Document.Insert(CaretOffset, "}"); CaretOffset--; } } else if (e.Text == "}") { if (Document.GetText(CaretOffset - 2, 1) == "{" && Document.GetText(CaretOffset, 1) == "}") { Document.Replace(CaretOffset, 1, ""); } } else if (e.Text == "\n") { if (Document.GetText(CaretOffset - 3, 1) == "{" && Document.GetText(CaretOffset, 1) == "}") { CaretOffset--; Document.Insert(CaretOffset, "\n\t"); } } else if (!currentWordIncludingParenthesis.Contains(".") || currentWord[currentWord.Length - 2] == '<') { if (completionWindow.IsVisible) { completionWindow.UpdatePositionFix(); return; } var previous_word = GetPreviousWord(); var word = GetCurrentWord(); if (word.Contains("<")) { word = word.Last(x => x != '<').ToString(); } if (previous_word != word) { if (_knownTypes.Exists(x => x.Name == previous_word)) { return; } if (_blocking_type_words.Contains(previous_word)) { return; } } if (!String.IsNullOrWhiteSpace(word)) { IList data = new List(); foreach (var type in _declaredTypes.Where(x => x.Name.StartsWith(word))) { if (type.Kind == TypeKind.Struct) { data.Add(new StructCompletionItem() { Name = type.Name, Description = type.Summary, Namespace = type.ContainingNamespace, Priority = 1, }); } else if (type.Kind == TypeKind.Enum) { data.Add(new EnumCompletionItem() { Name = type.Name, Description = type.Summary, Namespace = type.ContainingNamespace, Priority = 1, }); } else if (type.Kind == TypeKind.Interface) { data.Add(new InterfaceCompletionItem() { Name = type.Name, Description = type.Summary, Namespace = type.ContainingNamespace, Priority = 1, }); } else if (type.Kind == TypeKind.Class) { data.Add(new ClassCompletionItem() { Name = type.Name, Description = type.Summary, Namespace = type.ContainingNamespace, Priority = 1, }); } else { throw new NotImplementedException("Implement generic item here!"); } } foreach (var type in _knownTypes.ToList().Where(x => x.Name.StartsWith(word))) { if (type.Type.IsEnum) { data.Add(new EnumCompletionItem() { Namespace = type.Type.Namespace, Description = type.Summary, Name = type.FriendlyName, Priority = 0, }); } else if (type.Type.IsInterface) { data.Add(new InterfaceCompletionItem() { Name = type.FriendlyName, Description = type.Summary, Namespace = type.Type.Namespace, Priority = 0, }); } else if (type.Type.IsValueType) { data.Add(new StructCompletionItem() { Name = type.FriendlyName, Description = type.Summary, Namespace = type.Type.Namespace, Priority = 0, }); } else if (type.Type.IsClass) { data.Add(new ClassCompletionItem() { Name = type.FriendlyName, Description = type.Summary, Namespace = type.Type.Namespace, Priority = 0, }); } else { throw new NotImplementedException("Implement generic item here."); } } foreach (var symbol in _parser.GetContextSymbols(Document.Text, CaretOffset).Where(x => x.Name.StartsWith(GetCurrentWord()))) { if (symbol.Kind == SymbolKind.Property) { data.Add(new PropertyCompletionItem() { Class = symbol.Class, Description = symbol.Summary, Name = symbol.Name, Type = symbol.Type, Priority = 2, }); } else if (symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Parameter) { data.Add(new FieldCompletionItem() { Class = symbol.Class, Description = symbol.Summary, Name = symbol.Name, Type = symbol.Type, Priority = 2, }); } else if (symbol.Kind == SymbolKind.Method) { var methodCompletion = new MethodCompletionItem() { Class = symbol.Class, Description = symbol.Summary, Name = symbol.Name, ReturnType = symbol.Type, Priority = 2, }; for (int i = 0; i < symbol.Parameters.Count; i++) { var pair = symbol.Parameters[i]; methodCompletion.Parameters.Add(new KnownTypeMethodParameter() { Type = pair.Key, Name = pair.Value, IsLast = (i == symbol.Parameters.Count - 1) }); } data.Add(methodCompletion); } } ShowCompletionWindow(data, word); } } } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } } #endregion #region Completion Window Insertion private void CompletionWindow_InsertionRequest(ICompletionData item) { item.Complete(this); } #endregion #region Private/Internal Methods private void ShowCompletionWindow(IList suggestions, String filter) { IList data = completionWindow.CompletionList.CompletionData; data.Clear(); foreach (var item in suggestions.DistinctBy(x => x.Text)) { data.Add(item); } if (data.Count > 0) { completionWindow.ShowCompletion(); if (completionWindow.CompletionList.ListBox != null) { completionWindow.CompletionList.SelectItemFiltering(filter); } } else { completionWindow.HideCompletion(); } } private void HideCompletionWindow() { completionWindow.HideCompletion(); } private List GetScriptClassNames() { List list = new List(); Regex r = new Regex(@"(class\s+)(\w+)"); var matches = r.Matches(Text); foreach (var m in matches.OfType()) { var g = m.Groups.OfType().Last(); list.Add(g.Value); } return list.Distinct().ToList(); } private List GetScriptInterfaceOfEnumNames() { List list = new List(); Regex r = new Regex(@"((enum|interface)\s+)(\w+)"); var matches = r.Matches(Text); foreach (var m in matches.OfType()) { var g = m.Groups.OfType().Last(); list.Add(g.Value); } return list.Distinct().ToList(); } private List GetScriptEnumsAndInterfaces() { List list = new List(); Regex r = new Regex(@"((public|private|internal)\s+(enum|interface)\s+\w+)"); var matches = r.Matches(Text); foreach (var m in matches) { } return list; } private KnownType GetCurrentKnownType() { var expression = GetPreviousWords().LastOrDefault(); return GetKnownTypeFromExpression(expression); } private KnownType GetKnownTypeFromExpression(String expression) { if (expression != null) { var insideMethodExp = expression.Split('(').LastOrDefault(); if (insideMethodExp != null) { expression = insideMethodExp; } var tree = expression.Split('.').Select(x => x.Remove(@"\n|\t|\r|\(.*\)|\[.*\]|\s")).ToList(); var variableName = tree.FirstOrDefault(); if (variableName != null) { //Search for enum type first var enumType = _knownTypes.FirstOrDefault(x => x.Name == variableName && x.Type.IsEnum); if (enumType != null) { return enumType; } tree.RemoveAt(0); var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); var variable = variables.FirstOrDefault(x => x.Name == variableName); if (variable != null) { var name = Regex.Replace(variable.Type, "<.+>", ""); var knownType = _knownTypes.FirstOrDefault(x => name == x.FriendlyName || name == x.Alias); if (knownType != null) { while (tree.Count > 1) { var memberName = tree.First(); tree.RemoveAt(0); var member = knownType.Members.FirstOrDefault(x => x.Name == memberName); if (member == null) { return null; } knownType = _knownTypes.FirstOrDefault(x => x.Type.Namespace + "." + x.Type.Name == member.ReturnType.Namespace + "." + member.ReturnType.Name); } return knownType; } } } } return null; } private ScriptType GetCurrentDeclaredType() { var expression = GetPreviousWords().LastOrDefault(); if (expression != null) { var insideMethodExp = expression.Split('(').LastOrDefault(); if (insideMethodExp != null) { expression = insideMethodExp; } var tree = expression.Split('.').Select(x => x.Remove(@"\n|\t|\r|\(.*\)|\[.*\]|\s")).ToList(); var variableName = tree.FirstOrDefault(); if (variableName != null) { tree.RemoveAt(0); var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); var variable = variables.FirstOrDefault(x => x.Name == variableName); if (variable != null) { var declaredType = _declaredTypes.FirstOrDefault(x => x.Name == Regex.Replace(variable.Type, "<.+>", "")); if (declaredType != null) { while (tree.Count > 1) { var memberName = tree.First(); tree.RemoveAt(0); var member = declaredType.Symbols.FirstOrDefault(x => x.Name == memberName); if (member == null) { return null; } declaredType = _declaredTypes.FirstOrDefault(x => x.ContainingNamespace + "." + x.Name == member.ContainingNamespace + "." + member.Type); } return declaredType; } } } } return null; } private void ShowPopup(Object content) { var position = TextArea.Caret.Position; var textView = TextArea.TextView; var visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom); var visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop); Point location = textView.PointToScreen(visualLocation - textView.ScrollOffset); Point locationTop = textView.PointToScreen(visualLocationTop - textView.ScrollOffset); _popup.Placement = PlacementMode.Absolute; _popup.PlacementRectangle = new Rect(location, new Size(200, 100)); CurrentPopupContent = content; _popup.IsOpen = true; } private void HidePopup() { _popup.IsOpen = false; CurrentPopupContent = null; } private MethodPopup CreateConstructionSessionPopupContent(ConstructionSession session) { MethodPopup popup = new MethodPopup(); foreach (var c in session.Type.Constructors) { MethodDescription method = new MethodDescription(); method.ReturnType = session.Type.Name; method.Description = c.Summary; if (session.Type.Type.IsGenericType && session.TypeArguments != null) { method.ReturnType = new String(session.Type.Name.TakeWhile(x => x != '`').ToArray()) + $"<{String.Join(",", session.TypeArguments)}>"; } var parameters = c.Parameters; foreach (var p in parameters) { ParameterDescription pDescription = new ParameterDescription(method); pDescription.Name = p.Name; pDescription.Type = p.Type; pDescription.Description = p.Description; method.Parameters.Add(pDescription); } popup.Methods.Add(method); } if (session.ParameterIndex > 0) { popup.CurrentMethod = popup.Methods.FirstOrDefault(x => x.Parameters.Count == session.ParameterIndex + 1); if (popup.CurrentMethod == null) { popup.CurrentMethod = popup.Methods.FirstOrDefault(); } } else { popup.CurrentMethod = popup.Methods.FirstOrDefault(); } if (popup.CurrentMethod != null) { popup.CurrentMethodIndex = popup.Methods.IndexOf(popup.CurrentMethod) + 1; } return popup; } private MethodPopup CreateMethodSessionPopupContent(MethodSession session) { MethodPopup popup = new MethodPopup(); foreach (var m in session.Type.Methods.Where(x => x.Name == session.MethodName)) { MethodDescription method = new MethodDescription(); method.ReturnType = session.Type.Name; method.Description = m.Summary; method.Name = m.NameWithTypeArguments; method.Class = session.Type.FriendlyName; //if (session.Type.Type.IsGenericType && session.TypeArguments != null) //{ // method.ReturnType = new String(session.Type.Name.TakeWhile(x => x != '`').ToArray()) + $"<{String.Join(",", session.TypeArguments)}>"; //} var parameters = m.Parameters; foreach (var p in parameters) { ParameterDescription pDescription = new ParameterDescription(method); pDescription.Name = p.Name; pDescription.Type = p.Type; pDescription.Description = p.Description; method.Parameters.Add(pDescription); } popup.Methods.Add(method); } if (session.ParameterIndex > 0) { popup.CurrentMethod = popup.Methods.FirstOrDefault(x => x.Parameters.Count == session.ParameterIndex + 1); if (popup.CurrentMethod == null) { popup.CurrentMethod = popup.Methods.FirstOrDefault(); } } else { popup.CurrentMethod = popup.Methods.FirstOrDefault(); } if (popup.CurrentMethod != null) { popup.CurrentMethodIndex = popup.Methods.IndexOf(popup.CurrentMethod) + 1; } return popup; } private MethodPopup CreateDeclaredMethodSessionPopupContent(DeclaredMethodSession session) { MethodPopup popup = new MethodPopup(); MethodDescription method = new MethodDescription(); method.ReturnType = session.Method.Type; method.Description = session.Method.Summary; method.Name = session.Method.Name; method.Class = session.Method.Class; //if (session.Type.Type.IsGenericType && session.TypeArguments != null) //{ // method.ReturnType = new String(session.Type.Name.TakeWhile(x => x != '`').ToArray()) + $"<{String.Join(",", session.TypeArguments)}>"; //} foreach (var p in session.Method.Parameters) { ParameterDescription pDescription = new ParameterDescription(method); pDescription.Type = p.Key; pDescription.Name = p.Value; method.Parameters.Add(pDescription); } popup.Methods.Add(method); //if (false) //{ // popup.CurrentMethod = popup.Methods.FirstOrDefault(x => x.Parameters.Count == session.ParameterIndex + 1); // if (popup.CurrentMethod == null) // { // popup.CurrentMethod = popup.Methods.FirstOrDefault(); // } //} //else //{ popup.CurrentMethod = popup.Methods.FirstOrDefault(); //} if (popup.CurrentMethod != null) { popup.CurrentMethodIndex = popup.Methods.IndexOf(popup.CurrentMethod) + 1; } return popup; } public static void LoadUsingsSymbols(List assemblies, List usings) { lock (_loadUsingsLock) { LoadingSymbolsStarted?.Invoke(null, new EventArgs()); var allTypes = assemblies.SelectMany(x => x.GetTypes()); foreach (var use in usings) { if (!_cachedUsings.Exists(x => x.Namespace == use)) { var useFileName = System.IO.Path.Combine(KNOWN_TYPES_CACHE_FOLDER, use + ".json"); if (File.Exists(useFileName)) { LoadingSymbolsProgress?.Invoke(null, new TangoProgressChangedEventArgs() { Progress = new TangoProgress() { IsIndeterminate = true, Maximum = 100, Message = $"Loading symbols for '{use}'..." } }); CachedUsing cached = JsonConvert.DeserializeObject(File.ReadAllText(useFileName), _jsonSettings); _cachedUsings.Add(cached); foreach (var knownType in cached.KnownTypes) { _knownTypesCache.Add(knownType.Type, knownType); } continue; } var useTypes = allTypes.Where(x => x.IsVisible && x.IsPublic && x.Namespace == use).ToList(); CachedUsing cachedUsing = new CachedUsing(); cachedUsing.Namespace = use; _cachedUsings.Add(cachedUsing); int i = 1; foreach (var type in useTypes) { LoadingSymbolsProgress?.Invoke(null, new TangoProgressChangedEventArgs() { Progress = new TangoProgress() { IsIndeterminate = false, Maximum = useTypes.Count, Value = i++, Message = $"Loading symbols for '{use}'..." } }); KnownType knownType = new KnownType(type); if (type.IsPrimitive) { if (type == typeof(Int32)) { knownType.Alias = "int"; } else if (type == typeof(float)) { knownType.Alias = "float"; } else if (type == typeof(Double)) { knownType.Alias = "double"; } else if (type == typeof(long)) { knownType.Alias = "long"; } else if (type == typeof(bool)) { knownType.Alias = "bool"; } else if (type == typeof(uint)) { knownType.Alias = "uint"; } } _knownTypesCache.Add(type, knownType); cachedUsing.KnownTypes.Add(knownType); knownType.LoadDocumentation(); } if (!BlockedUsingsCache.Exists(x => x == use)) { Task.Factory.StartNew(() => { var json = JsonConvert.SerializeObject(cachedUsing, _jsonSettings); File.WriteAllText(useFileName, json); }); } } } LoadingSymbolsCompleted?.Invoke(null, new EventArgs()); } } //public static void LoadCachedAssemblies(List assemblies, List usings = null) //{ // if (_isLoadingCachedAssemblies) return; // _isLoadingCachedAssemblies = true; // LoadingSymbolsStarted?.Invoke(null, new EventArgs()); // if (!_isCacheAssembliesLoaded) // { // foreach (var file in System.IO.Directory.GetFiles(KNOWN_TYPES_CACHE_FOLDER)) // { // try // { // LoadingSymbolsProgress?.Invoke(null, new TangoProgressChangedEventArgs() // { // Progress = new TangoProgress() // { // IsIndeterminate = true, // Maximum = 100, // Message = $"Loading metadata cache for '{System.IO.Path.GetFileName(file)}'..." // } // }); // var cachedAssembly = JsonConvert.DeserializeObject(System.IO.File.ReadAllText(file), _jsonSettings); // foreach (var knownType in cachedAssembly.KnownTypes) // { // _knownTypesCache.Add(knownType.Type, knownType); // } // _cachedAssemblies.Add(cachedAssembly); // } // catch { } // } // _isCacheAssembliesLoaded = true; // } // foreach (var asm in assemblies) // { // if (!_cachedAssemblies.Exists(x => x.Name == asm.FullName)) // { // String asmFileName = System.IO.Path.GetFileName(asm.Location); // CachedAssembly cachedAssembly = new CachedAssembly(); // cachedAssembly.Name = asm.FullName; // _cachedAssemblies.Add(cachedAssembly); // var types = asm.GetTypes().Where(x => x.IsVisible && x.IsPublic).ToList(); // int i = 0; // foreach (var type in types) // { // LoadingSymbolsProgress?.Invoke(null, new TangoProgressChangedEventArgs() // { // Progress = new TangoProgress() // { // IsIndeterminate = false, // Maximum = types.Count, // Value = i++, // Message = $"Caching metadata for '{asmFileName}'..." // } // }); // KnownType knownType = new KnownType(type); // if (type.IsPrimitive) // { // if (type == typeof(Int32)) // { // knownType.Alias = "int"; // } // else if (type == typeof(float)) // { // knownType.Alias = "float"; // } // else if (type == typeof(Double)) // { // knownType.Alias = "double"; // } // else if (type == typeof(long)) // { // knownType.Alias = "long"; // } // else if (type == typeof(bool)) // { // knownType.Alias = "bool"; // } // else if (type == typeof(uint)) // { // knownType.Alias = "uint"; // } // } // _knownTypesCache.Add(type, knownType); // cachedAssembly.KnownTypes.Add(knownType); // //knownType.LoadDocumentation(); // } // String cachedAssemblyFile = System.IO.Path.Combine(KNOWN_TYPES_CACHE_FOLDER, asmFileName); // File.WriteAllText(cachedAssemblyFile, JsonConvert.SerializeObject(cachedAssembly, _jsonSettings)); // } // } // LoadingSymbolsCompleted?.Invoke(null, new EventArgs()); // _isLoadingCachedAssemblies = false; //} private void InvalidateHighlighting(bool loadKnownTypes = true) { if (!_isLoadingTypes) { _isLoadingTypes = true; var assemblies = ReferenceAssemblies.ToList(); var usings = _current_usings.ToList(); Thread t = new Thread(() => { LoadUsingsSymbols(assemblies, usings); if (loadKnownTypes) { _knownTypes.Clear(); foreach (var knownType in _knownTypesCache.ToList().Select(x => x.Value).ToList()) { if (usings.Exists(x => knownType.Type.Namespace == x) && assemblies.Exists(x => x == knownType.Type.Assembly)) { lock (_knownTypes) { _knownTypes.Add(knownType); } } } } if (_knownTypes.Count > 0 || _declaredTypes.Count > 0) { String text = String.Empty; Stream xshd_stream = typeof(ScriptEditor).Assembly.GetManifestResourceStream("Tango.Scripting.Editors.Highlighting.Resources.CSharp-Mode.xshd"); using (StreamReader reader = new StreamReader(xshd_stream)) { text = reader.ReadToEnd(); } List referenceTypes = new List(); List interfaceTypes = new List(); lock (_knownTypes) { foreach (var type in _knownTypes.ToList().Where(x => x != null)) { String name = type.Name; if (type.Type.ContainsGenericParameters) { name = new String(name.TakeWhile(x => x != '`').ToArray()); } if (type.Type.IsInterface || type.Type.IsEnum) { interfaceTypes.Add(String.Format("{0}", name)); } else if (type.Type.IsClass || (type.Type.IsValueType)) { referenceTypes.Add(String.Format("{0}", name)); } } } foreach (var type in _declaredTypes) { if (type.Kind == TypeKind.Interface || type.Kind == TypeKind.Enum) { interfaceTypes.Add(String.Format("{0}", type.Name)); } else if (type.Kind == TypeKind.Class) { referenceTypes.Add(String.Format("{0}", type.Name)); } } if (referenceTypes.Count > 0) { text = text.Replace("@ReferenceTypes@", String.Join(Environment.NewLine, referenceTypes.Distinct())); } if (interfaceTypes.Count > 0) { text = text.Replace("@InterfaceTypes@", String.Join(Environment.NewLine, interfaceTypes.Distinct())); } MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)); XmlTextReader xshd_reader = new XmlTextReader(ms); Dispatcher.BeginInvoke(new Action(() => { SyntaxHighlighting = HighlightingLoader.Load(xshd_reader, HighlightingManager.Instance); xshd_reader.Close(); ms.Dispose(); })); //foreach (var knownType in _knownTypes) //{ // knownType.LoadDocumentation(); //} } _isLoadingTypes = false; }); t.IsBackground = true; t.Start(); } } private void InvalidateScriptTypesHighlightings() { var declaredTypes = _parser.GetDeclaredTypes(Text); if (AdditionalScripts != null) { foreach (var script in AdditionalScripts) { declaredTypes.AddRange(_parser.GetDeclaredTypes(script.Code)); } } if (declaredTypes.Exists(x => !_declaredTypes.Exists(y => y.Name == x.Name)) || _declaredTypes.Exists(x => !declaredTypes.Exists(y => y.Name == x.Name))) { _declaredTypes = declaredTypes; InvalidateHighlighting(false); } _declaredTypes = declaredTypes; //for (int i = 0; i < TextArea.TextView.LineTransformers.Count; i++) //{ // if (TextArea.TextView.LineTransformers[i] is OffsetColorizer) // { // TextArea.TextView.LineTransformers.RemoveAt(i); // } //} //foreach (var cls in scriptClasses) //{ // Document.BeginUpdate(); // var line = Document.GetLineByOffset(cls.Index); // OffsetColorizer colorizer = new OffsetColorizer(line, cls.Index, cls.Index + cls.Name.Length, Brushes.Red); // TextArea.TextView.LineTransformers.Add(colorizer); // Document.EndUpdate(); //} } private void InvalidateUsings() { var oldUsings = _current_usings.ToList(); _current_usings = _parser.GetUsings(Text); if (_current_usings.Exists(x => !oldUsings.Exists(y => y == x)) || oldUsings.Exists(x => !_current_usings.Exists(y => y == x))) { InvalidateHighlighting(); } } private void InvalidateFolding() { if (EnableFolding) { if (foldingManager == null) { foldingManager = FoldingManager.Install(TextArea); } foldingStrategy.UpdateFoldings(foldingManager, Document); } } private void IndentCode() { Text = Indentation.CSharp.CSharpIndentationHelper.IndentCSharpCode(Text); //Text = _parser.IndentCSharpCode(Text); } internal DocumentLine GetCurrentLine() { int offset = CaretOffset; var line = Document.GetLineByOffset(offset); return line; } private String GetCurrentLineText() { var text = Document.GetText(GetCurrentLine()); return text; } internal String GetCurrentWord() { return GetWordByEndIndex(CaretOffset); } private String GetWordByEndIndex(int index) { String word = String.Empty; var line = GetCurrentLine(); int position = index; for (int i = position - 1; i >= line.Offset; i--) { char c = Document.GetText(i, 1).First(); if (word_separators.Contains(c)) { break; } word += c; } word = new string(word.Reverse().ToArray()); if (word.Length > 0) { word = word.Replace(".", ""); } return word; } internal int GetCurrentWordStartIndex() { var line = GetCurrentLine(); int position = CaretOffset; for (int i = position - 1; i >= line.Offset; i--) { char c = Document.GetText(i, 1).First(); if (word_separators.Contains(c)) { return i + 1; } } return line.Offset; } private String GetPreviousWord() { int index = GetCurrentWordStartIndex() - 1; return GetWordByEndIndex(index); } private ConstructionSession GetConstructionSession() { var currentLine = GetCurrentLineText(); //if (currentLine.Count(x => x == '(') > 1) return null; var expression = _parser.GetCurrentConstructionExpression(currentLine); if (expression != null) { ConstructionSession session = new ConstructionSession(); var line = GetCurrentLine(); int parameterIndex = 0; for (int i = CaretOffset; i > line.Offset; i--) { String c = Document.GetText(i, 1); if (c == "(") { var typeDeclaration = expression.Type as GenericNameSyntax; KnownType type = null; if (typeDeclaration != null) { var typeName = typeDeclaration.Identifier.ToString(); var argumentsCount = typeDeclaration.TypeArgumentList.Arguments.Count; session.TypeArguments = typeDeclaration.TypeArgumentList.Arguments.Select(x => x.ToString()).ToList(); if (argumentsCount == 0) { type = _knownTypes.FirstOrDefault(x => x.Type.Name == expression.Type.ToString()); } else { type = _knownTypes.FirstOrDefault(x => x.Type.Name == typeName + "`" + argumentsCount); } } else { type = _knownTypes.FirstOrDefault(x => x.Name == expression.Type.ToString()); } if (type != null) { session.Type = type; session.ParameterIndex = parameterIndex; return session; } else { return null; } } else if (c == ",") { parameterIndex++; } } } else { var expression2 = _parser.GetCurrentConstructionExpressionAlt(GetCurrentLineText()); if (expression2 != null && expression2.Identifier != null) { ConstructionSession session = new ConstructionSession(); var line = GetCurrentLine(); int parameterIndex = 0; for (int i = CaretOffset; i > line.Offset; i--) { String c = Document.GetText(i, 1); if (c == "(") { KnownType type = null; if (expression2.Identifier != null) { var typeName = expression2.Identifier.ToString(); type = _knownTypes.FirstOrDefault(x => x.Type.Name == typeName); if (type != null) { session.Type = type; session.ParameterIndex = parameterIndex; return session; } else { return null; } } } else if (c == ",") { parameterIndex++; } } } } return null; } private MethodSession GetMethodSession() { var currentLine = GetCurrentLineText(); if (currentLine.Count(x => x == '(') > 1) { currentLine = currentLine.Split('(')[currentLine.Split('(').Length - 2]; } var words = currentLine.Split(' '); if (words.Count() > 0 && (words.First() == "private" || words.First() == "public" || words.First() == "void")) { return null; } var expression = words.LastOrDefault(); if (expression != null) { int parameterIndex = expression.Count(x => x == ','); expression = new string(expression.TakeWhile(x => x != '(').ToArray()); var tree = expression.Split('.').Select(x => x.Remove(@"\n|\r|\s|\t|\(|\)|\[|\]|<.*>")).ToList(); var variableName = tree.FirstOrDefault(); if (variableName != null && tree.Count > 1) { tree.RemoveAt(0); var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); var variable = variables.FirstOrDefault(x => x.Name == variableName); if (variable != null) { var knownType = _knownTypes.FirstOrDefault(x => x.FriendlyName == Regex.Replace(variable.Type, "<.+>", "")); if (knownType != null) { while (tree.Count > 1) { var memberName = tree.First(); tree.RemoveAt(0); var member = knownType.Members.FirstOrDefault(x => x.Name == memberName); if (member == null) { return null; } knownType = _knownTypes.FirstOrDefault(x => x.Type.Namespace + "." + x.Type.Name == member.ReturnType.Namespace + "." + member.ReturnType.Name); } return new MethodSession() { Type = knownType, MethodName = tree.Last(), ParameterIndex = parameterIndex, }; } } } } return null; } private MethodSession GetStaticMethodSession() { var words = GetCurrentLineText().Split(' '); if (words.Count() > 0 && (words.First() == "private" || words.First() == "public" || words.First() == "void")) { return null; } var expression = words.LastOrDefault(); if (expression != null) { int parameterIndex = expression.Count(x => x == ','); expression = new string(expression.TakeWhile(x => x != '(').ToArray()); var tree = expression.Split('.').Select(x => x.Remove(@"\n|\r|\s|\t|\(|\)|\[|\]|<.*>")).ToList(); var variableName = tree.FirstOrDefault(); if (variableName != null && tree.Count > 1) { tree.RemoveAt(0); var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); var variable = variableName; if (variable != null) { var knownType = _knownTypes.FirstOrDefault(x => x.FriendlyName == Regex.Replace(variable, "<.+>", "")); if (knownType != null) { while (tree.Count > 1) { var memberName = tree.First(); tree.RemoveAt(0); var member = knownType.Members.FirstOrDefault(x => x.Name == memberName); if (member == null) { return null; } knownType = _knownTypes.FirstOrDefault(x => x.Type.Namespace + "." + x.Type.Name == member.ReturnType.Namespace + "." + member.ReturnType.Name); } return new MethodSession() { Type = knownType, MethodName = tree.Last(), ParameterIndex = parameterIndex, }; } } } } return null; } private DeclaredMethodSession GetDeclaredMethodSession() { var currentLine = GetCurrentLineText(); if (currentLine.Count(x => x == '(') > 1) { currentLine = currentLine.Split('(')[currentLine.Split('(').Length - 2]; } var words = currentLine.Split(' '); if (words.Count() > 0 && (words.First() == "private" || words.First() == "public" || words.First() == "void")) { return null; } var expression = currentLine; if (expression != null) { var tree = expression.Split('.').Select(x => x.Replace("\n", "").Replace("\r", "").Replace(" ", "").Replace("\t", "").Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "")).ToList(); var variableName = tree.FirstOrDefault(); if (variableName != null && tree.Count > 0) { tree.RemoveAt(0); var variables = _parser.GetContextSymbols(Document.Text, CaretOffset); var variable = variables.FirstOrDefault(x => x.Name == variableName); if (variable != null) { var declaredType = _declaredTypes.FirstOrDefault(x => x.Name == Regex.Replace(variable.Type, "<.+>", "")); if (declaredType != null) { while (tree.Count > 1) { var memberName = tree.First(); tree.RemoveAt(0); var member = declaredType.Symbols.FirstOrDefault(x => x.Name == memberName); if (member == null) { return null; } declaredType = _declaredTypes.FirstOrDefault(x => x.ContainingNamespace + "." + x.Name == member.ContainingNamespace + "." + member.Type); } var methodName = tree.Count > 0 ? tree.Last() : variableName; var method = declaredType.Symbols.FirstOrDefault(x => x.Kind == SymbolKind.Method && x.Name == methodName); if (method != null) { return new DeclaredMethodSession() { Type = declaredType, Method = method, }; } } else if (tree.Count == 0) { var method = variable; if (method != null) { return new DeclaredMethodSession() { Type = declaredType, Method = method, }; } } } } } return null; } private List GetPreviousWords() { var currentLine = GetCurrentLine(); var currentText = Document.GetText(currentLine.Offset, CaretOffset - currentLine.Offset); return currentText.Split(' ', ',').ToList(); } #endregion #region Reference Assemblies Changed private void OnReferenceAssembliesChanged() { if (ReferenceAssemblies != null) { ReferenceAssemblies.CollectionChanged -= ReferenceAssemblies_CollectionChanged; ReferenceAssemblies.CollectionChanged += ReferenceAssemblies_CollectionChanged; InvalidateHighlighting(); } } private void ReferenceAssemblies_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { InvalidateHighlighting(); } #endregion } }