aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.cs377
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.xaml72
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBox.cs103
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBoxItem.cs63
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindow.cs196
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs417
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/ICompletionData.cs49
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/IOverloadProvider.cs42
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.cs94
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.xaml118
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs58
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs101
12 files changed, 1690 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.cs
new file mode 100644
index 000000000..ea6e1856f
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.cs
@@ -0,0 +1,377 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Linq;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// The listbox used inside the CompletionWindow, contains CompletionListBox.
+ /// </summary>
+ public class CompletionList : Control
+ {
+ static CompletionList()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(CompletionList),
+ new FrameworkPropertyMetadata(typeof(CompletionList)));
+ }
+
+ bool isFiltering = true;
+ /// <summary>
+ /// If true, the CompletionList is filtered to show only matching items. Also enables search by substring.
+ /// If false, enables the old behavior: no filtering, search by string.StartsWith.
+ /// </summary>
+ public bool IsFiltering {
+ get { return isFiltering; }
+ set { isFiltering = value; }
+ }
+
+ /// <summary>
+ /// Dependency property for <see cref="EmptyTemplate" />.
+ /// </summary>
+ public static readonly DependencyProperty EmptyTemplateProperty =
+ DependencyProperty.Register("EmptyTemplate", typeof(ControlTemplate), typeof(CompletionList),
+ new FrameworkPropertyMetadata());
+
+ /// <summary>
+ /// Content of EmptyTemplate will be shown when CompletionList contains no items.
+ /// If EmptyTemplate is null, nothing will be shown.
+ /// </summary>
+ public ControlTemplate EmptyTemplate {
+ get { return (ControlTemplate)GetValue(EmptyTemplateProperty); }
+ set { SetValue(EmptyTemplateProperty, value); }
+ }
+
+ /// <summary>
+ /// Is raised when the completion list indicates that the user has chosen
+ /// an entry to be completed.
+ /// </summary>
+ public event EventHandler InsertionRequested;
+
+ /// <summary>
+ /// Raises the InsertionRequested event.
+ /// </summary>
+ public void RequestInsertion(EventArgs e)
+ {
+ if (InsertionRequested != null)
+ InsertionRequested(this, e);
+ }
+
+ CompletionListBox listBox;
+
+ /// <inheritdoc/>
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ listBox = GetTemplateChild("PART_ListBox") as CompletionListBox;
+ if (listBox != null) {
+ listBox.ItemsSource = completionData;
+ }
+ }
+
+ /// <summary>
+ /// Gets the list box.
+ /// </summary>
+ public CompletionListBox ListBox {
+ get {
+ if (listBox == null)
+ ApplyTemplate();
+ return listBox;
+ }
+ }
+
+ /// <summary>
+ /// Gets the scroll viewer used in this list box.
+ /// </summary>
+ public ScrollViewer ScrollViewer {
+ get { return listBox != null ? listBox.scrollViewer : null; }
+ }
+
+ ObservableCollection<ICompletionData> completionData = new ObservableCollection<ICompletionData>();
+
+ /// <summary>
+ /// Gets the list to which completion data can be added.
+ /// </summary>
+ public IList<ICompletionData> CompletionData {
+ get { return completionData; }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+ if (!e.Handled) {
+ HandleKey(e);
+ }
+ }
+
+ /// <summary>
+ /// Handles a key press. Used to let the completion list handle key presses while the
+ /// focus is still on the text editor.
+ /// </summary>
+ public void HandleKey(KeyEventArgs e)
+ {
+ if (listBox == null)
+ return;
+
+ // We have to do some key handling manually, because the default doesn't work with
+ // our simulated events.
+ // Also, the default PageUp/PageDown implementation changes the focus, so we avoid it.
+ switch (e.Key) {
+ case Key.Down:
+ e.Handled = true;
+ listBox.SelectIndex(listBox.SelectedIndex + 1);
+ break;
+ case Key.Up:
+ e.Handled = true;
+ listBox.SelectIndex(listBox.SelectedIndex - 1);
+ break;
+ case Key.PageDown:
+ e.Handled = true;
+ listBox.SelectIndex(listBox.SelectedIndex + listBox.VisibleItemCount);
+ break;
+ case Key.PageUp:
+ e.Handled = true;
+ listBox.SelectIndex(listBox.SelectedIndex - listBox.VisibleItemCount);
+ break;
+ case Key.Home:
+ e.Handled = true;
+ listBox.SelectIndex(0);
+ break;
+ case Key.End:
+ e.Handled = true;
+ listBox.SelectIndex(listBox.Items.Count - 1);
+ break;
+ case Key.Tab:
+ case Key.Enter:
+ e.Handled = true;
+ RequestInsertion(e);
+ break;
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
+ {
+ base.OnMouseDoubleClick(e);
+ if (e.ChangedButton == MouseButton.Left) {
+ e.Handled = true;
+ RequestInsertion(e);
+ }
+ }
+
+ /// <summary>
+ /// Gets/Sets the selected item.
+ /// </summary>
+ public ICompletionData SelectedItem {
+ get {
+ return (listBox != null ? listBox.SelectedItem : null) as ICompletionData;
+ }
+ set {
+ if (listBox == null && value != null)
+ ApplyTemplate();
+ listBox.SelectedItem = value;
+ }
+ }
+
+ /// <summary>
+ /// Occurs when the SelectedItem property changes.
+ /// </summary>
+ public event SelectionChangedEventHandler SelectionChanged {
+ add { AddHandler(Selector.SelectionChangedEvent, value); }
+ remove { RemoveHandler(Selector.SelectionChangedEvent, value); }
+ }
+
+ // SelectItem gets called twice for every typed character (once from FormatLine), this helps execute SelectItem only once
+ string currentText;
+ ObservableCollection<ICompletionData> currentList;
+
+ /// <summary>
+ /// Selects the best match, and filter the items if turned on using <see cref="IsFiltering" />.
+ /// </summary>
+ public void SelectItem(string text)
+ {
+ if (text == currentText)
+ return;
+ if (listBox == null)
+ ApplyTemplate();
+
+ if (this.IsFiltering) {
+ SelectItemFiltering(text);
+ }
+ else {
+ SelectItemWithStart(text);
+ }
+ currentText = text;
+ }
+
+ /// <summary>
+ /// Filters CompletionList items to show only those matching given query, and selects the best match.
+ /// </summary>
+ public void SelectItemFiltering(string query)
+ {
+ // if the user just typed one more character, don't filter all data but just filter what we are already displaying
+ var listToFilter = (this.currentList != null && (!string.IsNullOrEmpty(this.currentText)) && (!string.IsNullOrEmpty(query)) &&
+ query.StartsWith(this.currentText, StringComparison.Ordinal)) ?
+ this.currentList : this.completionData;
+
+ var matchingItems =
+ from item in listToFilter
+ let quality = GetMatchQuality(item.Text, query)
+ where quality > 0
+ select new { Item = item, Quality = quality };
+
+ // e.g. "DateTimeKind k = (*cc here suggests DateTimeKind*)"
+ ICompletionData suggestedItem = listBox.SelectedIndex != -1 ? (ICompletionData)(listBox.Items[listBox.SelectedIndex]) : null;
+
+ var listBoxItems = new ObservableCollection<ICompletionData>();
+ int bestIndex = -1;
+ int bestQuality = -1;
+ double bestPriority = 0;
+ int i = 0;
+ foreach (var matchingItem in matchingItems) {
+ double priority = matchingItem.Item == suggestedItem ? double.PositiveInfinity : matchingItem.Item.Priority;
+ int quality = matchingItem.Quality;
+ if (quality > bestQuality || (quality == bestQuality && (priority > bestPriority))) {
+ bestIndex = i;
+ bestPriority = priority;
+ bestQuality = quality;
+ }
+ listBoxItems.Add(matchingItem.Item);
+ i++;
+ }
+ this.currentList = listBoxItems;
+ listBox.ItemsSource = listBoxItems;
+ SelectIndexCentered(bestIndex);
+ }
+
+ /// <summary>
+ /// Selects the item that starts with the specified query.
+ /// </summary>
+ void SelectItemWithStart(string query)
+ {
+ if (string.IsNullOrEmpty(query))
+ return;
+
+ int suggestedIndex = listBox.SelectedIndex;
+
+ int bestIndex = -1;
+ int bestQuality = -1;
+ double bestPriority = 0;
+ for (int i = 0; i < completionData.Count; ++i) {
+ int quality = GetMatchQuality(completionData[i].Text, query);
+ if (quality < 0)
+ continue;
+
+ double priority = completionData[i].Priority;
+ bool useThisItem;
+ if (bestQuality < quality) {
+ useThisItem = true;
+ } else {
+ if (bestIndex == suggestedIndex) {
+ useThisItem = false;
+ } else if (i == suggestedIndex) {
+ // prefer recommendedItem, regardless of its priority
+ useThisItem = bestQuality == quality;
+ } else {
+ useThisItem = bestQuality == quality && bestPriority < priority;
+ }
+ }
+ if (useThisItem) {
+ bestIndex = i;
+ bestPriority = priority;
+ bestQuality = quality;
+ }
+ }
+ SelectIndexCentered(bestIndex);
+ }
+
+ void SelectIndexCentered(int bestIndex)
+ {
+ if (bestIndex < 0) {
+ listBox.ClearSelection();
+ } else {
+ int firstItem = listBox.FirstVisibleItem;
+ if (bestIndex < firstItem || firstItem + listBox.VisibleItemCount <= bestIndex) {
+ // CenterViewOn does nothing as CompletionListBox.ScrollViewer is null
+ listBox.CenterViewOn(bestIndex);
+ listBox.SelectIndex(bestIndex);
+ } else {
+ listBox.SelectIndex(bestIndex);
+ }
+ }
+ }
+
+ int GetMatchQuality(string itemText, string query)
+ {
+ if (itemText == null)
+ throw new ArgumentNullException("itemText", "ICompletionData.Text returned null");
+
+ // Qualities:
+ // 8 = full match case sensitive
+ // 7 = full match
+ // 6 = match start case sensitive
+ // 5 = match start
+ // 4 = match CamelCase when length of query is 1 or 2 characters
+ // 3 = match substring case sensitive
+ // 2 = match sustring
+ // 1 = match CamelCase
+ // -1 = no match
+ if (query == itemText)
+ return 8;
+ if (string.Equals(itemText, query, StringComparison.InvariantCultureIgnoreCase))
+ return 7;
+
+ if (itemText.StartsWith(query, StringComparison.InvariantCulture))
+ return 6;
+ if (itemText.StartsWith(query, StringComparison.InvariantCultureIgnoreCase))
+ return 5;
+
+ bool? camelCaseMatch = null;
+ if (query.Length <= 2) {
+ camelCaseMatch = CamelCaseMatch(itemText, query);
+ if (camelCaseMatch == true) return 4;
+ }
+
+ // search by substring, if filtering (i.e. new behavior) turned on
+ if (IsFiltering) {
+ if (itemText.IndexOf(query, StringComparison.InvariantCulture) >= 0)
+ return 3;
+ if (itemText.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) >= 0)
+ return 2;
+ }
+
+ if (!camelCaseMatch.HasValue)
+ camelCaseMatch = CamelCaseMatch(itemText, query);
+ if (camelCaseMatch == true)
+ return 1;
+
+ return -1;
+ }
+
+ static bool CamelCaseMatch(string text, string query)
+ {
+ int i = 0;
+ foreach (char upper in text.Where(c => char.IsUpper(c))) {
+ if (i > query.Length - 1)
+ return true; // return true here for CamelCase partial match ("CQ" matches "CodeQualityAnalysis")
+ if (char.ToUpper(query[i], CultureInfo.InvariantCulture) != upper)
+ return false;
+ i++;
+ }
+ if (i >= query.Length)
+ return true;
+ return false;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.xaml
new file mode 100644
index 000000000..3887b139d
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionList.xaml
@@ -0,0 +1,72 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:AvalonEdit="clr-namespace:Tango.Scripting.Editors"
+ xmlns:cc="clr-namespace:Tango.Scripting.Editors.CodeCompletion"
+>
+
+
+ <!--<Style TargetType="{x:Type ListBoxItem}" x:Key="CompletionListBoxItem">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type ListBoxItem}">
+ <Border Name="Bd"
+ Background="#202020"
+ BorderBrush="#222222"
+ BorderThickness="0 0 0 1"
+ Padding="{TemplateBinding Padding}"
+ SnapsToDevicePixels="true">
+ <ContentPresenter
+ HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+ VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+ SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
+ </Border>
+ --><!-- Simplified triggers:
+ we don't want a gray selection background when the ListBox doesn't have focus
+ --><!--
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsSelected" Value="true">
+ <Setter Property="Background"
+ Value="#242424"/>
+ <Setter Property="Foreground"
+ Value="#242424"/>
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>-->
+
+ <Style TargetType="{x:Type cc:CompletionList}">
+ <Style.Resources>
+ <Style TargetType="ListBoxItem">
+ <Setter Property="Background" Value="#202020"></Setter>
+ <Setter Property="Foreground" Value="Gainsboro"></Setter>
+ <Setter Property="Padding" Value="2"></Setter>
+ <Setter Property="BorderThickness" Value="0"></Setter>
+ <Style.Triggers>
+ <Trigger Property="IsSelected" Value="True">
+ <Setter Property="Foreground" Value="#202020"></Setter>
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+ </Style.Resources>
+ <Setter Property="Background" Value="#202020"></Setter>
+ <Setter Property="BorderThickness" Value="0"></Setter>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type cc:CompletionList}">
+ <cc:CompletionListBox x:Name="PART_ListBox">
+ <ItemsControl.ItemTemplate>
+ <DataTemplate>
+ <StackPanel Orientation="Horizontal">
+ <Image Source="{Binding Image}" Width="16" Height="16" Margin="0,0,4,0"/>
+ <ContentControl Content="{Binding Content}" />
+ </StackPanel>
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </cc:CompletionListBox>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+</ResourceDictionary> \ No newline at end of file
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBox.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBox.cs
new file mode 100644
index 000000000..235c07304
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBox.cs
@@ -0,0 +1,103 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// The list box used inside the CompletionList.
+ /// </summary>
+ public class CompletionListBox : ListBox
+ {
+ internal ScrollViewer scrollViewer;
+
+ /// <inheritdoc/>
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ // Find the scroll viewer:
+ scrollViewer = null;
+ if (this.VisualChildrenCount > 0) {
+ Border border = this.GetVisualChild(0) as Border;
+ if (border != null)
+ scrollViewer = border.Child as ScrollViewer;
+ scrollViewer.BorderThickness = new Thickness(0);
+
+ }
+ }
+
+ /// <summary>
+ /// Gets the number of the first visible item.
+ /// </summary>
+ public int FirstVisibleItem {
+ get {
+ if (scrollViewer == null || scrollViewer.ExtentHeight == 0) {
+ return 0;
+ } else {
+ return (int)(this.Items.Count * scrollViewer.VerticalOffset / scrollViewer.ExtentHeight);
+ }
+ }
+ set {
+ value = value.CoerceValue(0, this.Items.Count - this.VisibleItemCount);
+ if (scrollViewer != null) {
+ scrollViewer.ScrollToVerticalOffset((double)value / this.Items.Count * scrollViewer.ExtentHeight);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the number of visible items.
+ /// </summary>
+ public int VisibleItemCount {
+ get {
+ if (scrollViewer == null || scrollViewer.ExtentHeight == 0) {
+ return 10;
+ } else {
+ return Math.Max(
+ 3,
+ (int)Math.Ceiling(this.Items.Count * scrollViewer.ViewportHeight
+ / scrollViewer.ExtentHeight));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes the selection.
+ /// </summary>
+ public void ClearSelection()
+ {
+ this.SelectedIndex = -1;
+ }
+
+ /// <summary>
+ /// Selects the item with the specified index and scrolls it into view.
+ /// </summary>
+ public void SelectIndex(int index)
+ {
+ if (index >= this.Items.Count)
+ index = this.Items.Count - 1;
+ if (index < 0)
+ index = 0;
+ this.SelectedIndex = index;
+ this.ScrollIntoView(this.SelectedItem);
+ }
+
+ /// <summary>
+ /// Centers the view on the item with the specified index.
+ /// </summary>
+ public void CenterViewOn(int index)
+ {
+ this.FirstVisibleItem = index - VisibleItemCount / 2;
+ }
+
+ protected override DependencyObject GetContainerForItemOverride()
+ {
+ return new CompletionListBoxItem();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBoxItem.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBoxItem.cs
new file mode 100644
index 000000000..55d752bfd
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionListBoxItem.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ public class CompletionListBoxItem : ListBoxItem
+ {
+ public ToolTip toolTip;
+
+ public DataTemplate ToolTipContentTemplate
+ {
+ get { return (DataTemplate)GetValue(ToolTipContentTemplateProperty); }
+ set { SetValue(ToolTipContentTemplateProperty, value); }
+ }
+ public static readonly DependencyProperty ToolTipContentTemplateProperty =
+ DependencyProperty.Register("ToolTipContentTemplate", typeof(DataTemplate), typeof(CompletionListBoxItem), new PropertyMetadata(null));
+
+ public CompletionListBoxItem()
+ {
+ toolTip = new ToolTip();
+ toolTip.PlacementTarget = this;
+ toolTip.HorizontalOffset = 15;
+ toolTip.Background = Brushes.Transparent;
+ toolTip.BorderThickness = new Thickness(0);
+ toolTip.Opacity = 1;
+ toolTip.HasDropShadow = true;
+ toolTip.Placement = System.Windows.Controls.Primitives.PlacementMode.Right;
+ this.Unloaded += CompletionListBoxItem_Unloaded;
+ }
+
+ private void CompletionListBoxItem_Unloaded(object sender, RoutedEventArgs e)
+ {
+ toolTip.StaysOpen = false;
+ toolTip.IsOpen = false;
+ }
+
+ protected override void OnSelected(RoutedEventArgs e)
+ {
+ try
+ {
+ base.OnSelected(e);
+ toolTip.Content = DataContext;
+ toolTip.ContentTemplate = ToolTipContentTemplate;
+ toolTip.StaysOpen = true;
+ toolTip.IsOpen = true;
+ }
+ catch { }
+ }
+
+ protected override void OnUnselected(RoutedEventArgs e)
+ {
+ base.OnUnselected(e);
+ toolTip.StaysOpen = false;
+ toolTip.IsOpen = false;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindow.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindow.cs
new file mode 100644
index 000000000..33759e351
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindow.cs
@@ -0,0 +1,196 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Threading;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+using System.Windows.Media;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// The code completion window.
+ /// </summary>
+ public class CompletionWindow : CompletionWindowBase
+ {
+ readonly CompletionList completionList = new CompletionList();
+
+ public event Action<ICompletionData> InsertionRequest;
+
+ /// <summary>
+ /// Gets the completion list used in this completion window.
+ /// </summary>
+ public CompletionList CompletionList
+ {
+ get { return completionList; }
+ }
+
+ /// <summary>
+ /// Creates a new code completion window.
+ /// </summary>
+ public CompletionWindow(TextArea textArea) : base(textArea)
+ {
+
+ // keep height automatic
+ this.CloseAutomatically = true;
+ this.SizeToContent = SizeToContent.WidthAndHeight;
+ this.WindowStyle = WindowStyle.None;
+ this.ResizeMode = ResizeMode.NoResize;
+ this.Content = completionList;
+ // prevent user from resizing window to 0x0
+ this.MinHeight = 15;
+ this.MinWidth = 30;
+ //this.Background = new SolidColorBrush(Color.FromRgb(15, 15, 15));
+ this.Foreground = Brushes.Gainsboro;
+ }
+
+ public override void ShowCompletion()
+ {
+ base.ShowCompletion();
+ AttachEvents();
+ }
+
+ public override void HideCompletion()
+ {
+ base.HideCompletion();
+
+ foreach (var item in completionList.ListBox.Items)
+ {
+ var box = completionList.ListBox.ItemContainerGenerator.ContainerFromItem(item) as CompletionListBoxItem;
+ if (box != null && box.IsSelected)
+ {
+ box.toolTip.IsOpen = false;
+ }
+ }
+ }
+
+ void completionList_InsertionRequested(object sender, EventArgs e)
+ {
+ HideCompletion();
+ // The window must close before Complete() is called.
+ // If the Complete callback pushes stacked input handlers, we don't want to pop those when the CC window closes.
+ var item = completionList.SelectedItem;
+ //if (item != null)
+ // item.Complete(this.TextArea, new AnchorSegment(this.TextArea.Document, this.StartOffset, this.EndOffset - this.StartOffset), e);
+
+ if (item != null)
+ {
+ InsertionRequest?.Invoke(item);
+ }
+
+ completionList.SelectedItem = null;
+ }
+
+ void AttachEvents()
+ {
+ this.completionList.InsertionRequested += completionList_InsertionRequested;
+ this.TextArea.Caret.PositionChanged += CaretPositionChanged;
+ this.TextArea.MouseWheel += textArea_MouseWheel;
+ this.TextArea.PreviewTextInput += textArea_PreviewTextInput;
+ }
+
+ /// <inheritdoc/>
+ protected override void DetachEvents()
+ {
+ this.completionList.InsertionRequested -= completionList_InsertionRequested;
+ this.TextArea.Caret.PositionChanged -= CaretPositionChanged;
+ this.TextArea.MouseWheel -= textArea_MouseWheel;
+ this.TextArea.PreviewTextInput -= textArea_PreviewTextInput;
+ base.DetachEvents();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+ if (!e.Handled)
+ {
+ completionList.HandleKey(e);
+ }
+ }
+
+ void textArea_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ e.Handled = RaiseEventPair(this, PreviewTextInputEvent, TextInputEvent,
+ new TextCompositionEventArgs(e.Device, e.TextComposition));
+
+ if (e.Text == " ")
+ {
+ HideCompletion();
+ }
+ }
+
+ void textArea_MouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ e.Handled = RaiseEventPair(GetScrollEventTarget(),
+ PreviewMouseWheelEvent, MouseWheelEvent,
+ new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta));
+ }
+
+ UIElement GetScrollEventTarget()
+ {
+ if (completionList == null)
+ return this;
+ return completionList.ScrollViewer ?? completionList.ListBox ?? (UIElement)completionList;
+ }
+
+ /// <summary>
+ /// Gets/Sets whether the completion window should close automatically.
+ /// The default value is true.
+ /// </summary>
+ public bool CloseAutomatically { get; set; }
+
+ /// <inheritdoc/>
+ protected override bool CloseOnFocusLost
+ {
+ get { return this.CloseAutomatically; }
+ }
+
+ /// <summary>
+ /// When this flag is set, code completion closes if the caret moves to the
+ /// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing",
+ /// but not in dot-completion.
+ /// Has no effect if CloseAutomatically is false.
+ /// </summary>
+ public bool CloseWhenCaretAtBeginning { get; set; }
+
+ void CaretPositionChanged(object sender, EventArgs e)
+ {
+ int offset = this.TextArea.Caret.Offset;
+ if (offset == this.StartOffset)
+ {
+ if (CloseAutomatically && CloseWhenCaretAtBeginning)
+ {
+ HideCompletion();
+ }
+ else
+ {
+ completionList.SelectItem(string.Empty);
+ }
+ return;
+ }
+ if (offset < this.StartOffset || offset > this.EndOffset)
+ {
+ if (CloseAutomatically)
+ {
+ HideCompletion();
+ }
+ }
+ else
+ {
+ TextDocument document = this.TextArea.Document;
+ if (document != null)
+ {
+ completionList.SelectItem(document.GetText(this.StartOffset, offset - this.StartOffset));
+ }
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs
new file mode 100644
index 000000000..7766d323a
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/CompletionWindowBase.cs
@@ -0,0 +1,417 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Threading;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Rendering;
+using Tango.Scripting.Editors.Utils;
+using System.ComponentModel;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// Base class for completion windows. Handles positioning the window at the caret.
+ /// </summary>
+ public class CompletionWindowBase : Window
+ {
+ static CompletionWindowBase()
+ {
+ WindowStyleProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(WindowStyle.None));
+ ShowActivatedProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
+ ShowInTaskbarProperty.OverrideMetadata(typeof(CompletionWindowBase), new FrameworkPropertyMetadata(Boxes.False));
+ }
+
+ /// <summary>
+ /// Gets the parent TextArea.
+ /// </summary>
+ public TextArea TextArea { get; private set; }
+
+ Window parentWindow;
+ TextDocument document;
+
+ /// <summary>
+ /// Gets/Sets the start of the text range in which the completion window stays open.
+ /// This text portion is used to determine the text used to select an entry in the completion list by typing.
+ /// </summary>
+ public int StartOffset { get; set; }
+
+ /// <summary>
+ /// Gets/Sets the end of the text range in which the completion window stays open.
+ /// This text portion is used to determine the text used to select an entry in the completion list by typing.
+ /// </summary>
+ public int EndOffset { get; set; }
+
+ /// <summary>
+ /// Gets whether the window was opened above the current line.
+ /// </summary>
+ protected bool IsUp { get; private set; }
+
+ /// <summary>
+ /// Creates a new CompletionWindowBase.
+ /// </summary>
+ public CompletionWindowBase(TextArea textArea)
+ {
+ if (textArea == null)
+ throw new ArgumentNullException("textArea");
+ this.TextArea = textArea;
+
+ parentWindow = Window.GetWindow(TextArea);
+ this.Owner = parentWindow;
+ this.AddHandler(MouseUpEvent, new MouseButtonEventHandler(OnMouseUp), true);
+ }
+
+ public virtual void ShowCompletion()
+ {
+ StartOffset = EndOffset = this.TextArea.Caret.Offset;
+
+ AttachEvents();
+ SetPosition();
+ UpdatePosition();
+ Show();
+ }
+
+ public virtual void HideCompletion()
+ {
+ Debug.WriteLine("Hide Completion...");
+ DetachEvents();
+ Hide();
+ }
+
+ #region Event Handlers
+
+ void AttachEvents()
+ {
+ document = this.TextArea.Document;
+ if (document != null) {
+ document.Changing += textArea_Document_Changing;
+ }
+ // LostKeyboardFocus seems to be more reliable than PreviewLostKeyboardFocus - see SD-1729
+ this.TextArea.LostKeyboardFocus += TextAreaLostFocus;
+ this.TextArea.TextView.ScrollOffsetChanged += TextViewScrollOffsetChanged;
+ this.TextArea.DocumentChanged += TextAreaDocumentChanged;
+ if (parentWindow != null) {
+ parentWindow.LocationChanged += parentWindow_LocationChanged;
+ }
+
+ // close previous completion windows of same type
+ foreach (InputHandler x in this.TextArea.StackedInputHandlers.OfType<InputHandler>()) {
+ if (x.window.GetType() == this.GetType())
+ this.TextArea.PopStackedInputHandler(x);
+ }
+
+ myInputHandler = new InputHandler(this);
+ this.TextArea.PushStackedInputHandler(myInputHandler);
+ }
+
+ /// <summary>
+ /// Detaches events from the text area.
+ /// </summary>
+ protected virtual void DetachEvents()
+ {
+ if (document != null) {
+ document.Changing -= textArea_Document_Changing;
+ }
+ this.TextArea.LostKeyboardFocus -= TextAreaLostFocus;
+ this.TextArea.TextView.ScrollOffsetChanged -= TextViewScrollOffsetChanged;
+ this.TextArea.DocumentChanged -= TextAreaDocumentChanged;
+ if (parentWindow != null) {
+ parentWindow.LocationChanged -= parentWindow_LocationChanged;
+ }
+ this.TextArea.PopStackedInputHandler(myInputHandler);
+ }
+
+ #region InputHandler
+ InputHandler myInputHandler;
+
+ /// <summary>
+ /// A dummy input handler (that justs invokes the default input handler).
+ /// This is used to ensure the completion window closes when any other input handler
+ /// becomes active.
+ /// </summary>
+ sealed class InputHandler : TextAreaStackedInputHandler
+ {
+ internal readonly CompletionWindowBase window;
+
+ public InputHandler(CompletionWindowBase window)
+ : base(window.TextArea)
+ {
+ Debug.Assert(window != null);
+ this.window = window;
+ }
+
+ public override void Detach()
+ {
+ base.Detach();
+ //window.HideCompletion();
+ }
+
+ const Key KeyDeadCharProcessed = (Key)0xac; // Key.DeadCharProcessed; // new in .NET 4
+
+ public override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ // prevents crash when typing deadchar while CC window is open
+ if (e.Key == KeyDeadCharProcessed)
+ return;
+ e.Handled = RaiseEventPair(window, PreviewKeyDownEvent, KeyDownEvent,
+ new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
+ }
+
+ public override void OnPreviewKeyUp(KeyEventArgs e)
+ {
+ if (e.Key == KeyDeadCharProcessed)
+ return;
+ e.Handled = RaiseEventPair(window, PreviewKeyUpEvent, KeyUpEvent,
+ new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key));
+ }
+ }
+ #endregion
+
+ void TextViewScrollOffsetChanged(object sender, EventArgs e)
+ {
+ // Workaround for crash #1580 (reproduction steps unknown):
+ // NullReferenceException in System.Windows.Window.CreateSourceWindow()
+ if (!sourceIsInitialized)
+ return;
+
+ IScrollInfo scrollInfo = this.TextArea.TextView;
+ Rect visibleRect = new Rect(scrollInfo.HorizontalOffset, scrollInfo.VerticalOffset, scrollInfo.ViewportWidth, scrollInfo.ViewportHeight);
+ // close completion window when the user scrolls so far that the anchor position is leaving the visible area
+ if (visibleRect.Contains(visualLocation) || visibleRect.Contains(visualLocationTop))
+ UpdatePosition();
+ else
+ HideCompletion();
+
+ }
+
+ void TextAreaDocumentChanged(object sender, EventArgs e)
+ {
+ HideCompletion();
+ }
+
+ void TextAreaLostFocus(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
+ }
+
+ void parentWindow_LocationChanged(object sender, EventArgs e)
+ {
+ UpdatePosition();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnDeactivated(EventArgs e)
+ {
+ base.OnDeactivated(e);
+ Dispatcher.BeginInvoke(new Action(CloseIfFocusLost), DispatcherPriority.Background);
+ }
+ #endregion
+
+ /// <summary>
+ /// Raises a tunnel/bubble event pair for a WPF control.
+ /// </summary>
+ /// <param name="target">The WPF control for which the event should be raised.</param>
+ /// <param name="previewEvent">The tunneling event.</param>
+ /// <param name="event">The bubbling event.</param>
+ /// <param name="args">The event args to use.</param>
+ /// <returns>The <see cref="RoutedEventArgs.Handled"/> value of the event args.</returns>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
+ protected static bool RaiseEventPair(UIElement target, RoutedEvent previewEvent, RoutedEvent @event, RoutedEventArgs args)
+ {
+ if (target == null)
+ throw new ArgumentNullException("target");
+ if (previewEvent == null)
+ throw new ArgumentNullException("previewEvent");
+ if (@event == null)
+ throw new ArgumentNullException("event");
+ if (args == null)
+ throw new ArgumentNullException("args");
+ args.RoutedEvent = previewEvent;
+ target.RaiseEvent(args);
+ args.RoutedEvent = @event;
+ target.RaiseEvent(args);
+ return args.Handled;
+ }
+
+ // Special handler: handledEventsToo
+ void OnMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ ActivateParentWindow();
+ }
+
+ /// <summary>
+ /// Activates the parent window.
+ /// </summary>
+ protected virtual void ActivateParentWindow()
+ {
+ if (parentWindow != null)
+ parentWindow.Activate();
+ }
+
+ void CloseIfFocusLost()
+ {
+ if (CloseOnFocusLost) {
+ Debug.WriteLine("CloseIfFocusLost: this.IsActive=" + this.IsActive + " IsTextAreaFocused=" + IsTextAreaFocused);
+ if (!this.IsActive && !IsTextAreaFocused) {
+ HideCompletion();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets whether the completion window should automatically close when the text editor looses focus.
+ /// </summary>
+ protected virtual bool CloseOnFocusLost {
+ get { return true; }
+ }
+
+ bool IsTextAreaFocused {
+ get {
+ if (parentWindow != null && !parentWindow.IsActive)
+ return false;
+ return this.TextArea.IsKeyboardFocused;
+ }
+ }
+
+ bool sourceIsInitialized;
+
+ /// <inheritdoc/>
+ protected override void OnSourceInitialized(EventArgs e)
+ {
+ base.OnSourceInitialized(e);
+ SetPosition();
+
+ //sourceIsInitialized = true;
+ }
+
+ public void SetPosition()
+ {
+ if (document != null && this.StartOffset != this.TextArea.Caret.Offset)
+ {
+ SetPosition(new TextViewPosition(document.GetLocation(this.StartOffset)));
+ }
+ else
+ {
+ SetPosition(this.TextArea.Caret.Position);
+ }
+ }
+
+ public void UpdatePositionFix()
+ {
+ SetPosition(this.TextArea.Caret.Position);
+ }
+
+ //public void UpdatePosition()
+ //{
+ // SetPosition(this.TextArea.Caret.Position);
+ //}
+
+ /// <inheritdoc/>
+ protected override void OnClosed(EventArgs e)
+ {
+ DetachEvents();
+ HideCompletion();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+ if (!e.Handled && e.Key == Key.Escape) {
+ e.Handled = true;
+ HideCompletion();
+ }
+ }
+
+ Point visualLocation, visualLocationTop;
+
+ /// <summary>
+ /// Positions the completion window at the specified position.
+ /// </summary>
+ protected void SetPosition(TextViewPosition position)
+ {
+ TextView textView = this.TextArea.TextView;
+
+ visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom);
+ visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop);
+ UpdatePosition();
+ }
+
+ /// <summary>
+ /// Updates the position of the CompletionWindow based on the parent TextView position and the screen working area.
+ /// It ensures that the CompletionWindow is completely visible on the screen.
+ /// </summary>
+ public void UpdatePosition()
+ {
+ TextView textView = this.TextArea.TextView;
+ // PointToScreen returns device dependent units (physical pixels)
+ Point location = textView.PointToScreen(visualLocation - textView.ScrollOffset);
+ Point locationTop = textView.PointToScreen(visualLocationTop - textView.ScrollOffset);
+
+ // Let's use device dependent units for everything
+ Size completionWindowSize = new Size(this.ActualWidth, this.ActualHeight).TransformToDevice(textView);
+ Rect bounds = new Rect(location, completionWindowSize);
+ Rect workingScreen = System.Windows.Forms.Screen.GetWorkingArea(location.ToSystemDrawing()).ToWpf();
+ if (!workingScreen.Contains(bounds)) {
+ if (bounds.Left < workingScreen.Left) {
+ bounds.X = workingScreen.Left;
+ } else if (bounds.Right > workingScreen.Right) {
+ bounds.X = workingScreen.Right - bounds.Width;
+ }
+ if (bounds.Bottom > workingScreen.Bottom) {
+ bounds.Y = locationTop.Y - bounds.Height;
+ IsUp = true;
+ } else {
+ IsUp = false;
+ }
+ if (bounds.Y < workingScreen.Top) {
+ bounds.Y = workingScreen.Top;
+ }
+ }
+ // Convert the window bounds to device independent units
+ bounds = bounds.TransformFromDevice(textView);
+ this.Left = bounds.X;
+ this.Top = bounds.Y;
+
+ Debug.WriteLine($"Position: {Left}x{Top}");
+ }
+
+ /// <inheritdoc/>
+ protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
+ {
+ base.OnRenderSizeChanged(sizeInfo);
+ if (sizeInfo.HeightChanged && IsUp) {
+ this.Top += sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height;
+ }
+ }
+
+ /// <summary>
+ /// Gets/sets whether the completion window should expect text insertion at the start offset,
+ /// which not go into the completion region, but before it.
+ /// </summary>
+ /// <remarks>This property allows only a single insertion, it is reset to false
+ /// when that insertion has occurred.</remarks>
+ public bool ExpectInsertionBeforeStart { get; set; }
+
+ void textArea_Document_Changing(object sender, DocumentChangeEventArgs e)
+ {
+ if (e.Offset + e.RemovalLength == this.StartOffset && e.RemovalLength > 0) {
+ HideCompletion(); // removal immediately in front of completion segment: close the window
+ // this is necessary when pressing backspace after dot-completion
+ }
+ if (e.Offset == StartOffset && e.RemovalLength == 0 && ExpectInsertionBeforeStart) {
+ StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion);
+ this.ExpectInsertionBeforeStart = false;
+ } else {
+ StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.BeforeInsertion);
+ }
+ EndOffset = e.GetNewOffset(EndOffset, AnchorMovementType.AfterInsertion);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/ICompletionData.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/ICompletionData.cs
new file mode 100644
index 000000000..9f16876b3
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/ICompletionData.cs
@@ -0,0 +1,49 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Editing;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// Describes an entry in the <see cref="CompletionList"/>.
+ /// </summary>
+ public interface ICompletionData
+ {
+ /// <summary>
+ /// Gets the image.
+ /// </summary>
+ BitmapSource Image { get; }
+
+ /// <summary>
+ /// Gets the text. This property is used to filter the list of visible elements.
+ /// </summary>
+ string Text { get; }
+
+ /// <summary>
+ /// Gets the description.
+ /// </summary>
+ object Description { get; set; }
+
+ /// <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>
+ double Priority { get; set; }
+
+ /// <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>
+ void Complete(ScriptEditor editor);
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/IOverloadProvider.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/IOverloadProvider.cs
new file mode 100644
index 000000000..8aaaa8d41
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/IOverloadProvider.cs
@@ -0,0 +1,42 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// Provides the items for the OverloadViewer.
+ /// </summary>
+ public interface IOverloadProvider : INotifyPropertyChanged
+ {
+ /// <summary>
+ /// Gets/Sets the selected index.
+ /// </summary>
+ int SelectedIndex { get; set; }
+
+ /// <summary>
+ /// Gets the number of overloads.
+ /// </summary>
+ int Count { get; }
+
+ /// <summary>
+ /// Gets the text 'SelectedIndex of Count'.
+ /// </summary>
+ string CurrentIndexText { get; }
+
+ /// <summary>
+ /// Gets the current header.
+ /// </summary>
+ object CurrentHeader { get; }
+
+ /// <summary>
+ /// Gets the current content.
+ /// </summary>
+ object CurrentContent { get; }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.cs
new file mode 100644
index 000000000..bea06f3b3
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.cs
@@ -0,0 +1,94 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using Tango.Scripting.Editors.Editing;
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// A popup-like window that is attached to a text segment.
+ /// </summary>
+ public class InsightWindow : CompletionWindowBase
+ {
+ static InsightWindow()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(InsightWindow),
+ new FrameworkPropertyMetadata(typeof(InsightWindow)));
+ AllowsTransparencyProperty.OverrideMetadata(typeof(InsightWindow),
+ new FrameworkPropertyMetadata(Boxes.True));
+ }
+
+ /// <summary>
+ /// Creates a new InsightWindow.
+ /// </summary>
+ public InsightWindow(TextArea textArea) : base(textArea)
+ {
+ this.CloseAutomatically = true;
+ AttachEvents();
+ }
+
+ /// <inheritdoc/>
+ protected override void OnSourceInitialized(EventArgs e)
+ {
+ base.OnSourceInitialized(e);
+
+ Rect caret = this.TextArea.Caret.CalculateCaretRectangle();
+ Point pointOnScreen = this.TextArea.TextView.PointToScreen(caret.Location - this.TextArea.TextView.ScrollOffset);
+ Rect workingArea = System.Windows.Forms.Screen.FromPoint(pointOnScreen.ToSystemDrawing()).WorkingArea.ToWpf().TransformFromDevice(this);
+
+ MaxHeight = workingArea.Height;
+ MaxWidth = Math.Min(workingArea.Width, Math.Max(1000, workingArea.Width * 0.6));
+ }
+
+ /// <summary>
+ /// Gets/Sets whether the insight window should close automatically.
+ /// The default value is true.
+ /// </summary>
+ public bool CloseAutomatically { get; set; }
+
+ /// <inheritdoc/>
+ protected override bool CloseOnFocusLost {
+ get { return this.CloseAutomatically; }
+ }
+
+ void AttachEvents()
+ {
+ this.TextArea.Caret.PositionChanged += CaretPositionChanged;
+ }
+
+ /// <inheritdoc/>
+ protected override void DetachEvents()
+ {
+ this.TextArea.Caret.PositionChanged -= CaretPositionChanged;
+ base.DetachEvents();
+ }
+
+ void CaretPositionChanged(object sender, EventArgs e)
+ {
+ if (this.CloseAutomatically) {
+ int offset = this.TextArea.Caret.Offset;
+ if (offset < this.StartOffset || offset > this.EndOffset) {
+ Close();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// TemplateSelector for InsightWindow to replace plain string content by a TextBlock with TextWrapping.
+ /// </summary>
+ internal sealed class InsightWindowTemplateSelector : DataTemplateSelector
+ {
+ public override DataTemplate SelectTemplate(object item, DependencyObject container)
+ {
+ if (item is string)
+ return (DataTemplate)((FrameworkElement)container).FindResource("TextBlockTemplate");
+
+ return null;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.xaml b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.xaml
new file mode 100644
index 000000000..6b5a67388
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/InsightWindow.xaml
@@ -0,0 +1,118 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:cc="clr-namespace:Tango.Scripting.Editors.CodeCompletion"
+>
+ <cc:InsightWindowTemplateSelector x:Key="templateSelector" />
+
+ <!-- Template for InsightWindow. Based on the template for ToolTip. -->
+ <Style TargetType="{x:Type cc:InsightWindow}">
+ <Setter Property="SizeToContent" Value="WidthAndHeight" />
+
+ <Setter Property="BorderThickness" Value="1" />
+ <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" />
+ <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.InfoBrushKey}}" />
+ <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InfoTextBrushKey}}" />
+ <Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.StatusFontFamilyKey}}" />
+ <Setter Property="FontSize" Value="{DynamicResource {x:Static SystemFonts.StatusFontSizeKey}}" />
+ <Setter Property="FontStyle" Value="{DynamicResource {x:Static SystemFonts.StatusFontStyleKey}}" />
+ <Setter Property="FontWeight" Value="{DynamicResource {x:Static SystemFonts.StatusFontWeightKey}}" />
+ <Setter Property="Padding" Value="1,1,3,1" />
+ <Setter Property="HorizontalContentAlignment" Value="Left" />
+ <Setter Property="VerticalContentAlignment" Value="Center" />
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type cc:InsightWindow}">
+ <Border BorderThickness="{TemplateBinding Border.BorderThickness}"
+ Padding="{TemplateBinding Control.Padding}"
+ CornerRadius="2,2,2,2"
+ BorderBrush="{TemplateBinding Border.BorderBrush}"
+ Background="{TemplateBinding Panel.Background}">
+ <AdornerDecorator>
+ <ContentPresenter
+ Content="{TemplateBinding ContentControl.Content}"
+ ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
+ ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
+ HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
+ VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
+ SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
+ </AdornerDecorator>
+ </Border>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <!-- Template for OverloadViewer. -->
+ <Style TargetType="{x:Type cc:OverloadViewer}">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type cc:OverloadViewer}">
+ <Grid>
+ <Grid.Resources>
+ <cc:CollapseIfSingleOverloadConverter x:Key="collapseIfSingleOverloadConverter"/>
+
+ <!-- Style of the UpDownButton -->
+ <Style TargetType="{x:Type Button}" x:Key="upDownButtonStyle">
+ <Style.Setters>
+ <Setter Property="Background" Value="LightGray"/>
+ <Setter Property="Padding" Value="2,2,2,2"/>
+ <Setter Property="Width" Value="9"/>
+ <Setter Property="Height" Value="9"/>
+ <Setter Property="SnapsToDevicePixels" Value="True"/>
+ <Setter Property="OverridesDefaultStyle" Value="True"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type Button}">
+ <Border Name="bd"
+ Background="{TemplateBinding Background}" CornerRadius="2">
+ <ContentControl Margin="{TemplateBinding Padding}"
+ Content="{TemplateBinding Content}"/>
+ </Border>
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsMouseOver" Value="true">
+ <Setter TargetName="bd" Property="Background" Value="LightBlue"/>
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style.Setters>
+ </Style>
+
+ <DataTemplate x:Key="TextBlockTemplate">
+ <TextBlock TextWrapping="Wrap" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
+ </DataTemplate>
+ </Grid.Resources>
+
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="1*"/>
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto"/>
+ <RowDefinition Height="1*"/>
+ </Grid.RowDefinitions>
+
+ <StackPanel Grid.Row="0" Grid.Column="0"
+ Margin="0,0,4,0"
+ Orientation="Horizontal"
+ Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.Count, Converter={StaticResource collapseIfSingleOverloadConverter}}">
+ <Button Name="PART_UP" Style="{StaticResource upDownButtonStyle}">
+ <Path Stroke="Black" Fill="Black" Data="M 0,0.866 L 1,0.866 L 0.5,0 Z" Stretch="UniformToFill" />
+ </Button>
+ <TextBlock Margin="2,0,2,0"
+ Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.CurrentIndexText}"/>
+ <Button Name="PART_DOWN" Style="{StaticResource upDownButtonStyle}">
+ <Path Stroke="Black" Fill="Black" Data="M 0,0 L 1,0 L 0.5,0.866 Z" Stretch="UniformToFill" />
+ </Button>
+ </StackPanel>
+ <ContentPresenter Grid.Row="0" Grid.Column="1" ContentTemplateSelector="{StaticResource templateSelector}"
+ Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.CurrentHeader}" />
+ <ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ContentTemplateSelector="{StaticResource templateSelector}"
+ Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Provider.CurrentContent}" />
+ </Grid>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+</ResourceDictionary> \ No newline at end of file
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs
new file mode 100644
index 000000000..5ed165aad
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadInsightWindow.cs
@@ -0,0 +1,58 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Windows;
+using System.Windows.Input;
+
+using Tango.Scripting.Editors.Editing;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// Insight window that shows an OverloadViewer.
+ /// </summary>
+ public class OverloadInsightWindow : InsightWindow
+ {
+ OverloadViewer overloadViewer = new OverloadViewer();
+
+ /// <summary>
+ /// Creates a new OverloadInsightWindow.
+ /// </summary>
+ public OverloadInsightWindow(TextArea textArea) : base(textArea)
+ {
+ overloadViewer.Margin = new Thickness(2,0,0,0);
+ this.Content = overloadViewer;
+ }
+
+ /// <summary>
+ /// Gets/Sets the item provider.
+ /// </summary>
+ public IOverloadProvider Provider {
+ get { return overloadViewer.Provider; }
+ set { overloadViewer.Provider = value; }
+ }
+
+ /// <inheritdoc/>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+ if (!e.Handled && this.Provider.Count > 1) {
+ switch (e.Key) {
+ case Key.Up:
+ e.Handled = true;
+ overloadViewer.ChangeIndex(-1);
+ break;
+ case Key.Down:
+ e.Handled = true;
+ overloadViewer.ChangeIndex(+1);
+ break;
+ }
+ if (e.Handled) {
+ UpdateLayout();
+ UpdatePosition();
+ }
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs
new file mode 100644
index 000000000..f70229a8d
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/CodeCompletion/OverloadViewer.cs
@@ -0,0 +1,101 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+
+namespace Tango.Scripting.Editors.CodeCompletion
+{
+ /// <summary>
+ /// Represents a text between "Up" and "Down" buttons.
+ /// </summary>
+ public class OverloadViewer : Control
+ {
+ static OverloadViewer()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(OverloadViewer),
+ new FrameworkPropertyMetadata(typeof(OverloadViewer)));
+ }
+
+ /// <summary>
+ /// The text property.
+ /// </summary>
+ public static readonly DependencyProperty TextProperty =
+ DependencyProperty.Register("Text", typeof(string), typeof(OverloadViewer));
+
+ /// <summary>
+ /// Gets/Sets the text between the Up and Down buttons.
+ /// </summary>
+ public string Text {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ /// <inheritdoc/>
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ Button upButton = (Button)this.Template.FindName("PART_UP", this);
+ upButton.Click += (sender, e) => {
+ e.Handled = true;
+ ChangeIndex(-1);
+ };
+
+ Button downButton = (Button)this.Template.FindName("PART_DOWN", this);
+ downButton.Click += (sender, e) => {
+ e.Handled = true;
+ ChangeIndex(+1);
+ };
+ }
+
+ /// <summary>
+ /// The ItemProvider property.
+ /// </summary>
+ public static readonly DependencyProperty ProviderProperty =
+ DependencyProperty.Register("Provider", typeof(IOverloadProvider), typeof(OverloadViewer));
+
+ /// <summary>
+ /// Gets/Sets the item provider.
+ /// </summary>
+ public IOverloadProvider Provider {
+ get { return (IOverloadProvider)GetValue(ProviderProperty); }
+ set { SetValue(ProviderProperty, value); }
+ }
+
+ /// <summary>
+ /// Changes the selected index.
+ /// </summary>
+ /// <param name="relativeIndexChange">The relative index change - usual values are +1 or -1.</param>
+ public void ChangeIndex(int relativeIndexChange)
+ {
+ IOverloadProvider p = this.Provider;
+ if (p != null) {
+ int newIndex = p.SelectedIndex + relativeIndexChange;
+ if (newIndex < 0)
+ newIndex = p.Count - 1;
+ if (newIndex >= p.Count)
+ newIndex = 0;
+ p.SelectedIndex = newIndex;
+ }
+ }
+ }
+
+ sealed class CollapseIfSingleOverloadConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return ((int)value < 2) ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}