Explorar o código

Variable grid of Expression Editor is now a tree grid, using dots to separate tree items.

Kenric Nugteren hai 5 meses
pai
achega
740273c7f8

+ 49 - 12
inabox.wpf/DynamicGrid/Editors/ExpressionEditor/ExpressionEditorWindow.xaml

@@ -10,6 +10,19 @@
         Loaded="ExpressionWindow_Loaded"
         x:Name="ExpressionWindow">
     <Window.Resources>
+        <local:ExpressionEditorVariableTemplateSelector x:Key="variableTemplateSelector"/>
+        <local:ExpressionEditorVariableTemplateItemStyleSelector x:Key="variableTemplateItemStyleSelector"/>
+
+        <Style x:Key="variableTemplateItemStyle" TargetType="{x:Type ListBoxItem}">
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
+                        <ContentPresenter/>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
         <Style x:Key="tooltipDescriptionStyle" TargetType="TextBlock">
             <Style.Triggers>
                 <DataTrigger Binding="{Binding Path=Description}" Value="">
@@ -17,6 +30,38 @@
                 </DataTrigger>
             </Style.Triggers>
         </Style>
+        <Style x:Key="VariableListStyle" TargetType="ListBox">
+            <Setter Property="ItemTemplate" Value="{StaticResource VariableExpanderTemplate}"/>
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding HasChildren}" Value="True"/>
+            </Style.Triggers>
+        </Style>
+        <DataTemplate x:Key="VariableTemplate" DataType="local:ExpressionEditorVariable">
+            <ContentControl MouseDoubleClick="Variable_Click"
+                            Tag="{Binding VariableName}">
+                <TextBlock Text="{Binding Display}"
+                           FontFamily="Consolas" FontSize="12"
+                           Padding="5"/>
+            </ContentControl>
+        </DataTemplate>
+        <DataTemplate x:Key="VariableExpanderTemplate" DataType="{x:Type local:ExpressionEditorVariable}">
+            <Border BorderThickness="1,0,0,1" BorderBrush="LightGray"
+                    Padding="5">
+                <Expander Header="{Binding Display}">
+                    <ListBox ItemsSource="{Binding ChildVariables}"
+                             ItemTemplateSelector="{StaticResource variableTemplateSelector}"
+                             ItemContainerStyleSelector="{StaticResource variableTemplateItemStyleSelector}"
+                             HorizontalContentAlignment="Stretch"
+                             BorderThickness="0">
+                        <ListBox.Template>
+                            <ControlTemplate>
+                                <ItemsPresenter/>
+                            </ControlTemplate>
+                        </ListBox.Template>
+                    </ListBox>
+                </Expander>
+            </Border>
+        </DataTemplate>
     </Window.Resources>
     <Grid Margin="5">
         <Grid.ColumnDefinitions>
@@ -47,7 +92,7 @@
                             <ItemsControl ItemsSource="{x:Static local:ExpressionEditorWindow.FunctionTemplates}">
                                 <ItemsControl.ItemTemplate>
                                     <DataTemplate>
-                                        <Border BorderThickness="1,0,0,1" BorderBrush="WhiteSmoke"
+                                        <Border BorderThickness="1,0,0,1" BorderBrush="LightGray"
                                                 Padding="5">
                                             <Expander Header="{Binding Item1}">
                                                 <ListBox ItemsSource="{Binding Item2}"
@@ -93,22 +138,14 @@
                         <Expander Header="Variables" IsExpanded="True">
                             <ListBox ItemsSource="{Binding Path=Variables,ElementName=ExpressionWindow}"
                                      HorizontalContentAlignment="Stretch"
-                                     BorderThickness="0">
+                                     BorderThickness="0"
+                                     ItemTemplateSelector="{StaticResource variableTemplateSelector}"
+                                     ItemContainerStyleSelector="{StaticResource variableTemplateItemStyleSelector}">
                                 <ListBox.Template>
                                     <ControlTemplate>
                                         <ItemsPresenter/>
                                     </ControlTemplate>
                                 </ListBox.Template>
-                                <ListBox.ItemTemplate>
-                                    <DataTemplate DataType="{x:Type system:String}">
-                                        <ContentControl MouseDoubleClick="Variable_Click"
-                                                        Tag="{Binding}">
-                                            <TextBlock Text="{Binding}"
-                                                       FontFamily="Consolas" FontSize="12"
-                                                       Padding="5"/>
-                                        </ContentControl>
-                                    </DataTemplate>
-                                </ListBox.ItemTemplate>
                             </ListBox>
                         </Expander>
                     </Border>

+ 311 - 212
inabox.wpf/DynamicGrid/Editors/ExpressionEditor/ExpressionEditorWindow.xaml.cs

@@ -16,273 +16,372 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Shapes;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+public class ExpressionEditorVariable(string variableName, string display, List<ExpressionEditorVariable> children)
+{
+    public string VariableName { get; } = variableName;
+
+    public string Display { get; } = display;
+
+    public List<ExpressionEditorVariable> ChildVariables { get; } = children;
+
+    public bool HasChildren => ChildVariables.Count > 0;
+}
+
+public class ExpressionEditorVariableTemplateItemStyleSelector : StyleSelector
+{
+    public override Style? SelectStyle(object item, DependencyObject container)
+    {
+        if(item is ExpressionEditorVariable variable && container is FrameworkElement element && variable.HasChildren)
+        {
+            return element.FindResource("variableTemplateItemStyle") as Style;
+        }
+        return base.SelectStyle(item, container);
+    }
+}
+public class ExpressionEditorVariableTemplateSelector : DataTemplateSelector
 {
-    /// <summary>
-    /// Interaction logic for ExpressionEditorWindow.xaml
-    /// </summary>
-    public partial class ExpressionEditorWindow : Window
+    public override DataTemplate? SelectTemplate(object item, DependencyObject container)
     {
-        public static List<Tuple<string, List<FunctionTemplate>>> FunctionTemplates = new();
+        if(item is ExpressionEditorVariable variable && container is FrameworkElement element)
+        {
+            if (variable.HasChildren)
+            {
+                return element.FindResource("VariableExpanderTemplate") as DataTemplate;
+            }
+            else
+            {
+                return element.FindResource("VariableTemplate") as DataTemplate;
+            }
+        }
+        return base.SelectTemplate(item, container);
+    }
+}
+
+/// <summary>
+/// Interaction logic for ExpressionEditorWindow.xaml
+/// </summary>
+public partial class ExpressionEditorWindow : Window
+{
+    public static List<Tuple<string, List<FunctionTemplate>>> FunctionTemplates = new();
 
-        public IEnumerable<string> Variables { get; private set; }
+    public IEnumerable<ExpressionEditorVariable> Variables { get; private set; }
 
-        public string Expression
+    public string Expression
+    {
+        get => Editor.Text;
+        set => Editor.Text = value;
+    }
+
+    private static void RegisterFunctionTemplate(string group, string name, string? description, List<IFunctionParameter> parameters)
+    {
+        var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
+        if(groupList is null)
         {
-            get => Editor.Text;
-            set => Editor.Text = value;
+            groupList = new List<FunctionTemplate>();
+            FunctionTemplates.Add(new(group, groupList));
         }
 
-        private static void RegisterFunctionTemplate(string group, string name, string? description, List<IFunctionParameter> parameters)
+        groupList.Add(new FunctionTemplate
         {
-            var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
-            if(groupList is null)
+            Name = name,
+            Parameters = parameters,
+            Description = description ?? ""
+        });
+    }
+    private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
+        => RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
+        {
+            var parts = x.Split(' ');
+            if (parts.Length == 1)
             {
-                groupList = new List<FunctionTemplate>();
-                FunctionTemplates.Add(new(group, groupList));
+                return new FunctionParameter(parts[0], "");
             }
-
-            groupList.Add(new FunctionTemplate
+            return new FunctionParameter(parts[1], parts[0]);
+        }).ToList<IFunctionParameter>());
+    
+    private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
+        => RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
+        {
+            var parts = x.Split(' ');
+            if (parts.Length == 1)
             {
-                Name = name,
-                Parameters = parameters,
-                Description = description ?? ""
-            });
+                return new FunctionParameter(parts[0], "");
+            }
+            return new FunctionParameter(parts[1], parts[0]);
+        }).ToList<IFunctionParameter>());
+
+    static ExpressionEditorWindow()
+    {
+        RegisterFunctionTemplate("Math", "Abs", "double x");
+        RegisterFunctionTemplate("Math", "Acos", "double x");
+        RegisterFunctionTemplate("Math", "Asin", "double x");
+        RegisterFunctionTemplate("Math", "Atan", "double x");
+        RegisterFunctionTemplate("Math", "Ceiling", "double x");
+        RegisterFunctionTemplate("Math", "Cos", "double x");
+        RegisterFunctionTemplate("Math", "Count", "...");
+        RegisterFunctionTemplate("Math", "Exp", "double exp");
+        RegisterFunctionTemplate("Math", "Floor", "double x");
+        RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
+        RegisterFunctionTemplate("Math", "Log10", "double x");
+        RegisterFunctionTemplate("Math", "Log", "double x", "double base");
+        RegisterFunctionTemplate("Math", "Max", "double x", "double y");
+        RegisterFunctionTemplate("Math", "Min", "double x", "double y");
+        RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
+        RegisterFunctionTemplate("Math", "Random");
+        RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
+        RegisterFunctionTemplate("Math", "Sign", "double x");
+        RegisterFunctionTemplate("Math", "Sin", "double x");
+        RegisterFunctionTemplate("Math", "Sqrt", "double x");
+        RegisterFunctionTemplate("Math", "Tan", "double x");
+        RegisterFunctionTemplate("Math", "Truncate", "double x");
+
+        RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
+        RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");
+
+        RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
+        RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
+        RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
+        RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
+        RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");
+
+        RegisterFunctionTemplate("String", "Contains", "string text", "str value");
+        RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
+        RegisterFunctionTemplate("String", "Length", "string value");
+        RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
+        RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
+        RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
+        RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");
+
+        RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
+        RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
+        RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
+        RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
+        RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
+        RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
+        RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
+        RegisterFunctionTemplate("Date", "YearOf", "date date");
+        RegisterFunctionTemplate("Date", "MonthOf", "date date");
+        RegisterFunctionTemplate("Date", "DayOf", "date date");
+        RegisterFunctionTemplate("Date", "HourOf", "date date");
+        RegisterFunctionTemplate("Date", "MinuteOf", "date date");
+        RegisterFunctionTemplate("Date", "SecondOf", "date date");
+        RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
+        RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
+        RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
+        RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
+        RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
+        RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");
+
+        RegisterFunctionTemplate("Conversion", "Date", "value");
+        RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
+        RegisterFunctionTemplate("Conversion", "Decimal", "value");
+        RegisterFunctionTemplate("Conversion", "Double", "value");
+        RegisterFunctionTemplate("Conversion", "Integer", "value");
+        RegisterFunctionTemplate("Conversion", "Long", "value");
+        RegisterFunctionTemplate("Conversion", "String", "value");
+
+        foreach(var function in CoreExpression.Functions)
+        {
+            RegisterFunctionTemplateDesc(
+                function.Group,
+                function.Name,
+                function.Description,
+                function.Parameters);
         }
-        private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
-            => RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
+    }
+
+    public ExpressionEditorWindow(IEnumerable<string> variables)
+    {
+        var parentColumns = new Dictionary<string, ExpressionEditorVariable>();
+        var items = new List<ExpressionEditorVariable>();
+        foreach (var variable in variables)
+        {
+            var display = variable;
+            var lastDot = variable.LastIndexOf('.');
+            if(lastDot != -1)
             {
-                var parts = x.Split(' ');
-                if (parts.Length == 1)
+                display = variable[(lastDot + 1)..];
+            }
+            var item = new ExpressionEditorVariable(variable, display, []);
+
+            var props = variable.Split('.');
+
+            var vars = items;
+            string? parent = null;
+            for (int i = 0; i < props.Length - 1; ++i)
+            {
+                if (parent is null)
                 {
-                    return new FunctionParameter(parts[0], "");
+                    parent = props[i];
                 }
-                return new FunctionParameter(parts[1], parts[0]);
-            }).ToList<IFunctionParameter>());
-        
-        private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
-            => RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
-            {
-                var parts = x.Split(' ');
-                if (parts.Length == 1)
+                else
                 {
-                    return new FunctionParameter(parts[0], "");
+                    parent = $"{parent}.{props[i]}";
                 }
-                return new FunctionParameter(parts[1], parts[0]);
-            }).ToList<IFunctionParameter>());
-
-        static ExpressionEditorWindow()
-        {
-            RegisterFunctionTemplate("Math", "Abs", "double x");
-            RegisterFunctionTemplate("Math", "Acos", "double x");
-            RegisterFunctionTemplate("Math", "Asin", "double x");
-            RegisterFunctionTemplate("Math", "Atan", "double x");
-            RegisterFunctionTemplate("Math", "Ceiling", "double x");
-            RegisterFunctionTemplate("Math", "Cos", "double x");
-            RegisterFunctionTemplate("Math", "Count", "...");
-            RegisterFunctionTemplate("Math", "Exp", "double exp");
-            RegisterFunctionTemplate("Math", "Floor", "double x");
-            RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
-            RegisterFunctionTemplate("Math", "Log10", "double x");
-            RegisterFunctionTemplate("Math", "Log", "double x", "double base");
-            RegisterFunctionTemplate("Math", "Max", "double x", "double y");
-            RegisterFunctionTemplate("Math", "Min", "double x", "double y");
-            RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
-            RegisterFunctionTemplate("Math", "Random");
-            RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
-            RegisterFunctionTemplate("Math", "Sign", "double x");
-            RegisterFunctionTemplate("Math", "Sin", "double x");
-            RegisterFunctionTemplate("Math", "Sqrt", "double x");
-            RegisterFunctionTemplate("Math", "Tan", "double x");
-            RegisterFunctionTemplate("Math", "Truncate", "double x");
-
-            RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
-            RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");
-
-            RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
-            RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
-            RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
-            RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
-            RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");
-
-            RegisterFunctionTemplate("String", "Contains", "string text", "str value");
-            RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
-            RegisterFunctionTemplate("String", "Length", "string value");
-            RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
-            RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
-            RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
-            RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");
-
-            RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
-            RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
-            RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
-            RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
-            RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
-            RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
-            RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
-            RegisterFunctionTemplate("Date", "YearOf", "date date");
-            RegisterFunctionTemplate("Date", "MonthOf", "date date");
-            RegisterFunctionTemplate("Date", "DayOf", "date date");
-            RegisterFunctionTemplate("Date", "HourOf", "date date");
-            RegisterFunctionTemplate("Date", "MinuteOf", "date date");
-            RegisterFunctionTemplate("Date", "SecondOf", "date date");
-            RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
-            RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
-            RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
-            RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
-            RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
-            RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");
-
-            RegisterFunctionTemplate("Conversion", "Date", "value");
-            RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
-            RegisterFunctionTemplate("Conversion", "Decimal", "value");
-            RegisterFunctionTemplate("Conversion", "Double", "value");
-            RegisterFunctionTemplate("Conversion", "Integer", "value");
-            RegisterFunctionTemplate("Conversion", "Long", "value");
-            RegisterFunctionTemplate("Conversion", "String", "value");
-
-            foreach(var function in CoreExpression.Functions)
-            {
-                RegisterFunctionTemplateDesc(
-                    function.Group,
-                    function.Name,
-                    function.Description,
-                    function.Parameters);
+                if(!parentColumns.TryGetValue(parent, out var parentVar))
+                {
+                    parentVar = new ExpressionEditorVariable(parent, props[i], []);
+                    parentColumns.Add(parent, parentVar);
+                    vars.Add(parentVar);
+                }
+                vars = parentVar.ChildVariables;
             }
+            vars.Add(item);
         }
+        SortVariables(items);
 
-        public ExpressionEditorWindow(IEnumerable<string> variables)
-        {
-            Variables = variables;
-
-            InitializeComponent();
-        }
+        Variables = items;
 
-        private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e)
-        {
-            Editor.Focus();
-        }
+        InitializeComponent();
+    }
 
-        private void InsertText(string text)
-        {
-            var newCaret = Editor.CaretIndex + text.Length;
-            Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
-            Editor.CaretIndex = newCaret;
-        }
-        private void AppendText(string text)
-        {
-            var oldCaret = Editor.CaretIndex;
-            Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
-            Editor.CaretIndex = oldCaret;
-        }
-        private void DeleteText(int start, int length)
+    private void SortVariables(List<ExpressionEditorVariable> vars)
+    {
+        vars.Sort((a, b) =>
         {
-            var oldCaret = Editor.CaretIndex;
-            Editor.Text = Editor.Text.Remove(start, length);
-            if(oldCaret >= start && oldCaret < start + length)
+            if (a.HasChildren && !b.HasChildren)
             {
-                Editor.CaretIndex = start;
+                return -1;
             }
-            else
+            else if (!a.HasChildren && b.HasChildren)
             {
-                Editor.CaretIndex = oldCaret - length;
+                return 1;
             }
+            return a.Display.CompareTo(b.Display);
+        });
+        foreach(var v in vars)
+        {
+            SortVariables(v.ChildVariables);
         }
+    }
 
-        private static Regex tagRegex = new("\\{\\S+\\}");
-        private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft);
+    private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e)
+    {
+        Editor.Focus();
+    }
 
-        private bool DeletePlaceholder()
+    private void InsertText(string text)
+    {
+        var newCaret = Editor.CaretIndex + text.Length;
+        Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
+        Editor.CaretIndex = newCaret;
+    }
+    private void AppendText(string text)
+    {
+        var oldCaret = Editor.CaretIndex;
+        Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
+        Editor.CaretIndex = oldCaret;
+    }
+    private void DeleteText(int start, int length)
+    {
+        var oldCaret = Editor.CaretIndex;
+        Editor.Text = Editor.Text.Remove(start, length);
+        if(oldCaret >= start && oldCaret < start + length)
         {
-            if (Editor.CaretIndex >= Editor.Text.Length) return false;
+            Editor.CaretIndex = start;
+        }
+        else
+        {
+            Editor.CaretIndex = oldCaret - length;
+        }
+    }
 
-            var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex);
-            if (tagStart == -1) return false;
+    private static Regex tagRegex = new("\\{\\S+\\}");
+    private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft);
 
-            var tagMatch = tagRegex.Match(Editor.Text, tagStart);
-            if (!tagMatch.Success) return false;
+    private bool DeletePlaceholder()
+    {
+        if (Editor.CaretIndex >= Editor.Text.Length) return false;
 
-            var startIndex = tagMatch.Index;
+        var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex);
+        if (tagStart == -1) return false;
 
-            if (startIndex != tagStart) return false;
+        var tagMatch = tagRegex.Match(Editor.Text, tagStart);
+        if (!tagMatch.Success) return false;
 
-            var length = tagMatch.Length;
+        var startIndex = tagMatch.Index;
 
-            if (Editor.CaretIndex >= startIndex + length) return false;
+        if (startIndex != tagStart) return false;
 
-            DeleteText(startIndex, length);
+        var length = tagMatch.Length;
 
-            return true;
-        }
-        private void JumpPlaceholderBackwards()
-        {
-            if (Editor.CaretIndex == 0) return;
+        if (Editor.CaretIndex >= startIndex + length) return false;
 
-            var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1);
-            if (!tagMatch.Success) return;
+        DeleteText(startIndex, length);
 
-            Editor.CaretIndex = tagMatch.Index;
-        }
-        private void JumpPlaceholder(bool allowStart = false)
-        {
-            if (Editor.CaretIndex >= Editor.Text.Length) return;
+        return true;
+    }
+    private void JumpPlaceholderBackwards()
+    {
+        if (Editor.CaretIndex == 0) return;
 
-            var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1;
+        var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1);
+        if (!tagMatch.Success) return;
 
-            var tagMatch = tagRegex.Match(Editor.Text, start);
-            if (!tagMatch.Success) return;
+        Editor.CaretIndex = tagMatch.Index;
+    }
+    private void JumpPlaceholder(bool allowStart = false)
+    {
+        if (Editor.CaretIndex >= Editor.Text.Length) return;
 
-            Editor.CaretIndex = tagMatch.Index;
-        }
+        var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1;
 
-        private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e)
-        {
-            if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return;
+        var tagMatch = tagRegex.Match(Editor.Text, start);
+        if (!tagMatch.Success) return;
 
-            var placeholder = DeletePlaceholder();
-            InsertText($"{template.Name}(");
-            AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
-            if (placeholder)
-                JumpPlaceholder(true);
-        }
+        Editor.CaretIndex = tagMatch.Index;
+    }
 
-        private void Variable_Click(object sender, MouseButtonEventArgs e)
-        {
-            if ((sender as FrameworkElement)!.Tag is not string str) return;
+    private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e)
+    {
+        if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return;
 
-            DeletePlaceholder();
-            InsertText($"[{str}]");
-        }
+        var placeholder = DeletePlaceholder();
+        InsertText($"{template.Name}(");
+        AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
+        if (placeholder)
+            JumpPlaceholder(true);
+    }
 
-        private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
-        {
-            e.Handled = true;
-        }
+    private void Variable_Click(object sender, MouseButtonEventArgs e)
+    {
+        if ((sender as FrameworkElement)!.Tag is not string str) return;
 
-        private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e)
-        {
-            DeletePlaceholder();
-        }
+        DeletePlaceholder();
+        InsertText($"[{str}]");
+    }
+
+    private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+    {
+        e.Handled = true;
+    }
+
+    private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e)
+    {
+        DeletePlaceholder();
+    }
 
-        private void Editor_KeyDown(object sender, KeyEventArgs e)
+    private void Editor_KeyDown(object sender, KeyEventArgs e)
+    {
+        if(e.Key == Key.Tab)
         {
-            if(e.Key == Key.Tab)
+            if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
             {
-                if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
-                {
-                    JumpPlaceholderBackwards();
-                }
-                else
-                {
-                    JumpPlaceholder();
-                }
-                e.Handled = true;
+                JumpPlaceholderBackwards();
             }
+            else
+            {
+                JumpPlaceholder();
+            }
+            e.Handled = true;
         }
+    }
 
-        private void OK_Click(object sender, RoutedEventArgs e)
-        {
-            DialogResult = true;
-        }
+    private void OK_Click(object sender, RoutedEventArgs e)
+    {
+        DialogResult = true;
     }
+
 }