aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.Stubs/Controls/ScriptEditorControl.xaml.cs
blob: 7628e5655e8d7a2035d0196019ce89efefa7ac6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
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.Stubs.Controls
{
    /// <summary>
    /// Represents a C# script editor control.
    /// </summary>
    /// <seealso cref="System.Windows.Controls.UserControl" />
    /// <seealso cref="System.Windows.Markup.IComponentConnector" />
    public partial class ScriptEditorControl : UserControl
    {
        #region Completion

        /// <summary>
        /// Represents an auto complete item.
        /// </summary>
        /// <seealso cref="ICSharpCode.AvalonEdit.CodeCompletion.ICompletionData" />
        internal class CompletionData : ICompletionData
        {
            private String _description;

            /// <summary>
            /// Gets or sets the icon source.
            /// </summary>
            public BitmapSource Source { get; set; }

            /// <summary>
            /// Initializes a new instance of the <see cref="CompletionData"/> class.
            /// </summary>
            /// <param name="text">The text.</param>
            /// <param name="description">The description.</param>
            public CompletionData(string text, String description)
            {
                this.Text = text;
                _description = description;
            }

            /// <summary>
            /// Gets the image.
            /// </summary>
            public System.Windows.Media.ImageSource Image
            {
                get { return Source; }
            }

            /// <summary>
            /// Gets the text. This property is used to filter the list of visible elements.
            /// </summary>
            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; }
            }

            /// <summary>
            /// Gets the description.
            /// </summary>
            public object Description
            {
                get { return _description; }
            }

            /// <summary>
            /// 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.
            /// </summary>
            public double Priority { get { return 0; } }

            /// <summary>
            /// Perform the completion.
            /// </summary>
            /// <param name="textArea">The text area on which completion is performed.</param>
            /// <param name="completionSegment">The text segment that was used by the completion window if
            /// the user types (segment between CompletionWindow.StartOffset and CompletionWindow.EndOffset).</param>
            /// <param name="insertionRequestEventArgs">The EventArgs used for the insertion request.
            /// These can be TextCompositionEventArgs, KeyEventArgs, MouseEventArgs, depending on how
            /// the insertion was triggered.</param>
            public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs)
            {
                textArea.Document.Replace(completionSegment, this.Text);
            }

            /// <summary>
            /// Returns a <see cref="System.String" /> that represents this instance.
            /// </summary>
            /// <returns>
            /// A <see cref="System.String" /> that represents this instance.
            /// </returns>
            public override string ToString()
            {
                return Text;
            }
        }

        #endregion

        private CompletionWindow completionWindow; //Holds the auto-complete window instance.

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ScriptEditorControl"/> class.
        /// </summary>
        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

        /// <summary>
        /// Handles the TextEntered event of the textEditor_TextArea control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="TextCompositionEventArgs"/> instance containing the event data.</param>
        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<ICompletionData> data = completionWindow.CompletionList.CompletionData;

                    bool ok = false;

                    List<KeyValuePair<String, Type>> types = new List<KeyValuePair<String, Type>>();

                    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<string, Type>(v.Name, hT.Value));
                                }
                            }

                        }
                        catch { }
                    }

                    KeyValuePair<String, Type> 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;
                        };
                    }
                }
            }
        }

        /// <summary>
        /// Handles the TextEntering event of the textEditor_TextArea control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="TextCompositionEventArgs"/> instance containing the event data.</param>
        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);
                }
            }
        }

        /// <summary>
        /// Handles the TextChanged event of the textEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        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.Stubs.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("<Word>{0}</Word>", 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

        /// <summary>
        /// Fills the type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="data">The data.</param>
        private void FillType(Type type, IList<ICompletionData> data)
        {
            List<CompletionData> items = new List<CompletionData>();

            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);
            }
        }

        /// <summary>
        /// Fills the assembly.
        /// </summary>
        /// <param name="asm">The asm.</param>
        /// <param name="data">The data.</param>
        private void FillAssembly(Assembly asm, IList<ICompletionData> 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

        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        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()));

        /// <summary>
        /// Gets or sets the highlight types.
        /// </summary>
        public ObservableCollection<KeyValuePair<string, Type>> HighlightTypes
        {
            get { return (ObservableCollection<KeyValuePair<string, Type>>)GetValue(HighlightTypesProperty); }
            set { SetValue(HighlightTypesProperty, value); }
        }
        public static readonly DependencyProperty HighlightTypesProperty =
            DependencyProperty.Register("HighlightTypes", typeof(ObservableCollection<KeyValuePair<string, Type>>), typeof(ScriptEditorControl), new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets the intellisense types.
        /// </summary>
        public ObservableCollection<KeyValuePair<String,Type>> IntellisenseTypes
        {
            get { return (ObservableCollection<KeyValuePair<String,Type>>)GetValue(IntellisenseTypesProperty); }
            set { SetValue(IntellisenseTypesProperty, value); }
        }
        public static readonly DependencyProperty IntellisenseTypesProperty =
            DependencyProperty.Register("IntellisenseTypes", typeof(ObservableCollection<KeyValuePair<String,Type>>), typeof(ScriptEditorControl), new PropertyMetadata(null));


        #endregion

        #region Virtual Methods

        /// <summary>
        /// Called when the text has changed.
        /// </summary>
        protected virtual void OnTextChanged()
        {
            if (textEditor.Text != Text)
            {
                textEditor.Text = Text;
            }
        }

        /// <summary>
        /// Called when the insert script command has changed.
        /// </summary>
        protected virtual void OnInsertScriptCommandChanged()
        {
            if (InsertSnippetCommand != null)
            {
                InsertSnippetCommand.Executed += (x, snippet) =>
                {
                    textEditor.Document.Insert(textEditor.TextArea.Caret.Offset, snippet);
                };
            }
        }

        #endregion

        #region Commands

        /// <summary>
        /// Gets or sets the run command.
        /// </summary>
        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));

        /// <summary>
        /// Gets or sets the stop command.
        /// </summary>
        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));

        /// <summary>
        /// Gets or sets the save command.
        /// </summary>
        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));

        /// <summary>
        /// Gets or sets the insert snippet command.
        /// </summary>
        public RelayCommand<String> InsertSnippetCommand
        {
            get { return (RelayCommand<String>)GetValue(InsertSnippetCommandProperty); }
            set { SetValue(InsertSnippetCommandProperty, value); }
        }
        public static readonly DependencyProperty InsertSnippetCommandProperty =
            DependencyProperty.Register("InsertSnippetCommand", typeof(RelayCommand<String>), 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<String> 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();
        }
    }
}