Sicherung
This commit is contained in:
20
.gitremotes
20
.gitremotes
@@ -1,10 +1,10 @@
|
|||||||
FSI.Lib //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/fsi.lib.git (fetch)
|
FSI.Lib r:/fsi/fsi.lib.git (fetch)
|
||||||
FSI.Lib //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/fsi.lib.git (push)
|
FSI.Lib r:/fsi/fsi.lib.git (push)
|
||||||
NHotkey //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/nhotkey.git (fetch)
|
NHotkey r:/fsi/nhotkey.git (fetch)
|
||||||
NHotkey //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/nhotkey.git (push)
|
NHotkey r:/fsi/nhotkey.git (push)
|
||||||
NotifyIconWpf //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/notifyiconwpf.git (fetch)
|
NotifyIconWpf r:/fsi/notifyiconwpf.git (fetch)
|
||||||
NotifyIconWpf //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/notifyiconwpf.git (push)
|
NotifyIconWpf r:/fsi/notifyiconwpf.git (push)
|
||||||
RadialMenu //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/radialmenu.git (fetch)
|
RadialMenu r:/fsi/radialmenu.git (fetch)
|
||||||
RadialMenu //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/radialmenu.git (push)
|
RadialMenu r:/fsi/radialmenu.git (push)
|
||||||
origin //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/fsi.tools.git (fetch)
|
origin r:/fsi/fsi.bt.tools.git (fetch)
|
||||||
origin //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/fsi.tools.git (push)
|
origin r:/fsi/fsi.bt.tools.git (push)
|
||||||
|
|||||||
32
AutoCompleteTextBox/AutoCompleteTextBox.csproj
Normal file
32
AutoCompleteTextBox/AutoCompleteTextBox.csproj
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
|
<PropertyGroup>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<TargetFrameworks>net48;netcoreapp3.1;net6.0-windows</TargetFrameworks>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<Version>1.6.0.0</Version>
|
||||||
|
<PackageProjectUrl>https://github.com/quicoli/WPF-AutoComplete-TextBox</PackageProjectUrl>
|
||||||
|
<RepositoryType />
|
||||||
|
<RepositoryUrl>https://github.com/quicoli/WPF-AutoComplete-TextBox</RepositoryUrl>
|
||||||
|
<PackageTags>wpf, autocomplete, usercontrol</PackageTags>
|
||||||
|
<PackageIconUrl>https://github.com/quicoli/WPF-AutoComplete-TextBox/blob/develop/AutoCompleteTextBox/Logo/AutoCompleteTextBox.ico?raw=true</PackageIconUrl>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<ApplicationIcon>AutoCompleteTextBox.ico</ApplicationIcon>
|
||||||
|
<PackageReleaseNotes>
|
||||||
|
Better support for keyboard focus
|
||||||
|
</PackageReleaseNotes>
|
||||||
|
<Description>An auto complete textbox and combo box for WPF</Description>
|
||||||
|
<PackageIcon>AutoCompleteTextBox.png</PackageIcon>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\..\README.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<None Include="..\Logo\AutoCompleteTextBox.png">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
BIN
AutoCompleteTextBox/AutoCompleteTextBox.ico
Normal file
BIN
AutoCompleteTextBox/AutoCompleteTextBox.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 492 B |
49
AutoCompleteTextBox/BindingEvaluator.cs
Normal file
49
AutoCompleteTextBox/BindingEvaluator.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox
|
||||||
|
{
|
||||||
|
public class BindingEvaluator : FrameworkElement
|
||||||
|
{
|
||||||
|
|
||||||
|
#region "Fields"
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(BindingEvaluator), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Constructors"
|
||||||
|
|
||||||
|
public BindingEvaluator(Binding binding)
|
||||||
|
{
|
||||||
|
ValueBinding = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Properties"
|
||||||
|
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get => (string)GetValue(ValueProperty);
|
||||||
|
|
||||||
|
set => SetValue(ValueProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding ValueBinding { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Methods"
|
||||||
|
|
||||||
|
public string Evaluate(object dataItem)
|
||||||
|
{
|
||||||
|
DataContext = dataItem;
|
||||||
|
SetBinding(ValueProperty, ValueBinding);
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
612
AutoCompleteTextBox/Editors/AutoCompleteComboBox.cs
Normal file
612
AutoCompleteTextBox/Editors/AutoCompleteComboBox.cs
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Editors
|
||||||
|
{
|
||||||
|
[TemplatePart(Name = PartEditor, Type = typeof(TextBox))]
|
||||||
|
[TemplatePart(Name = PartPopup, Type = typeof(Popup))]
|
||||||
|
[TemplatePart(Name = PartSelector, Type = typeof(Selector))]
|
||||||
|
[TemplatePart(Name = PartExpander, Type = typeof(Expander))]
|
||||||
|
public class AutoCompleteComboBox : Control
|
||||||
|
{
|
||||||
|
|
||||||
|
#region "Fields"
|
||||||
|
|
||||||
|
public const string PartEditor = "PART_Editor";
|
||||||
|
public const string PartPopup = "PART_Popup";
|
||||||
|
|
||||||
|
public const string PartSelector = "PART_Selector";
|
||||||
|
public const string PartExpander = "PART_Expander";
|
||||||
|
public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(200));
|
||||||
|
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
public static readonly DependencyProperty IconPlacementProperty = DependencyProperty.Register("IconPlacement", typeof(IconPlacement), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(IconPlacement.Left));
|
||||||
|
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty IconVisibilityProperty = DependencyProperty.Register("IconVisibility", typeof(Visibility), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(Visibility.Visible));
|
||||||
|
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register("IsLoading", typeof(bool), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(AutoCompleteComboBox));
|
||||||
|
public static readonly DependencyProperty LoadingContentProperty = DependencyProperty.Register("LoadingContent", typeof(object), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty ProviderProperty = DependencyProperty.Register("Provider", typeof(IComboSuggestionProvider), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(null, OnSelectedItemChanged));
|
||||||
|
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty, propertyChangedCallback: null, coerceValueCallback: null, isAnimationProhibited: false, defaultUpdateSourceTrigger: UpdateSourceTrigger.LostFocus, flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||||
|
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(0));
|
||||||
|
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.Register("CharacterCasing", typeof(CharacterCasing), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(CharacterCasing.Normal));
|
||||||
|
public static readonly DependencyProperty MaxPopUpHeightProperty = DependencyProperty.Register("MaxPopUpHeight", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(600));
|
||||||
|
public static readonly DependencyProperty MaxPopUpWidthProperty = DependencyProperty.Register("MaxPopUpWidth", typeof(int), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(2000));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(string), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty SuggestionBackgroundProperty = DependencyProperty.Register("SuggestionBackground", typeof(Brush), typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(Brushes.White));
|
||||||
|
private bool _isUpdatingText;
|
||||||
|
private bool _selectionCancelled;
|
||||||
|
|
||||||
|
private SuggestionsAdapter _suggestionsAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Constructors"
|
||||||
|
|
||||||
|
static AutoCompleteComboBox()
|
||||||
|
{
|
||||||
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteComboBox), new FrameworkPropertyMetadata(typeof(AutoCompleteComboBox)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Properties"
|
||||||
|
|
||||||
|
|
||||||
|
public int MaxPopupHeight
|
||||||
|
{
|
||||||
|
get => (int)GetValue(MaxPopUpHeightProperty);
|
||||||
|
set => SetValue(MaxPopUpHeightProperty, value);
|
||||||
|
}
|
||||||
|
public int MaxPopupWidth
|
||||||
|
{
|
||||||
|
get => (int)GetValue(MaxPopUpWidthProperty);
|
||||||
|
set => SetValue(MaxPopUpWidthProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BindingEvaluator BindingEvaluator { get; set; }
|
||||||
|
|
||||||
|
public CharacterCasing CharacterCasing
|
||||||
|
{
|
||||||
|
get => (CharacterCasing)GetValue(CharacterCasingProperty);
|
||||||
|
set => SetValue(CharacterCasingProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxLength
|
||||||
|
{
|
||||||
|
get => (int)GetValue(MaxLengthProperty);
|
||||||
|
set => SetValue(MaxLengthProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Delay
|
||||||
|
{
|
||||||
|
get => (int)GetValue(DelayProperty);
|
||||||
|
|
||||||
|
set => SetValue(DelayProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DisplayMember
|
||||||
|
{
|
||||||
|
get => (string)GetValue(DisplayMemberProperty);
|
||||||
|
|
||||||
|
set => SetValue(DisplayMemberProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextBox Editor { get; set; }
|
||||||
|
public Expander Expander { get; set; }
|
||||||
|
|
||||||
|
public DispatcherTimer FetchTimer { get; set; }
|
||||||
|
|
||||||
|
public string Filter
|
||||||
|
{
|
||||||
|
get => (string)GetValue(FilterProperty);
|
||||||
|
|
||||||
|
set => SetValue(FilterProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Icon
|
||||||
|
{
|
||||||
|
get => GetValue(IconProperty);
|
||||||
|
|
||||||
|
set => SetValue(IconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPlacement IconPlacement
|
||||||
|
{
|
||||||
|
get => (IconPlacement)GetValue(IconPlacementProperty);
|
||||||
|
|
||||||
|
set => SetValue(IconPlacementProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Visibility IconVisibility
|
||||||
|
{
|
||||||
|
get => (Visibility)GetValue(IconVisibilityProperty);
|
||||||
|
|
||||||
|
set => SetValue(IconVisibilityProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDropDownOpen
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.Expander.IsExpanded = value;
|
||||||
|
SetValue(IsDropDownOpenProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLoading
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsLoadingProperty);
|
||||||
|
|
||||||
|
set => SetValue(IsLoadingProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsReadOnlyProperty);
|
||||||
|
|
||||||
|
set => SetValue(IsReadOnlyProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Selector ItemsSelector { get; set; }
|
||||||
|
|
||||||
|
public DataTemplate ItemTemplate
|
||||||
|
{
|
||||||
|
get => (DataTemplate)GetValue(ItemTemplateProperty);
|
||||||
|
|
||||||
|
set => SetValue(ItemTemplateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTemplateSelector ItemTemplateSelector
|
||||||
|
{
|
||||||
|
get => ((DataTemplateSelector)(GetValue(ItemTemplateSelectorProperty)));
|
||||||
|
set => SetValue(ItemTemplateSelectorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object LoadingContent
|
||||||
|
{
|
||||||
|
get => GetValue(LoadingContentProperty);
|
||||||
|
|
||||||
|
set => SetValue(LoadingContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popup Popup { get; set; }
|
||||||
|
|
||||||
|
public IComboSuggestionProvider Provider
|
||||||
|
{
|
||||||
|
get => (IComboSuggestionProvider)GetValue(ProviderProperty);
|
||||||
|
|
||||||
|
set => SetValue(ProviderProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SelectedItem
|
||||||
|
{
|
||||||
|
get => GetValue(SelectedItemProperty);
|
||||||
|
|
||||||
|
set => SetValue(SelectedItemProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionAdapter SelectionAdapter { get; set; }
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => (string)GetValue(TextProperty);
|
||||||
|
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Watermark
|
||||||
|
{
|
||||||
|
get => (string)GetValue(WatermarkProperty);
|
||||||
|
|
||||||
|
set => SetValue(WatermarkProperty, value);
|
||||||
|
}
|
||||||
|
public Brush SuggestionBackground
|
||||||
|
{
|
||||||
|
get => (Brush)GetValue(SuggestionBackgroundProperty);
|
||||||
|
|
||||||
|
set => SetValue(SuggestionBackgroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Methods"
|
||||||
|
|
||||||
|
public static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
AutoCompleteComboBox act = null;
|
||||||
|
act = d as AutoCompleteComboBox;
|
||||||
|
if (act != null)
|
||||||
|
{
|
||||||
|
if (act.Editor != null & !act._isUpdatingText)
|
||||||
|
{
|
||||||
|
act._isUpdatingText = true;
|
||||||
|
act.Editor.Text = act.BindingEvaluator.Evaluate(e.NewValue);
|
||||||
|
act._isUpdatingText = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScrollToSelectedItem()
|
||||||
|
{
|
||||||
|
if (ItemsSelector is ListBox listBox && listBox.SelectedItem != null)
|
||||||
|
listBox.ScrollIntoView(listBox.SelectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding){
|
||||||
|
var res = base.SetBinding(dp, binding);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public new BindingExpressionBase SetBinding(DependencyProperty dp, String path) {
|
||||||
|
var res = base.SetBinding(dp, path);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public new void ClearValue(DependencyPropertyKey key) {
|
||||||
|
base.ClearValue(key);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
}
|
||||||
|
public new void ClearValue(DependencyProperty dp) {
|
||||||
|
base.ClearValue(dp);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
}
|
||||||
|
private void CheckForParentTextBindingChange(bool force=false) {
|
||||||
|
var CurrentBindingMode = BindingOperations.GetBinding(this, TextProperty)?.UpdateSourceTrigger ?? UpdateSourceTrigger.Default;
|
||||||
|
if (CurrentBindingMode != UpdateSourceTrigger.PropertyChanged)//preventing going any less frequent than property changed
|
||||||
|
CurrentBindingMode = UpdateSourceTrigger.Default;
|
||||||
|
|
||||||
|
|
||||||
|
if (CurrentBindingMode == CurrentTextboxTextBindingUpdateMode && force == false)
|
||||||
|
return;
|
||||||
|
var binding = new Binding {
|
||||||
|
Mode = BindingMode.TwoWay,
|
||||||
|
UpdateSourceTrigger = CurrentBindingMode,
|
||||||
|
Path = new PropertyPath(nameof(Text)),
|
||||||
|
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
|
||||||
|
};
|
||||||
|
CurrentTextboxTextBindingUpdateMode = CurrentBindingMode;
|
||||||
|
Editor?.SetBinding(TextBox.TextProperty, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UpdateSourceTrigger CurrentTextboxTextBindingUpdateMode;
|
||||||
|
|
||||||
|
public override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
Editor = Template.FindName(PartEditor, this) as TextBox;
|
||||||
|
Popup = Template.FindName(PartPopup, this) as Popup;
|
||||||
|
ItemsSelector = Template.FindName(PartSelector, this) as Selector;
|
||||||
|
Expander = Template.FindName(PartExpander, this) as Expander;
|
||||||
|
|
||||||
|
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||||
|
|
||||||
|
if (Editor != null)
|
||||||
|
{
|
||||||
|
Editor.TextChanged += OnEditorTextChanged;
|
||||||
|
Editor.PreviewKeyDown += OnEditorKeyDown;
|
||||||
|
Editor.LostFocus += OnEditorLostFocus;
|
||||||
|
CheckForParentTextBindingChange(true);
|
||||||
|
|
||||||
|
if (SelectedItem != null)
|
||||||
|
{
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = BindingEvaluator.Evaluate(SelectedItem);
|
||||||
|
_isUpdatingText = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (Expander != null)
|
||||||
|
{
|
||||||
|
Expander.IsExpanded = false;
|
||||||
|
Expander.Collapsed += Expander_Expanded;
|
||||||
|
Expander.Expanded += Expander_Expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
GotFocus += AutoCompleteComboBox_GotFocus;
|
||||||
|
|
||||||
|
if (Popup != null)
|
||||||
|
{
|
||||||
|
Popup.StaysOpen = false;
|
||||||
|
Popup.Opened += OnPopupOpened;
|
||||||
|
Popup.Closed += OnPopupClosed;
|
||||||
|
}
|
||||||
|
if (ItemsSelector != null)
|
||||||
|
{
|
||||||
|
SelectionAdapter = new SelectionAdapter(ItemsSelector);
|
||||||
|
SelectionAdapter.Commit += OnSelectionAdapterCommit;
|
||||||
|
SelectionAdapter.Cancel += OnSelectionAdapterCancel;
|
||||||
|
SelectionAdapter.SelectionChanged += OnSelectionAdapterSelectionChanged;
|
||||||
|
ItemsSelector.PreviewMouseDown += ItemsSelector_PreviewMouseDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Expander_Expanded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
this.IsDropDownOpen = Expander.IsExpanded;
|
||||||
|
if (!this.IsDropDownOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_suggestionsAdapter == null)
|
||||||
|
{
|
||||||
|
_suggestionsAdapter = new SuggestionsAdapter(this);
|
||||||
|
}
|
||||||
|
if (SelectedItem != null || String.IsNullOrWhiteSpace(Editor.Text))
|
||||||
|
_suggestionsAdapter.ShowFullCollection();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemsSelector_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if ((e.OriginalSource as FrameworkElement)?.DataContext == null)
|
||||||
|
return;
|
||||||
|
if (!ItemsSelector.Items.Contains(((FrameworkElement)e.OriginalSource)?.DataContext))
|
||||||
|
return;
|
||||||
|
ItemsSelector.SelectedItem = ((FrameworkElement)e.OriginalSource)?.DataContext;
|
||||||
|
OnSelectionAdapterCommit(SelectionAdapter.EventCause.ItemClicked);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
private void AutoCompleteComboBox_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Editor?.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDisplayText(object dataItem)
|
||||||
|
{
|
||||||
|
if (BindingEvaluator == null)
|
||||||
|
{
|
||||||
|
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||||
|
}
|
||||||
|
if (dataItem == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(DisplayMember))
|
||||||
|
{
|
||||||
|
return dataItem.ToString();
|
||||||
|
}
|
||||||
|
return BindingEvaluator.Evaluate(dataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectionAdapter != null)
|
||||||
|
{
|
||||||
|
if (IsDropDownOpen)
|
||||||
|
SelectionAdapter.HandleKeyDown(e);
|
||||||
|
else
|
||||||
|
IsDropDownOpen = e.Key == Key.Down || e.Key == Key.Up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorLostFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsKeyboardFocusWithin)
|
||||||
|
{
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorTextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Text = Editor.Text;
|
||||||
|
if (_isUpdatingText)
|
||||||
|
return;
|
||||||
|
if (FetchTimer == null)
|
||||||
|
{
|
||||||
|
FetchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Delay) };
|
||||||
|
FetchTimer.Tick += OnFetchTimerTick;
|
||||||
|
}
|
||||||
|
FetchTimer.IsEnabled = false;
|
||||||
|
FetchTimer.Stop();
|
||||||
|
SetSelectedItem(null);
|
||||||
|
if (Editor.Text.Length > 0)
|
||||||
|
{
|
||||||
|
FetchTimer.IsEnabled = true;
|
||||||
|
FetchTimer.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFetchTimerTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
FetchTimer.IsEnabled = false;
|
||||||
|
FetchTimer.Stop();
|
||||||
|
if (Provider != null && ItemsSelector != null)
|
||||||
|
{
|
||||||
|
Filter = Editor.Text;
|
||||||
|
if (_suggestionsAdapter == null)
|
||||||
|
{
|
||||||
|
_suggestionsAdapter = new SuggestionsAdapter(this);
|
||||||
|
}
|
||||||
|
_suggestionsAdapter.GetSuggestions(Filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPopupClosed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!_selectionCancelled)
|
||||||
|
{
|
||||||
|
OnSelectionAdapterCommit(SelectionAdapter.EventCause.PopupClosed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPopupOpened(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_selectionCancelled = false;
|
||||||
|
ItemsSelector.SelectedItem = SelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<SelectionAdapter.PreSelectionAdapterFinishArgs> PreSelectionAdapterFinish;
|
||||||
|
private bool PreSelectionEventSomeoneHandled(SelectionAdapter.EventCause cause, bool is_cancel) {
|
||||||
|
if (PreSelectionAdapterFinish == null)
|
||||||
|
return false;
|
||||||
|
var args = new SelectionAdapter.PreSelectionAdapterFinishArgs { cause = cause, is_cancel = is_cancel };
|
||||||
|
PreSelectionAdapterFinish?.Invoke(this, args);
|
||||||
|
return args.handled;
|
||||||
|
|
||||||
|
}
|
||||||
|
private void OnSelectionAdapterCancel(SelectionAdapter.EventCause cause)
|
||||||
|
{
|
||||||
|
if (PreSelectionEventSomeoneHandled(cause, true))
|
||||||
|
return;
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = SelectedItem == null ? Filter : GetDisplayText(SelectedItem);
|
||||||
|
Editor.SelectionStart = Editor.Text.Length;
|
||||||
|
Editor.SelectionLength = 0;
|
||||||
|
_isUpdatingText = false;
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
_selectionCancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionAdapterCommit(SelectionAdapter.EventCause cause)
|
||||||
|
{
|
||||||
|
if (PreSelectionEventSomeoneHandled(cause, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ItemsSelector.SelectedItem != null)
|
||||||
|
{
|
||||||
|
SelectedItem = ItemsSelector.SelectedItem;
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = GetDisplayText(ItemsSelector.SelectedItem);
|
||||||
|
SetSelectedItem(ItemsSelector.SelectedItem);
|
||||||
|
_isUpdatingText = false;
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionAdapterSelectionChanged()
|
||||||
|
{
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = ItemsSelector.SelectedItem == null ? Filter : GetDisplayText(ItemsSelector.SelectedItem);
|
||||||
|
Editor.SelectionStart = Editor.Text.Length;
|
||||||
|
Editor.SelectionLength = 0;
|
||||||
|
ScrollToSelectedItem();
|
||||||
|
_isUpdatingText = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetSelectedItem(object item)
|
||||||
|
{
|
||||||
|
_isUpdatingText = true;
|
||||||
|
SelectedItem = item;
|
||||||
|
_isUpdatingText = false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Nested Types"
|
||||||
|
|
||||||
|
private class SuggestionsAdapter
|
||||||
|
{
|
||||||
|
|
||||||
|
#region "Fields"
|
||||||
|
|
||||||
|
private readonly AutoCompleteComboBox _actb;
|
||||||
|
|
||||||
|
private string _filter;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Constructors"
|
||||||
|
|
||||||
|
public SuggestionsAdapter(AutoCompleteComboBox actb)
|
||||||
|
{
|
||||||
|
_actb = actb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Methods"
|
||||||
|
|
||||||
|
public void GetSuggestions(string searchText)
|
||||||
|
{
|
||||||
|
_actb.IsLoading = true;
|
||||||
|
// Do not open drop down if control is not focused
|
||||||
|
if (_actb.IsKeyboardFocusWithin)
|
||||||
|
_actb.IsDropDownOpen = true;
|
||||||
|
_actb.ItemsSelector.ItemsSource = null;
|
||||||
|
ParameterizedThreadStart thInfo = GetSuggestionsAsync;
|
||||||
|
Thread th = new Thread(thInfo);
|
||||||
|
_filter = searchText;
|
||||||
|
th.Start(new object[] { searchText, _actb.Provider });
|
||||||
|
}
|
||||||
|
public void ShowFullCollection()
|
||||||
|
{
|
||||||
|
_filter = string.Empty;
|
||||||
|
_actb.IsLoading = true;
|
||||||
|
// Do not open drop down if control is not focused
|
||||||
|
if (_actb.IsKeyboardFocusWithin)
|
||||||
|
_actb.IsDropDownOpen = true;
|
||||||
|
_actb.ItemsSelector.ItemsSource = null;
|
||||||
|
ParameterizedThreadStart thInfo = GetFullCollectionAsync;
|
||||||
|
Thread th = new Thread(thInfo);
|
||||||
|
th.Start(_actb.Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplaySuggestions(IEnumerable suggestions, string filter)
|
||||||
|
{
|
||||||
|
if (_filter != filter)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_actb.IsLoading = false;
|
||||||
|
_actb.ItemsSelector.ItemsSource = suggestions;
|
||||||
|
// Close drop down if there are no items
|
||||||
|
if (_actb.IsDropDownOpen)
|
||||||
|
{
|
||||||
|
_actb.IsDropDownOpen = _actb.ItemsSelector.HasItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetSuggestionsAsync(object param)
|
||||||
|
{
|
||||||
|
if (param is object[] args)
|
||||||
|
{
|
||||||
|
string searchText = Convert.ToString(args[0]);
|
||||||
|
if (args[1] is IComboSuggestionProvider provider)
|
||||||
|
{
|
||||||
|
IEnumerable list = provider.GetSuggestions(searchText);
|
||||||
|
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, searchText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void GetFullCollectionAsync(object param)
|
||||||
|
{
|
||||||
|
if (param is IComboSuggestionProvider provider)
|
||||||
|
{
|
||||||
|
IEnumerable list = provider.GetFullCollection();
|
||||||
|
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
569
AutoCompleteTextBox/Editors/AutoCompleteTextBox.cs
Normal file
569
AutoCompleteTextBox/Editors/AutoCompleteTextBox.cs
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Editors
|
||||||
|
{
|
||||||
|
[TemplatePart(Name = PartEditor, Type = typeof(TextBox))]
|
||||||
|
[TemplatePart(Name = PartPopup, Type = typeof(Popup))]
|
||||||
|
[TemplatePart(Name = PartSelector, Type = typeof(Selector))]
|
||||||
|
public class AutoCompleteTextBox : Control
|
||||||
|
{
|
||||||
|
|
||||||
|
#region "Fields"
|
||||||
|
|
||||||
|
public const string PartEditor = "PART_Editor";
|
||||||
|
public const string PartPopup = "PART_Popup";
|
||||||
|
|
||||||
|
public const string PartSelector = "PART_Selector";
|
||||||
|
public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(200));
|
||||||
|
public static readonly DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
public static readonly DependencyProperty IconPlacementProperty = DependencyProperty.Register("IconPlacement", typeof(IconPlacement), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(IconPlacement.Left));
|
||||||
|
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty IconVisibilityProperty = DependencyProperty.Register("IconVisibility", typeof(Visibility), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(Visibility.Visible));
|
||||||
|
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register("IsLoading", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(AutoCompleteTextBox));
|
||||||
|
public static readonly DependencyProperty LoadingContentProperty = DependencyProperty.Register("LoadingContent", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty ProviderProperty = DependencyProperty.Register("Provider", typeof(ISuggestionProvider), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(null, OnSelectedItemChanged));
|
||||||
|
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty, propertyChangedCallback:null,coerceValueCallback:null, isAnimationProhibited:false, defaultUpdateSourceTrigger: UpdateSourceTrigger.LostFocus, flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
||||||
|
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(0));
|
||||||
|
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.Register("CharacterCasing", typeof(CharacterCasing), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(CharacterCasing.Normal));
|
||||||
|
public static readonly DependencyProperty MaxPopUpHeightProperty = DependencyProperty.Register("MaxPopUpHeight", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(600));
|
||||||
|
public static readonly DependencyProperty MaxPopUpWidthProperty = DependencyProperty.Register("MaxPopUpWidth", typeof(int), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(2000));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty SuggestionBackgroundProperty = DependencyProperty.Register("SuggestionBackground", typeof(Brush), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(Brushes.White));
|
||||||
|
private bool _isUpdatingText;
|
||||||
|
private bool _selectionCancelled;
|
||||||
|
|
||||||
|
private SuggestionsAdapter _suggestionsAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Constructors"
|
||||||
|
|
||||||
|
static AutoCompleteTextBox()
|
||||||
|
{
|
||||||
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(typeof(AutoCompleteTextBox)));
|
||||||
|
FocusableProperty.OverrideMetadata(typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Properties"
|
||||||
|
|
||||||
|
|
||||||
|
public int MaxPopupHeight
|
||||||
|
{
|
||||||
|
get => (int)GetValue(MaxPopUpHeightProperty);
|
||||||
|
set => SetValue(MaxPopUpHeightProperty, value);
|
||||||
|
}
|
||||||
|
public int MaxPopupWidth
|
||||||
|
{
|
||||||
|
get => (int)GetValue(MaxPopUpWidthProperty);
|
||||||
|
set => SetValue(MaxPopUpWidthProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BindingEvaluator BindingEvaluator { get; set; }
|
||||||
|
|
||||||
|
public CharacterCasing CharacterCasing
|
||||||
|
{
|
||||||
|
get => (CharacterCasing)GetValue(CharacterCasingProperty);
|
||||||
|
set => SetValue(CharacterCasingProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxLength
|
||||||
|
{
|
||||||
|
get => (int)GetValue(MaxLengthProperty);
|
||||||
|
set => SetValue(MaxLengthProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Delay
|
||||||
|
{
|
||||||
|
get => (int)GetValue(DelayProperty);
|
||||||
|
|
||||||
|
set => SetValue(DelayProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DisplayMember
|
||||||
|
{
|
||||||
|
get => (string)GetValue(DisplayMemberProperty);
|
||||||
|
|
||||||
|
set => SetValue(DisplayMemberProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextBox Editor { get; set; }
|
||||||
|
|
||||||
|
public DispatcherTimer FetchTimer { get; set; }
|
||||||
|
|
||||||
|
public string Filter
|
||||||
|
{
|
||||||
|
get => (string)GetValue(FilterProperty);
|
||||||
|
|
||||||
|
set => SetValue(FilterProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Icon
|
||||||
|
{
|
||||||
|
get => GetValue(IconProperty);
|
||||||
|
|
||||||
|
set => SetValue(IconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPlacement IconPlacement
|
||||||
|
{
|
||||||
|
get => (IconPlacement)GetValue(IconPlacementProperty);
|
||||||
|
|
||||||
|
set => SetValue(IconPlacementProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Visibility IconVisibility
|
||||||
|
{
|
||||||
|
get => (Visibility)GetValue(IconVisibilityProperty);
|
||||||
|
|
||||||
|
set => SetValue(IconVisibilityProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDropDownOpen
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||||
|
|
||||||
|
set => SetValue(IsDropDownOpenProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLoading
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsLoadingProperty);
|
||||||
|
|
||||||
|
set => SetValue(IsLoadingProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsReadOnlyProperty);
|
||||||
|
|
||||||
|
set => SetValue(IsReadOnlyProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Selector ItemsSelector { get; set; }
|
||||||
|
|
||||||
|
public DataTemplate ItemTemplate
|
||||||
|
{
|
||||||
|
get => (DataTemplate)GetValue(ItemTemplateProperty);
|
||||||
|
|
||||||
|
set => SetValue(ItemTemplateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTemplateSelector ItemTemplateSelector
|
||||||
|
{
|
||||||
|
get => ((DataTemplateSelector)(GetValue(ItemTemplateSelectorProperty)));
|
||||||
|
set => SetValue(ItemTemplateSelectorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object LoadingContent
|
||||||
|
{
|
||||||
|
get => GetValue(LoadingContentProperty);
|
||||||
|
|
||||||
|
set => SetValue(LoadingContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popup Popup { get; set; }
|
||||||
|
|
||||||
|
public ISuggestionProvider Provider
|
||||||
|
{
|
||||||
|
get => (ISuggestionProvider)GetValue(ProviderProperty);
|
||||||
|
|
||||||
|
set => SetValue(ProviderProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SelectedItem
|
||||||
|
{
|
||||||
|
get => GetValue(SelectedItemProperty);
|
||||||
|
|
||||||
|
set => SetValue(SelectedItemProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionAdapter SelectionAdapter { get; set; }
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => (string)GetValue(TextProperty);
|
||||||
|
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Watermark
|
||||||
|
{
|
||||||
|
get => (string)GetValue(WatermarkProperty);
|
||||||
|
|
||||||
|
set => SetValue(WatermarkProperty, value);
|
||||||
|
}
|
||||||
|
public Brush SuggestionBackground
|
||||||
|
{
|
||||||
|
get => (Brush)GetValue(SuggestionBackgroundProperty);
|
||||||
|
|
||||||
|
set => SetValue(SuggestionBackgroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Methods"
|
||||||
|
|
||||||
|
public static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
AutoCompleteTextBox act = null;
|
||||||
|
act = d as AutoCompleteTextBox;
|
||||||
|
if (act != null)
|
||||||
|
{
|
||||||
|
if (act.Editor != null & !act._isUpdatingText)
|
||||||
|
{
|
||||||
|
act._isUpdatingText = true;
|
||||||
|
act.Editor.Text = act.BindingEvaluator.Evaluate(e.NewValue);
|
||||||
|
act._isUpdatingText = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScrollToSelectedItem()
|
||||||
|
{
|
||||||
|
if (ItemsSelector is ListBox listBox && listBox.SelectedItem != null)
|
||||||
|
listBox.ScrollIntoView(listBox.SelectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding){
|
||||||
|
var res = base.SetBinding(dp, binding);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public new BindingExpressionBase SetBinding(DependencyProperty dp, String path) {
|
||||||
|
var res = base.SetBinding(dp, path);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public new void ClearValue(DependencyPropertyKey key) {
|
||||||
|
base.ClearValue(key);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
}
|
||||||
|
public new void ClearValue(DependencyProperty dp) {
|
||||||
|
base.ClearValue(dp);
|
||||||
|
CheckForParentTextBindingChange();
|
||||||
|
}
|
||||||
|
private void CheckForParentTextBindingChange(bool force=false) {
|
||||||
|
var CurrentBindingMode = BindingOperations.GetBinding(this, TextProperty)?.UpdateSourceTrigger ?? UpdateSourceTrigger.Default;
|
||||||
|
if (CurrentBindingMode != UpdateSourceTrigger.PropertyChanged)//preventing going any less frequent than property changed
|
||||||
|
CurrentBindingMode = UpdateSourceTrigger.Default;
|
||||||
|
|
||||||
|
|
||||||
|
if (CurrentBindingMode == CurrentTextboxTextBindingUpdateMode && force == false)
|
||||||
|
return;
|
||||||
|
var binding = new Binding {
|
||||||
|
Mode = BindingMode.TwoWay,
|
||||||
|
UpdateSourceTrigger = CurrentBindingMode,
|
||||||
|
Path = new PropertyPath(nameof(Text)),
|
||||||
|
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
|
||||||
|
};
|
||||||
|
CurrentTextboxTextBindingUpdateMode = CurrentBindingMode;
|
||||||
|
Editor?.SetBinding(TextBox.TextProperty, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UpdateSourceTrigger CurrentTextboxTextBindingUpdateMode;
|
||||||
|
public override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
Editor = Template.FindName(PartEditor, this) as TextBox;
|
||||||
|
Editor.Focus();
|
||||||
|
Popup = Template.FindName(PartPopup, this) as Popup;
|
||||||
|
ItemsSelector = Template.FindName(PartSelector, this) as Selector;
|
||||||
|
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||||
|
|
||||||
|
if (Editor != null)
|
||||||
|
{
|
||||||
|
Editor.TextChanged += OnEditorTextChanged;
|
||||||
|
Editor.PreviewKeyDown += OnEditorKeyDown;
|
||||||
|
Editor.LostFocus += OnEditorLostFocus;
|
||||||
|
CheckForParentTextBindingChange(true);
|
||||||
|
|
||||||
|
if (SelectedItem != null)
|
||||||
|
{
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = BindingEvaluator.Evaluate(SelectedItem);
|
||||||
|
_isUpdatingText = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GotFocus += AutoCompleteTextBox_GotFocus;
|
||||||
|
GotKeyboardFocus += AutoCompleteTextBox_GotKeyboardFocus;
|
||||||
|
|
||||||
|
if (Popup != null)
|
||||||
|
{
|
||||||
|
Popup.StaysOpen = false;
|
||||||
|
Popup.Opened += OnPopupOpened;
|
||||||
|
Popup.Closed += OnPopupClosed;
|
||||||
|
}
|
||||||
|
if (ItemsSelector != null)
|
||||||
|
{
|
||||||
|
SelectionAdapter = new SelectionAdapter(ItemsSelector);
|
||||||
|
SelectionAdapter.Commit += OnSelectionAdapterCommit;
|
||||||
|
SelectionAdapter.Cancel += OnSelectionAdapterCancel;
|
||||||
|
SelectionAdapter.SelectionChanged += OnSelectionAdapterSelectionChanged;
|
||||||
|
ItemsSelector.PreviewMouseDown += ItemsSelector_PreviewMouseDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void ItemsSelector_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if ((e.OriginalSource as FrameworkElement)?.DataContext == null)
|
||||||
|
return;
|
||||||
|
if (!ItemsSelector.Items.Contains(((FrameworkElement)e.OriginalSource)?.DataContext))
|
||||||
|
return;
|
||||||
|
ItemsSelector.SelectedItem = ((FrameworkElement)e.OriginalSource)?.DataContext;
|
||||||
|
OnSelectionAdapterCommit(SelectionAdapter.EventCause.MouseDown);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
private void AutoCompleteTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Editor?.Focus();
|
||||||
|
}
|
||||||
|
private void AutoCompleteTextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
|
||||||
|
if (e.NewFocus != this)
|
||||||
|
return;
|
||||||
|
if (e.OldFocus == Editor)
|
||||||
|
MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDisplayText(object dataItem)
|
||||||
|
{
|
||||||
|
if (BindingEvaluator == null)
|
||||||
|
{
|
||||||
|
BindingEvaluator = new BindingEvaluator(new Binding(DisplayMember));
|
||||||
|
}
|
||||||
|
if (dataItem == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(DisplayMember))
|
||||||
|
{
|
||||||
|
return dataItem.ToString();
|
||||||
|
}
|
||||||
|
return BindingEvaluator.Evaluate(dataItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectionAdapter != null)
|
||||||
|
{
|
||||||
|
if (IsDropDownOpen)
|
||||||
|
SelectionAdapter.HandleKeyDown(e);
|
||||||
|
else
|
||||||
|
IsDropDownOpen = e.Key == Key.Down || e.Key == Key.Up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorLostFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsKeyboardFocusWithin)
|
||||||
|
{
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEditorTextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Text = Editor.Text;
|
||||||
|
if (_isUpdatingText)
|
||||||
|
return;
|
||||||
|
if (FetchTimer == null)
|
||||||
|
{
|
||||||
|
FetchTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Delay) };
|
||||||
|
FetchTimer.Tick += OnFetchTimerTick;
|
||||||
|
}
|
||||||
|
FetchTimer.IsEnabled = false;
|
||||||
|
FetchTimer.Stop();
|
||||||
|
SetSelectedItem(null);
|
||||||
|
if (Editor.Text.Length > 0)
|
||||||
|
{
|
||||||
|
FetchTimer.IsEnabled = true;
|
||||||
|
FetchTimer.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFetchTimerTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
FetchTimer.IsEnabled = false;
|
||||||
|
FetchTimer.Stop();
|
||||||
|
if (Provider != null && ItemsSelector != null)
|
||||||
|
{
|
||||||
|
Filter = Editor.Text;
|
||||||
|
if (_suggestionsAdapter == null)
|
||||||
|
{
|
||||||
|
_suggestionsAdapter = new SuggestionsAdapter(this);
|
||||||
|
}
|
||||||
|
_suggestionsAdapter.GetSuggestions(Filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPopupClosed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!_selectionCancelled)
|
||||||
|
{
|
||||||
|
OnSelectionAdapterCommit(SelectionAdapter.EventCause.PopupClosed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPopupOpened(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_selectionCancelled = false;
|
||||||
|
ItemsSelector.SelectedItem = SelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionAdapterCancel(SelectionAdapter.EventCause cause)
|
||||||
|
{
|
||||||
|
if (PreSelectionEventSomeoneHandled(cause, true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = SelectedItem == null ? Filter : GetDisplayText(SelectedItem);
|
||||||
|
Editor.SelectionStart = Editor.Text.Length;
|
||||||
|
Editor.SelectionLength = 0;
|
||||||
|
_isUpdatingText = false;
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
_selectionCancelled = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<SelectionAdapter.PreSelectionAdapterFinishArgs> PreSelectionAdapterFinish;
|
||||||
|
private bool PreSelectionEventSomeoneHandled(SelectionAdapter.EventCause cause, bool is_cancel) {
|
||||||
|
if (PreSelectionAdapterFinish == null)
|
||||||
|
return false;
|
||||||
|
var args = new SelectionAdapter.PreSelectionAdapterFinishArgs { cause = cause, is_cancel = is_cancel };
|
||||||
|
PreSelectionAdapterFinish?.Invoke(this, args);
|
||||||
|
return args.handled;
|
||||||
|
|
||||||
|
}
|
||||||
|
private void OnSelectionAdapterCommit(SelectionAdapter.EventCause cause)
|
||||||
|
{
|
||||||
|
if (PreSelectionEventSomeoneHandled(cause, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ItemsSelector.SelectedItem != null)
|
||||||
|
{
|
||||||
|
SelectedItem = ItemsSelector.SelectedItem;
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = GetDisplayText(ItemsSelector.SelectedItem);
|
||||||
|
SetSelectedItem(ItemsSelector.SelectedItem);
|
||||||
|
_isUpdatingText = false;
|
||||||
|
IsDropDownOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionAdapterSelectionChanged()
|
||||||
|
{
|
||||||
|
_isUpdatingText = true;
|
||||||
|
Editor.Text = ItemsSelector.SelectedItem == null ? Filter : GetDisplayText(ItemsSelector.SelectedItem);
|
||||||
|
Editor.SelectionStart = Editor.Text.Length;
|
||||||
|
Editor.SelectionLength = 0;
|
||||||
|
ScrollToSelectedItem();
|
||||||
|
_isUpdatingText = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetSelectedItem(object item)
|
||||||
|
{
|
||||||
|
_isUpdatingText = true;
|
||||||
|
SelectedItem = item;
|
||||||
|
_isUpdatingText = false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Nested Types"
|
||||||
|
|
||||||
|
private class SuggestionsAdapter
|
||||||
|
{
|
||||||
|
|
||||||
|
#region "Fields"
|
||||||
|
|
||||||
|
private readonly AutoCompleteTextBox _actb;
|
||||||
|
|
||||||
|
private string _filter;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Constructors"
|
||||||
|
|
||||||
|
public SuggestionsAdapter(AutoCompleteTextBox actb)
|
||||||
|
{
|
||||||
|
_actb = actb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Methods"
|
||||||
|
|
||||||
|
public void GetSuggestions(string searchText)
|
||||||
|
{
|
||||||
|
_filter = searchText;
|
||||||
|
_actb.IsLoading = true;
|
||||||
|
// Do not open drop down if control is not focused
|
||||||
|
if (_actb.IsKeyboardFocusWithin)
|
||||||
|
_actb.IsDropDownOpen = true;
|
||||||
|
_actb.ItemsSelector.ItemsSource = null;
|
||||||
|
ParameterizedThreadStart thInfo = GetSuggestionsAsync;
|
||||||
|
Thread th = new Thread(thInfo);
|
||||||
|
th.Start(new object[] { searchText, _actb.Provider });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplaySuggestions(IEnumerable suggestions, string filter)
|
||||||
|
{
|
||||||
|
if (_filter != filter)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_actb.IsLoading = false;
|
||||||
|
_actb.ItemsSelector.ItemsSource = suggestions;
|
||||||
|
// Close drop down if there are no items
|
||||||
|
if (_actb.IsDropDownOpen)
|
||||||
|
{
|
||||||
|
_actb.IsDropDownOpen = _actb.ItemsSelector.HasItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetSuggestionsAsync(object param)
|
||||||
|
{
|
||||||
|
if (param is object[] args)
|
||||||
|
{
|
||||||
|
string searchText = Convert.ToString(args[0]);
|
||||||
|
if (args[1] is ISuggestionProvider provider)
|
||||||
|
{
|
||||||
|
IEnumerable list = provider.GetSuggestions(searchText);
|
||||||
|
_actb.Dispatcher.BeginInvoke(new Action<IEnumerable, string>(DisplaySuggestions), DispatcherPriority.Background, list, searchText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
AutoCompleteTextBox/Editors/IComboSuggestionProvider.cs
Normal file
16
AutoCompleteTextBox/Editors/IComboSuggestionProvider.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Editors
|
||||||
|
{
|
||||||
|
public interface IComboSuggestionProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
IEnumerable GetSuggestions(string filter);
|
||||||
|
IEnumerable GetFullCollection();
|
||||||
|
|
||||||
|
#endregion Public Methods
|
||||||
|
}
|
||||||
|
}
|
||||||
15
AutoCompleteTextBox/Editors/ISuggestionProvider.cs
Normal file
15
AutoCompleteTextBox/Editors/ISuggestionProvider.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Editors
|
||||||
|
{
|
||||||
|
public interface ISuggestionProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
IEnumerable GetSuggestions(string filter);
|
||||||
|
|
||||||
|
#endregion Public Methods
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
122
AutoCompleteTextBox/Editors/SelectionAdapter.cs
Normal file
122
AutoCompleteTextBox/Editors/SelectionAdapter.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Editors
|
||||||
|
{
|
||||||
|
public class SelectionAdapter
|
||||||
|
{
|
||||||
|
public class PreSelectionAdapterFinishArgs {
|
||||||
|
public EventCause cause;
|
||||||
|
public bool is_cancel;
|
||||||
|
public bool handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region "Fields"
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Constructors"
|
||||||
|
|
||||||
|
public SelectionAdapter(Selector selector)
|
||||||
|
{
|
||||||
|
SelectorControl = selector;
|
||||||
|
SelectorControl.PreviewMouseUp += OnSelectorMouseDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Events"
|
||||||
|
|
||||||
|
public enum EventCause { Other, PopupClosed, ItemClicked, EnterPressed, EscapePressed, TabPressed, MouseDown}
|
||||||
|
public delegate void CancelEventHandler(EventCause cause);
|
||||||
|
|
||||||
|
public delegate void CommitEventHandler(EventCause cause);
|
||||||
|
|
||||||
|
public delegate void SelectionChangedEventHandler();
|
||||||
|
|
||||||
|
public event CancelEventHandler Cancel;
|
||||||
|
public event CommitEventHandler Commit;
|
||||||
|
public event SelectionChangedEventHandler SelectionChanged;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Properties"
|
||||||
|
|
||||||
|
public Selector SelectorControl { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region "Methods"
|
||||||
|
|
||||||
|
public void HandleKeyDown(KeyEventArgs key)
|
||||||
|
{
|
||||||
|
switch (key.Key)
|
||||||
|
{
|
||||||
|
case Key.Down:
|
||||||
|
IncrementSelection();
|
||||||
|
break;
|
||||||
|
case Key.Up:
|
||||||
|
DecrementSelection();
|
||||||
|
break;
|
||||||
|
case Key.Enter:
|
||||||
|
Commit?.Invoke(EventCause.EnterPressed);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case Key.Escape:
|
||||||
|
Cancel?.Invoke(EventCause.EscapePressed);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case Key.Tab:
|
||||||
|
Commit?.Invoke(EventCause.TabPressed);
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
key.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DecrementSelection()
|
||||||
|
{
|
||||||
|
if (SelectorControl.SelectedIndex == -1)
|
||||||
|
{
|
||||||
|
SelectorControl.SelectedIndex = SelectorControl.Items.Count - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectorControl.SelectedIndex -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncrementSelection()
|
||||||
|
{
|
||||||
|
if (SelectorControl.SelectedIndex == SelectorControl.Items.Count - 1)
|
||||||
|
{
|
||||||
|
SelectorControl.SelectedIndex = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectorControl.SelectedIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectorMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
// If sender is the RepeatButton from the scrollbar we need to
|
||||||
|
// to skip this event otherwise focus get stuck in the RepeatButton
|
||||||
|
// and list is scrolled up or down til the end.
|
||||||
|
if (e.OriginalSource.GetType() != typeof(RepeatButton))
|
||||||
|
{
|
||||||
|
Commit?.Invoke(EventCause.MouseDown);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
AutoCompleteTextBox/Editors/SuggestionProvider.cs
Normal file
35
AutoCompleteTextBox/Editors/SuggestionProvider.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Editors
|
||||||
|
{
|
||||||
|
public class SuggestionProvider : ISuggestionProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
private readonly Func<string, IEnumerable> _method;
|
||||||
|
|
||||||
|
#endregion Private Fields
|
||||||
|
|
||||||
|
#region Public Constructors
|
||||||
|
|
||||||
|
public SuggestionProvider(Func<string, IEnumerable> method)
|
||||||
|
{
|
||||||
|
_method = method ?? throw new ArgumentNullException(nameof(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public Constructors
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public IEnumerable GetSuggestions(string filter)
|
||||||
|
{
|
||||||
|
return _method(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public Methods
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
268
AutoCompleteTextBox/Editors/Themes/Generic.xaml
Normal file
268
AutoCompleteTextBox/Editors/Themes/Generic.xaml
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:editors="clr-namespace:AutoCompleteTextBox.Editors">
|
||||||
|
|
||||||
|
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
|
||||||
|
|
||||||
|
<Style x:Key="TextBoxSuggestionItemStyle" TargetType="ListBoxItem">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ListBoxItem">
|
||||||
|
<Border x:Name="ContentBorder" Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="IsSelected" Value="True" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="ContentBorder" Property="Background" Value="{x:Static SystemColors.HighlightBrush}" />
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{x:Static SystemColors.HighlightTextBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="ComboBoxSuggestionItemStyle" TargetType="ListBoxItem">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ListBoxItem">
|
||||||
|
<Border x:Name="ContentBorder" Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteComboBox}, Mode=OneWay}">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="IsSelected" Value="True" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="ContentBorder" Property="Background" Value="{x:Static SystemColors.HighlightBrush}" />
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{x:Static SystemColors.HighlightTextBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="TransparentTextBoxStyle" TargetType="{x:Type TextBox}">
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||||
|
<Grid>
|
||||||
|
<ScrollViewer
|
||||||
|
x:Name="PART_ContentHost"
|
||||||
|
Background="Transparent"
|
||||||
|
CanContentScroll="True"
|
||||||
|
Focusable="True"
|
||||||
|
HorizontalScrollBarVisibility="Hidden"
|
||||||
|
VerticalScrollBarVisibility="Hidden" />
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type editors:AutoCompleteTextBox}">
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="BorderBrush" Value="#FFABADB3" />
|
||||||
|
<Setter Property="SuggestionBackground" Value="White" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Top" />
|
||||||
|
<Setter Property="AllowDrop" Value="true" />
|
||||||
|
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
|
||||||
|
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||||
|
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type editors:AutoCompleteTextBox}">
|
||||||
|
<Border
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="0">
|
||||||
|
<Grid>
|
||||||
|
<DockPanel>
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="PART_Icon"
|
||||||
|
ContentSource="Icon"
|
||||||
|
Visibility="{TemplateBinding IconVisibility}" />
|
||||||
|
<Grid>
|
||||||
|
<TextBlock
|
||||||
|
x:Name="PART_Watermark"
|
||||||
|
Margin="3,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Focusable="False"
|
||||||
|
Foreground="Gray"
|
||||||
|
Text="{TemplateBinding Watermark}"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="PART_Editor"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
CharacterCasing="{Binding Path=CharacterCasing, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=OneWay}"
|
||||||
|
MaxLength="{Binding Path=MaxLength, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
Style="{StaticResource ResourceKey=TransparentTextBoxStyle}" />
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
<Popup
|
||||||
|
x:Name="PART_Popup"
|
||||||
|
MinWidth="{TemplateBinding ActualWidth}"
|
||||||
|
MinHeight="25"
|
||||||
|
MaxHeight="600"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalOffset="0"
|
||||||
|
IsOpen="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
PopupAnimation="Slide">
|
||||||
|
<Border
|
||||||
|
Padding="2"
|
||||||
|
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}"
|
||||||
|
BorderBrush="Gray"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="5">
|
||||||
|
<Grid Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}">
|
||||||
|
<ListBox
|
||||||
|
x:Name="PART_Selector"
|
||||||
|
MaxWidth="{Binding Path=MaxPopupWidth, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
MaxHeight="{Binding Path=MaxPopupHeight, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteTextBox}, Mode=OneWay}"
|
||||||
|
BorderThickness="0"
|
||||||
|
Focusable="False"
|
||||||
|
ItemContainerStyle="{StaticResource ResourceKey=TextBoxSuggestionItemStyle}"
|
||||||
|
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||||
|
ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||||
|
<Border Visibility="{Binding Path=IsLoading, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource ResourceKey=BoolToVisConverter}}">
|
||||||
|
<ContentPresenter ContentSource="LoadingContent" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger SourceName="PART_Editor" Property="Text" Value="">
|
||||||
|
<Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IconPlacement" Value="Left">
|
||||||
|
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Left" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IconPlacement" Value="Right">
|
||||||
|
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Right" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="Validation.HasError" Value="True">
|
||||||
|
<Setter Property="BorderBrush" Value="Red" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="editors:AutoCompleteComboBox">
|
||||||
|
<Setter Property="Focusable" Value="True" />
|
||||||
|
<Setter Property="SuggestionBackground" Value="White" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Top" />
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||||
|
<Setter Property="AllowDrop" Value="true" />
|
||||||
|
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
|
||||||
|
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type editors:AutoCompleteComboBox}">
|
||||||
|
<Grid>
|
||||||
|
<DockPanel>
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="PART_Icon"
|
||||||
|
ContentSource="Icon"
|
||||||
|
Visibility="{TemplateBinding IconVisibility}" />
|
||||||
|
<Grid>
|
||||||
|
<TextBlock
|
||||||
|
x:Name="PART_Watermark"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
DockPanel.Dock="Left"
|
||||||
|
Focusable="False"
|
||||||
|
Foreground="Gray"
|
||||||
|
Text="{TemplateBinding Watermark}"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<DockPanel Margin="3,0">
|
||||||
|
<Expander x:Name="PART_Expander" DockPanel.Dock="Right" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="PART_Editor"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
CharacterCasing="{Binding Path=CharacterCasing, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
Focusable="True"
|
||||||
|
MaxLength="{Binding Path=MaxLength, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}" />
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
<Popup
|
||||||
|
x:Name="PART_Popup"
|
||||||
|
MinWidth="{TemplateBinding ActualWidth}"
|
||||||
|
MinHeight="25"
|
||||||
|
MaxHeight="600"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalOffset="0"
|
||||||
|
IsOpen="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
PopupAnimation="Slide">
|
||||||
|
<Border
|
||||||
|
Padding="2"
|
||||||
|
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteComboBox}, Mode=OneWay}"
|
||||||
|
BorderBrush="Gray"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="5">
|
||||||
|
<Grid>
|
||||||
|
<ListBox
|
||||||
|
x:Name="PART_Selector"
|
||||||
|
MaxWidth="{Binding Path=MaxPopupWidth, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
MaxHeight="{Binding Path=MaxPopupHeight, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}"
|
||||||
|
Background="{Binding Path=SuggestionBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=editors:AutoCompleteComboBox}, Mode=OneWay}"
|
||||||
|
BorderThickness="0"
|
||||||
|
Focusable="False"
|
||||||
|
ItemContainerStyle="{StaticResource ResourceKey=ComboBoxSuggestionItemStyle}"
|
||||||
|
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||||
|
ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||||
|
<Border Visibility="{Binding Path=IsLoading, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource ResourceKey=BoolToVisConverter}}">
|
||||||
|
<ContentPresenter ContentSource="LoadingContent" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
<!--</Border>-->
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger SourceName="PART_Editor" Property="Text" Value="">
|
||||||
|
<Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IconPlacement" Value="Left">
|
||||||
|
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Left" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IconPlacement" Value="Right">
|
||||||
|
<Setter TargetName="PART_Icon" Property="DockPanel.Dock" Value="Right" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="Validation.HasError" Value="True">
|
||||||
|
<Setter Property="BorderBrush" Value="Red" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
8
AutoCompleteTextBox/Enumerations.cs
Normal file
8
AutoCompleteTextBox/Enumerations.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace AutoCompleteTextBox
|
||||||
|
{
|
||||||
|
public enum IconPlacement
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
}
|
||||||
|
}
|
||||||
53
AutoCompleteTextBox/Properties/AssemblyInfo.cs
Normal file
53
AutoCompleteTextBox/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Markup;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("AutoCompleteTextBox")]
|
||||||
|
[assembly: AssemblyDescription("An autocomplete textbox for WPF")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("AutoCompleteTextBox")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
//In order to begin building localizable applications, set
|
||||||
|
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||||
|
//inside a <PropertyGroup>. For example, if you are using US english
|
||||||
|
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||||
|
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||||
|
//the line below to match the UICulture setting in the project file.
|
||||||
|
|
||||||
|
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||||
|
|
||||||
|
|
||||||
|
[assembly:ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.1.1.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.1.1.0")]
|
||||||
|
[assembly: XmlnsDefinition("http://wpfcontrols.com/", "AutoCompleteTextBox")]
|
||||||
|
[assembly: XmlnsDefinition("http://wpfcontrols.com/", "AutoCompleteTextBox.Editors")]
|
||||||
63
AutoCompleteTextBox/Properties/Resources.Designer.cs
generated
Normal file
63
AutoCompleteTextBox/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Properties {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoCompleteTextBox.Properties.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
AutoCompleteTextBox/Properties/Resources.resx
Normal file
117
AutoCompleteTextBox/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
26
AutoCompleteTextBox/Properties/Settings.Designer.cs
generated
Normal file
26
AutoCompleteTextBox/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace AutoCompleteTextBox.Properties {
|
||||||
|
|
||||||
|
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default {
|
||||||
|
get {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
AutoCompleteTextBox/Properties/Settings.settings
Normal file
7
AutoCompleteTextBox/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||||
|
<Profiles>
|
||||||
|
<Profile Name="(Default)" />
|
||||||
|
</Profiles>
|
||||||
|
<Settings />
|
||||||
|
</SettingsFile>
|
||||||
6
AutoCompleteTextBox/Themes/Generic.xaml
Normal file
6
AutoCompleteTextBox/Themes/Generic.xaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="/AutoCompleteTextBox;component/editors/themes/generic.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -43,7 +43,7 @@ namespace Config.Net.Stores
|
|||||||
// nothing to dispose.
|
// nothing to dispose.
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "json";
|
public static string Name => "json";
|
||||||
|
|
||||||
public bool CanRead => true;
|
public bool CanRead => true;
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ namespace Config.Net.Stores
|
|||||||
|
|
||||||
if (isIndex)
|
if (isIndex)
|
||||||
{
|
{
|
||||||
if (!(node is JsonArray ja)) return null;
|
if (node is not JsonArray ja) return null;
|
||||||
|
|
||||||
if (partIndex < ja.Count)
|
if (partIndex < ja.Count)
|
||||||
{
|
{
|
||||||
@@ -132,7 +132,7 @@ namespace Config.Net.Stores
|
|||||||
|
|
||||||
string js = _j.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
|
string js = _j.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
|
||||||
FileInfo file = new FileInfo(_pathName);
|
FileInfo file = new(_pathName);
|
||||||
|
|
||||||
if (file is not null)
|
if (file is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoboSharp", "RoboSharp\Robo
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Config.Net", "Config.Net\Config.Net.csproj", "{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Config.Net", "Config.Net\Config.Net.csproj", "{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoCompleteTextBox", "AutoCompleteTextBox\AutoCompleteTextBox.csproj", "{3162765C-B702-4927-8276-833E9046716D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -51,6 +53,10 @@ Global
|
|||||||
{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3162765C-B702-4927-8276-833E9046716D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3162765C-B702-4927-8276-833E9046716D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3162765C-B702-4927-8276-833E9046716D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3162765C-B702-4927-8276-833E9046716D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using Config.Net;
|
||||||
|
using Config.Net.Stores;
|
||||||
|
using Hardcodet.Wpf.TaskbarNotification;
|
||||||
using NHotkey;
|
using NHotkey;
|
||||||
using NHotkey.Wpf;
|
using NHotkey.Wpf;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Reflection;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using FSI.Lib.CompareNetObjects;
|
|
||||||
using Config.Net.Stores;
|
|
||||||
using System.IO;
|
|
||||||
using Config.Net;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FSI.BT.Tools
|
namespace FSI.BT.Tools
|
||||||
{
|
{
|
||||||
@@ -23,11 +21,14 @@ namespace FSI.BT.Tools
|
|||||||
|
|
||||||
|
|
||||||
public void Application_Startup(object sender, StartupEventArgs e)
|
public void Application_Startup(object sender, StartupEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
||||||
Global.Log.Info("Anwendung wurde gestartet!");
|
Global.Log.Info("Anwendung wurde gestartet!");
|
||||||
|
|
||||||
|
ExtractEmbeddedZip("FSI.BT.Tools.ExtTools.kalk.zip", Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\ExtTools\\");
|
||||||
|
|
||||||
// App-Settings
|
// App-Settings
|
||||||
JsonConfigStore _store = new JsonConfigStore(System.IO.Path.Combine(Directory.GetCurrentDirectory(), "config.json"), true);
|
JsonConfigStore _store = new(System.IO.Path.Combine(Directory.GetCurrentDirectory(), "config.json"), true);
|
||||||
Global.AppSettings = new ConfigurationBuilder<Settings.AppSettings.IAppSettings>()
|
Global.AppSettings = new ConfigurationBuilder<Settings.AppSettings.IAppSettings>()
|
||||||
.UseConfigStore(_store)
|
.UseConfigStore(_store)
|
||||||
.Build();
|
.Build();
|
||||||
@@ -40,9 +41,9 @@ namespace FSI.BT.Tools
|
|||||||
|
|
||||||
HotkeyManager.Current.AddOrReplace("RadialMenu", RadialMenu, ShowRadialMenu);
|
HotkeyManager.Current.AddOrReplace("RadialMenu", RadialMenu, ShowRadialMenu);
|
||||||
HotkeyManager.Current.AddOrReplace("TimeStampToClipboard", TimeStamp, TimeStampToClipboard);
|
HotkeyManager.Current.AddOrReplace("TimeStampToClipboard", TimeStamp, TimeStampToClipboard);
|
||||||
|
|
||||||
Global.FrmRadialMenu = new FrmRadialMenu();
|
Global.FrmRadialMenu = new FrmRadialMenu();
|
||||||
|
|
||||||
Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.ViewModel()
|
Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.ViewModel()
|
||||||
{
|
{
|
||||||
Data = Global.AppSettings.WinCC
|
Data = Global.AppSettings.WinCC
|
||||||
@@ -54,7 +55,6 @@ namespace FSI.BT.Tools
|
|||||||
Data = Global.AppSettings.IbaDirSync
|
Data = Global.AppSettings.IbaDirSync
|
||||||
};
|
};
|
||||||
Global.Iba.Init();
|
Global.Iba.Init();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowRadialMenu(object sender, HotkeyEventArgs e)
|
private void ShowRadialMenu(object sender, HotkeyEventArgs e)
|
||||||
@@ -75,13 +75,13 @@ namespace FSI.BT.Tools
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeCrypt(ref IEnumerable<Settings.StringValue.IStringValueCrypt> values)
|
private static void ExtractEmbeddedZip(string zipName, string destPath)
|
||||||
{
|
{
|
||||||
var valuesToDeCrypt = values.ToList();
|
System.IO.Directory.CreateDirectory(destPath); // Erstellt alle fehlenden Verzeichnisse
|
||||||
|
using Stream _pluginZipResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(zipName);
|
||||||
foreach (var value in valuesToDeCrypt.ToList())
|
using ZipArchive zip = new(_pluginZipResourceStream);
|
||||||
value.ValueDeCrypt = Lib.DeEncryptString.DeEncrypt.DecryptString(value.Value, AppDomain.CurrentDomain.FriendlyName);
|
zip.ExtractToDirectory(destPath, true);
|
||||||
|
Global.Log.Info("Externes Tool \"{0}\" wurde in das Verzeichnis \"{1}\" entpackt", zipName, destPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Application_Exit(object sender, ExitEventArgs e)
|
private void Application_Exit(object sender, ExitEventArgs e)
|
||||||
@@ -94,5 +94,6 @@ namespace FSI.BT.Tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,26 +26,24 @@ namespace FSI.BT.Tools.Commands
|
|||||||
var cmds = Global.AppSettings.Cmds.ToList();
|
var cmds = Global.AppSettings.Cmds.ToList();
|
||||||
ICmd selectedCmd = null;
|
ICmd selectedCmd = null;
|
||||||
|
|
||||||
// IEnumerable<Settings.Exe.IExe> files = new List<Settings.Exe.IExe>();
|
|
||||||
IExe selectedFile;
|
|
||||||
|
|
||||||
switch ((string)parameter)
|
switch ((string)parameter)
|
||||||
{
|
{
|
||||||
|
|
||||||
case "EplPrj":
|
case "Epl.Prj":
|
||||||
//selectedFile = GetApp(Global.AppSettings.Apps.Epl);
|
Lib.Guis.Prj.Mgt.FrmMain frmMainEplPrj = new()
|
||||||
//Lib.Guis.Prj.Mgt.FrmMain frmMainEplPrj = new()
|
{
|
||||||
//{
|
ShowPdf = false,
|
||||||
// ShowPdf = false,
|
CloseAtLostFocus = true,
|
||||||
// CloseAtLostFocus = true,
|
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||||
// WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
Path = FSI.BT.Tools.Settings.AppSettings.GetFolderByName(Global.AppSettings.Folders, "EplPrj").path,
|
||||||
// Path = FSI.BT.Tools.Settings.AppSettings.GetFolderByName(Global.AppSettings.Folders, "EplPrj").path,
|
EplExe = GetExeByCmdName("Epl").ExePath,
|
||||||
// EplExe = selectedFile.ExePath,
|
};
|
||||||
//};
|
frmMainEplPrj.Show();
|
||||||
//frmMainEplPrj.Show();
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "EplPdf":
|
case "Epl.Pdf":
|
||||||
Lib.Guis.Prj.Mgt.FrmMain frmMainEplPdf = new()
|
Lib.Guis.Prj.Mgt.FrmMain frmMainEplPdf = new()
|
||||||
{
|
{
|
||||||
ShowPdf = true,
|
ShowPdf = true,
|
||||||
@@ -56,7 +54,7 @@ namespace FSI.BT.Tools.Commands
|
|||||||
frmMainEplPdf.Show();
|
frmMainEplPdf.Show();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "EplPdfMgt":
|
case "Epl.PdfMgt":
|
||||||
Lib.Guis.Pdf.Mgt.FrmMain frmMainEplPdfMgt = new()
|
Lib.Guis.Pdf.Mgt.FrmMain frmMainEplPdfMgt = new()
|
||||||
{
|
{
|
||||||
CloseAtLostFocus = true
|
CloseAtLostFocus = true
|
||||||
@@ -96,14 +94,22 @@ namespace FSI.BT.Tools.Commands
|
|||||||
};
|
};
|
||||||
frmTxtToClipMain.Show();
|
frmTxtToClipMain.Show();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case "Rdp.Mgt":
|
||||||
|
Lib.Guis.Rdp.Mgt.FrmMain frmRdpMain = new()
|
||||||
|
{
|
||||||
|
CloseAtLostFocus = true,
|
||||||
|
InputData = Global.AppSettings.Rdps,
|
||||||
|
Exe = GetExeByCmdName("Rdp").ExePath,
|
||||||
|
};
|
||||||
|
frmRdpMain.Show();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
foreach (ICmd cmd in cmds)
|
foreach (ICmd cmd in cmds)
|
||||||
{
|
{
|
||||||
if (String.Equals(parameter.ToString().ToLower(), cmd.Cmd.ToLower()))
|
if (String.Equals(parameter.ToString(), cmd.Cmd))
|
||||||
{
|
|
||||||
selectedCmd = cmd;
|
selectedCmd = cmd;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -121,14 +127,14 @@ namespace FSI.BT.Tools.Commands
|
|||||||
ICmd selectedCmd = null;
|
ICmd selectedCmd = null;
|
||||||
|
|
||||||
switch ((string)parameter)
|
switch ((string)parameter)
|
||||||
{
|
{
|
||||||
case "EplPrj":
|
case "Epl.Prj":
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case "EplPdf":
|
case "Epl.Pdf":
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case "EplPdfMgt":
|
case "Epl.PdfMgt":
|
||||||
return Global.AdminRights;
|
return Global.AdminRights;
|
||||||
|
|
||||||
case "DeEncrypt":
|
case "DeEncrypt":
|
||||||
@@ -143,16 +149,16 @@ namespace FSI.BT.Tools.Commands
|
|||||||
case "TxtToClip":
|
case "TxtToClip":
|
||||||
return Global.AppSettings.TxtToClip != null;
|
return Global.AppSettings.TxtToClip != null;
|
||||||
|
|
||||||
|
case "Rdp.Mgt":
|
||||||
|
return Global.AppSettings.Rdps != null;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
foreach (ICmd cmd in cmds)
|
foreach (ICmd cmd in cmds)
|
||||||
{
|
{
|
||||||
if (String.Equals(parameter.ToString().ToLower(), cmd.Cmd.ToLower()))
|
if (String.Equals(parameter.ToString(), cmd.Cmd))
|
||||||
{
|
|
||||||
selectedCmd = cmd;
|
selectedCmd = cmd;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedCmd == null)
|
if (selectedCmd == null)
|
||||||
@@ -160,26 +166,24 @@ namespace FSI.BT.Tools.Commands
|
|||||||
|
|
||||||
foreach (var file in selectedCmd.Exe.ToList())
|
foreach (var file in selectedCmd.Exe.ToList())
|
||||||
{
|
{
|
||||||
|
|
||||||
if (File.Exists(Environment.ExpandEnvironmentVariables(file.ExePath.Trim())))
|
if (File.Exists(Environment.ExpandEnvironmentVariables(file.ExePath.Trim())))
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
else if (File.Exists(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), file.ExePath.Trim())))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var url in selectedCmd.Urls)
|
foreach (var url in selectedCmd.Urls)
|
||||||
{
|
{
|
||||||
if (url != String.Empty)
|
if (url != String.Empty)
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OpenExe(ICmd selectedCmd)
|
||||||
private static void OpenExe(ICmd selectedCmd)
|
{
|
||||||
{
|
|
||||||
IExe selectedFile = GetApp(selectedCmd.Exe);
|
IExe selectedFile = GetApp(selectedCmd.Exe);
|
||||||
|
|
||||||
if (selectedFile == null)
|
if (selectedFile == null)
|
||||||
@@ -196,8 +200,8 @@ namespace FSI.BT.Tools.Commands
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Process process = new();
|
Process process = new();
|
||||||
process.StartInfo.FileName = selectedFile.ExePath;
|
process.StartInfo.FileName = Environment.ExpandEnvironmentVariables(selectedFile.ExePath);
|
||||||
process.StartInfo.WorkingDirectory = selectedFile.Path == null ? selectedFile.Path : Path.GetDirectoryName(selectedFile.ExePath);
|
process.StartInfo.WorkingDirectory = selectedFile.Path ?? Path.GetDirectoryName(Environment.ExpandEnvironmentVariables(selectedFile.ExePath));
|
||||||
process.StartInfo.Arguments = selectedFile.Arguments;
|
process.StartInfo.Arguments = selectedFile.Arguments;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -237,7 +241,20 @@ namespace FSI.BT.Tools.Commands
|
|||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IExe GetExeByCmdName(string cmdName)
|
||||||
|
{
|
||||||
|
foreach (var cmd in Global.AppSettings.Cmds)
|
||||||
|
{
|
||||||
|
if (string.Equals(cmd.Cmd, cmdName, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return GetApp(cmd.Exe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool ProgramIsRunning(string FullPath)
|
private static bool ProgramIsRunning(string FullPath)
|
||||||
{
|
{
|
||||||
string FilePath = Path.GetDirectoryName(FullPath);
|
string FilePath = Path.GetDirectoryName(FullPath);
|
||||||
@@ -260,7 +277,7 @@ namespace FSI.BT.Tools.Commands
|
|||||||
|
|
||||||
private static IExe GetApp(IEnumerable<IExe> files)
|
private static IExe GetApp(IEnumerable<IExe> files)
|
||||||
{
|
{
|
||||||
if(files.ToList().Count == 0)
|
if (files.ToList().Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var selectedFile = files.ToList()[0];
|
var selectedFile = files.ToList()[0];
|
||||||
|
|||||||
BIN
FSI.BT.Tools/ExtTools/kalk.zip
Normal file
BIN
FSI.BT.Tools/ExtTools/kalk.zip
Normal file
Binary file not shown.
@@ -1,22 +1,35 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||||
<ApplicationIcon>Icons\FondiumU.ico</ApplicationIcon>
|
<ApplicationIcon>Icons\FondiumU.ico</ApplicationIcon>
|
||||||
<AssemblyVersion>2.0</AssemblyVersion>
|
<AssemblyVersion>2.0</AssemblyVersion>
|
||||||
|
<Version>2.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Icons\Folders.png" />
|
<Compile Remove="ExtTools\kalk\**" />
|
||||||
<None Remove="Icons\Iba.jpg" />
|
<EmbeddedResource Remove="ExtTools\kalk\**" />
|
||||||
<None Remove="Icons\txt.png" />
|
<None Remove="ExtTools\kalk\**" />
|
||||||
|
<Page Remove="ExtTools\kalk\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="ExtTools\kalk.zip" />
|
||||||
|
<None Remove="Icons\Folders.png" />
|
||||||
|
<None Remove="Icons\Iba.jpg" />
|
||||||
|
<None Remove="Icons\txt.png" />
|
||||||
|
<None Remove="Icons\VolDown.png" />
|
||||||
|
<None Remove="Icons\VolOff.png" />
|
||||||
|
<None Remove="Icons\VolOn.png" />
|
||||||
|
<None Remove="Icons\VolUp.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -59,10 +72,24 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Icons\txt.png" />
|
<EmbeddedResource Include="ExtTools\kalk.zip" />
|
||||||
|
<Resource Include="Icons\VolDown.png" />
|
||||||
|
<Resource Include="Icons\VolOff.png" />
|
||||||
|
<Resource Include="Icons\VolOn.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.6" />
|
||||||
|
<PackageReference Include="TextCopy" Version="6.2.1" />
|
||||||
|
<PackageReference Include="DotNetZip" Version="1.16.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="Icons\txt.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AutoCompleteTextBox\AutoCompleteTextBox.csproj" />
|
||||||
<ProjectReference Include="..\Config.Net\Config.Net.csproj" />
|
<ProjectReference Include="..\Config.Net\Config.Net.csproj" />
|
||||||
<ProjectReference Include="..\FSI.Lib\FSI.Lib\FSI.Lib.csproj" />
|
<ProjectReference Include="..\FSI.Lib\FSI.Lib\FSI.Lib.csproj" />
|
||||||
<ProjectReference Include="..\NHotkey\NHotkey.Wpf\NHotkey.Wpf.csproj" />
|
<ProjectReference Include="..\NHotkey\NHotkey.Wpf\NHotkey.Wpf.csproj" />
|
||||||
@@ -73,7 +100,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Icons\1087815.png">
|
<Resource Include="Icons\1087815.png">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</Resource>
|
</Resource>
|
||||||
<Resource Include="Icons\Admin.jpg" />
|
<Resource Include="Icons\Admin.jpg" />
|
||||||
<Resource Include="Icons\Apps.png" />
|
<Resource Include="Icons\Apps.png" />
|
||||||
@@ -109,6 +136,7 @@
|
|||||||
<Resource Include="Icons\TotalCmd.jfif" />
|
<Resource Include="Icons\TotalCmd.jfif" />
|
||||||
<Resource Include="Icons\Vnc.png" />
|
<Resource Include="Icons\Vnc.png" />
|
||||||
<Resource Include="Icons\VncAdrBook.png" />
|
<Resource Include="Icons\VncAdrBook.png" />
|
||||||
|
<Resource Include="Icons\VolUp.png" />
|
||||||
<Resource Include="Icons\Vs.png" />
|
<Resource Include="Icons\Vs.png" />
|
||||||
<Resource Include="Icons\VsCode.png" />
|
<Resource Include="Icons\VsCode.png" />
|
||||||
<Resource Include="Icons\TeXstudio.png" />
|
<Resource Include="Icons\TeXstudio.png" />
|
||||||
@@ -116,14 +144,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="config.json">
|
<None Update="config.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="nlog.config">
|
<None Update="nlog.config">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ProjectExtensions><VisualStudio><UserProperties config_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
|
|
||||||
|
<ProjectExtensions>
|
||||||
|
<VisualStudio>
|
||||||
|
<UserProperties config_1json__JsonSchema="" />
|
||||||
|
</VisualStudio>
|
||||||
|
</ProjectExtensions>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
<Window x:Class="FSI.BT.Tools.FrmRadialMenu"
|
<Window x:Class="FSI.BT.Tools.FrmRadialMenu"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:FSI.BT.Tools"
|
||||||
xmlns:RadialMenu="clr-namespace:RadialMenu.Controls;assembly=RadialMenu"
|
xmlns:RadialMenu="clr-namespace:RadialMenu.Controls;assembly=RadialMenu"
|
||||||
|
xmlns:RadialMenuProvider="clr-namespace:FSI.BT.Tools.RadialMenu"
|
||||||
xmlns:commands="clr-namespace:FSI.BT.Tools.Commands"
|
xmlns:commands="clr-namespace:FSI.BT.Tools.Commands"
|
||||||
|
xmlns:wpfCtrl="http://wpfcontrols.com/"
|
||||||
SizeToContent="WidthAndHeight"
|
SizeToContent="WidthAndHeight"
|
||||||
WindowStyle="None"
|
WindowStyle="None"
|
||||||
ShowInTaskbar="False"
|
ShowInTaskbar="False"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Deactivated="Window_Deactivated"
|
Deactivated="Window_Deactivated"
|
||||||
|
Activated="Window_Activated"
|
||||||
Loaded="Window_Loaded">
|
Loaded="Window_Loaded">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
@@ -39,6 +43,7 @@
|
|||||||
FontSize="10" />
|
FontSize="10" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
<RadialMenu:RadialMenu IsOpen="{Binding IsOpenHome}">
|
<RadialMenu:RadialMenu IsOpen="{Binding IsOpenHome}">
|
||||||
|
|
||||||
<RadialMenu:RadialMenu.CentralItem>
|
<RadialMenu:RadialMenu.CentralItem>
|
||||||
@@ -207,7 +212,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="EplPrj">
|
CommandParameter="Epl.Prj">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="35"
|
<Rectangle Width="35"
|
||||||
Height="35">
|
Height="35">
|
||||||
@@ -223,7 +228,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="EplPdf">
|
CommandParameter="Epl.Pdf">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="35"
|
<Rectangle Width="35"
|
||||||
Height="35">
|
Height="35">
|
||||||
@@ -239,7 +244,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="EplPdfMgt">
|
CommandParameter="Epl.PdfMgt">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="35"
|
<Rectangle Width="35"
|
||||||
Height="35">
|
Height="35">
|
||||||
@@ -728,7 +733,7 @@
|
|||||||
</RadialMenu:RadialMenu.CentralItem>
|
</RadialMenu:RadialMenu.CentralItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL1Pls">
|
CommandParameter="PL1.Pls">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -744,7 +749,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL1Lst">
|
CommandParameter="PL1.Lst">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -775,7 +780,7 @@
|
|||||||
</RadialMenu:RadialMenu.CentralItem>
|
</RadialMenu:RadialMenu.CentralItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL2Alg">
|
CommandParameter="PL2.Alg">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -792,7 +797,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL2Pls">
|
CommandParameter="PL2.Pls">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -808,7 +813,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL2Als">
|
CommandParameter="PL2.Als">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -824,7 +829,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL2Lst">
|
CommandParameter="PL2.Lst">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -840,7 +845,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL2Nc">
|
CommandParameter="PL2.Nc">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -856,7 +861,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL2Key">
|
CommandParameter="PL2.Key">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -887,7 +892,7 @@
|
|||||||
</RadialMenu:RadialMenu.CentralItem>
|
</RadialMenu:RadialMenu.CentralItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL3Pls">
|
CommandParameter="PL3.Pls">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -903,7 +908,7 @@
|
|||||||
</RadialMenu:RadialMenuItem>
|
</RadialMenu:RadialMenuItem>
|
||||||
|
|
||||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||||
CommandParameter="PL3Lst">
|
CommandParameter="PL3.Lst">
|
||||||
<WrapPanel Orientation="Vertical">
|
<WrapPanel Orientation="Vertical">
|
||||||
<Rectangle Width="30"
|
<Rectangle Width="30"
|
||||||
Height="30">
|
Height="30">
|
||||||
@@ -1109,19 +1114,75 @@
|
|||||||
|
|
||||||
</RadialMenu:RadialMenu>
|
</RadialMenu:RadialMenu>
|
||||||
|
|
||||||
|
|
||||||
|
<Grid HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="15" />
|
||||||
|
<ColumnDefinition Width="20" />
|
||||||
|
<ColumnDefinition Width="15" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="15" />
|
||||||
|
<RowDefinition Height="15" />
|
||||||
|
<RowDefinition Height="15" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Button x:Name="btnVolUp"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
Click="btnVolUp_Click" />
|
||||||
|
|
||||||
|
<Button x:Name="btnVolDwn"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
Click="btnVolDwn_Click" />
|
||||||
|
|
||||||
|
<Button x:Name="btnMute"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
Click="btnMute_Click" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Width="300"
|
<Grid Width="200"
|
||||||
Height="50"
|
Height="50"
|
||||||
Canvas.Bottom="0">
|
Canvas.Bottom="0"
|
||||||
|
Canvas.Left="50">
|
||||||
<StackPanel Width="Auto"
|
<StackPanel Width="Auto"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Margin="5 5 5 5 ">
|
Margin="5 5 5 5 ">
|
||||||
|
|
||||||
<TextBox x:Name="tbCmd"
|
<wpfCtrl:AutoCompleteTextBox x:Name="tbCmd"
|
||||||
|
FontSize="20"
|
||||||
|
Background="White"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Provider="{Binding Provider}"
|
||||||
|
Text="{Binding Path=Cmd, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
KeyDown="tbCmd_KeyDown"
|
||||||
|
Watermark="Cmd eingeben">
|
||||||
|
</wpfCtrl:AutoCompleteTextBox>
|
||||||
|
<!--<TextBox x:Name="tbCmd"
|
||||||
FontSize="20"
|
FontSize="20"
|
||||||
KeyDown="tbCmd_KeyDown"
|
KeyDown="tbCmd_KeyDown"
|
||||||
TextChanged="tbCmd_TextChanged" />
|
TextChanged="tbCmd_TextChanged" />-->
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,429 +1,128 @@
|
|||||||
using FSI.BT.Tools.Commands;
|
using AutoCompleteTextBox.Editors;
|
||||||
|
using FSI.BT.Tools.Commands;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace FSI.BT.Tools
|
namespace FSI.BT.Tools
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for MainWindow.xaml
|
/// Interaction logic for MainWindow.xaml
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class FrmRadialMenu : Window, INotifyPropertyChanged
|
public partial class FrmRadialMenu : Window//, INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private CmdCommand _cmd;
|
private CmdCommand _cmd;
|
||||||
|
|
||||||
public FrmRadialMenu()
|
public FrmRadialMenu()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = this;
|
|
||||||
_isOpenHome = true;
|
List<string> cmds = new List<string>();
|
||||||
|
foreach(var cmd in Global.AppSettings.Cmds)
|
||||||
|
{
|
||||||
|
cmds.Add(cmd.Cmd);
|
||||||
|
}
|
||||||
|
DataContext = new RadialMenu.MainViewModel(this, cmds);
|
||||||
|
|
||||||
tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor + "b";
|
tbversion.Text = "v" + Assembly.GetExecutingAssembly().GetName().Version.Major + "." + Assembly.GetExecutingAssembly().GetName().Version.Minor + "b";
|
||||||
_cmd = new ();
|
_cmd = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Home
|
private void Window_Activated(object sender, EventArgs e)
|
||||||
private bool _isOpenHome = true;
|
|
||||||
public bool IsOpenHome
|
|
||||||
{
|
{
|
||||||
get
|
tbCmd.Focus();
|
||||||
{
|
|
||||||
return _isOpenHome;
|
ChangeBtnIcon();
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenHome = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand CloseRadialMenuHome
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() => Visibility = Visibility.Hidden);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuHome
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenHome = true;
|
|
||||||
IsOpenEpl =
|
|
||||||
IsOpenTools =
|
|
||||||
IsOpenSie =
|
|
||||||
IsOpenApps =
|
|
||||||
IsOpenPlantLinksPl1 =
|
|
||||||
IsOpenPlantLinksPl2 =
|
|
||||||
IsOpenPlantLinksPl3 =
|
|
||||||
IsOpenAppsVncRdp =
|
|
||||||
IsOpenLinks = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Epl
|
|
||||||
|
|
||||||
private bool _isOpenEpl = false;
|
|
||||||
public bool IsOpenEpl
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenEpl;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenEpl = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuEpl
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenEpl = true;
|
|
||||||
IsOpenHome = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Tools
|
|
||||||
|
|
||||||
private bool _isOpenTools = false;
|
|
||||||
public bool IsOpenTools
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenTools;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenTools = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuTools
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenTools = true;
|
|
||||||
IsOpenHome = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Siemens
|
|
||||||
|
|
||||||
private bool _isOpenSie = false;
|
|
||||||
public bool IsOpenSie
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenSie;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenSie = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuSie
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenSie = true;
|
|
||||||
IsOpenHome = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Links
|
|
||||||
|
|
||||||
private bool _isOpenLinks = false;
|
|
||||||
public bool IsOpenLinks
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenLinks;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenLinks = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuLinks
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenLinks = true;
|
|
||||||
IsOpenPlantLinks =
|
|
||||||
IsOpenHome = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Anlagen Links
|
|
||||||
|
|
||||||
private bool _isOpenPlantLinks = false;
|
|
||||||
public bool IsOpenPlantLinks
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenPlantLinks;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenPlantLinks = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuPlantLinks
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenPlantLinks = true;
|
|
||||||
IsOpenPlantLinksPl1 =
|
|
||||||
IsOpenPlantLinksPl2 =
|
|
||||||
IsOpenPlantLinksPl3 =
|
|
||||||
IsOpenLinks = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Anlagen Links Pl1
|
|
||||||
|
|
||||||
private bool _isOpenPlantLinksPl1 = false;
|
|
||||||
public bool IsOpenPlantLinksPl1
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenPlantLinksPl1;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenPlantLinksPl1 = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuPlantLinksPl1
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenPlantLinksPl1 = true;
|
|
||||||
IsOpenPlantLinks = false;
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Anlagen Links Pl2
|
|
||||||
|
|
||||||
private bool _isOpenPlantLinksPl2 = false;
|
|
||||||
public bool IsOpenPlantLinksPl2
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenPlantLinksPl2;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenPlantLinksPl2 = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuPlantLinksPl2
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenPlantLinksPl2 = true;
|
|
||||||
IsOpenPlantLinks = false;
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Anlagen Links Pl3
|
|
||||||
|
|
||||||
private bool _isOpenPlantLinksPl3 = false;
|
|
||||||
public bool IsOpenPlantLinksPl3
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenPlantLinksPl3;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenPlantLinksPl3 = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuPlantLinksPl3
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenPlantLinksPl3 = true;
|
|
||||||
IsOpenPlantLinks = false;
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Apps
|
|
||||||
|
|
||||||
private bool _isOpenApps = false;
|
|
||||||
public bool IsOpenApps
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenApps;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenApps = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuApps
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenApps = true;
|
|
||||||
IsOpenAppsVncRdp =
|
|
||||||
IsOpenHome = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Apps RDP VNC
|
|
||||||
|
|
||||||
private bool _isOpenAppsVncRdp = false;
|
|
||||||
public bool IsOpenAppsVncRdp
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _isOpenAppsVncRdp;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isOpenAppsVncRdp = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand OpenRadialMenuAppsVncRdp
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return new RelayCommand(() =>
|
|
||||||
{
|
|
||||||
IsOpenAppsVncRdp = true;
|
|
||||||
IsOpenApps = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
void RaisePropertyChanged([CallerMemberName] string propertyName = null)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Deactivated(object sender, EventArgs e)
|
private void Window_Deactivated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
tbCmd.Text = String.Empty;
|
tbCmd.Text = String.Empty;
|
||||||
tbCmd.Focus();
|
tbCmd.Focus();
|
||||||
Visibility = Visibility.Hidden;
|
|
||||||
|
|
||||||
IsOpenHome = true;
|
|
||||||
IsOpenEpl =
|
|
||||||
IsOpenTools =
|
|
||||||
IsOpenSie =
|
|
||||||
IsOpenLinks =
|
|
||||||
IsOpenApps =
|
|
||||||
IsOpenPlantLinksPl1 =
|
|
||||||
IsOpenPlantLinksPl2 =
|
|
||||||
IsOpenPlantLinksPl3 =
|
|
||||||
IsOpenAppsVncRdp =
|
|
||||||
IsOpenPlantLinks = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
tbCmd.Focus();
|
ChangeBtnIcon();
|
||||||
|
|
||||||
|
tbCmd.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tbCmd_KeyDown(object sender, KeyEventArgs e)
|
private void tbCmd_KeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key.Enter && _cmd.CanExecute(((TextBox)sender).Text))
|
if (e.Key == Key.Enter && _cmd.CanExecute(tbCmd.Text))
|
||||||
{
|
{
|
||||||
_cmd.Execute(((TextBox)sender).Text);
|
_cmd.Execute(tbCmd.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tbCmd_TextChanged(object sender, TextChangedEventArgs e)
|
private void tbCmd_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_cmd.CanExecute(((TextBox)sender).Text))
|
if (_cmd.CanExecute(((TextBox)sender).Text))
|
||||||
((TextBox)sender).Background = new SolidColorBrush(Colors.Green);
|
((TextBox)sender).Background = new SolidColorBrush(Colors.Green);
|
||||||
else
|
else
|
||||||
((TextBox)sender).Background = new SolidColorBrush(Colors.White);
|
((TextBox)sender).Background = new SolidColorBrush(Colors.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void btnMute_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Lib.Audio.AudioManager.SetMasterVolumeMute(!Lib.Audio.AudioManager.GetMasterVolumeMute());
|
||||||
|
ChangeBtnIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnVolUp_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Lib.Audio.AudioManager.StepMasterVolume(2F);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnVolDwn_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Lib.Audio.AudioManager.StepMasterVolume(-2F);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeBtnIcon()
|
||||||
|
{
|
||||||
|
if (FSI.Lib.Audio.AudioManager.GetMasterVolumeMute())
|
||||||
|
{
|
||||||
|
btnMute.Content = new System.Windows.Controls.Image
|
||||||
|
{
|
||||||
|
Source = new BitmapImage(new Uri("../../Icons/VolOff.png", UriKind.RelativeOrAbsolute)),
|
||||||
|
Width = 15,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
btnMute.Content = new System.Windows.Controls.Image
|
||||||
|
{
|
||||||
|
Source = new BitmapImage(new Uri("../../Icons/VolOn.png", UriKind.RelativeOrAbsolute)),
|
||||||
|
Width = 15,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
btnVolUp.Content = new System.Windows.Controls.Image
|
||||||
|
{
|
||||||
|
Source = new BitmapImage(new Uri("../../Icons/VolUp.png", UriKind.RelativeOrAbsolute)),
|
||||||
|
Width = 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
btnVolDwn.Content = new System.Windows.Controls.Image
|
||||||
|
{
|
||||||
|
Source = new BitmapImage(new Uri("../../Icons/VolDown.png", UriKind.RelativeOrAbsolute)),
|
||||||
|
Width = 15,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
FSI.BT.Tools/Icons/VolDown.png
Normal file
BIN
FSI.BT.Tools/Icons/VolDown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
FSI.BT.Tools/Icons/VolOff.png
Normal file
BIN
FSI.BT.Tools/Icons/VolOff.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
FSI.BT.Tools/Icons/VolOn.png
Normal file
BIN
FSI.BT.Tools/Icons/VolOn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
FSI.BT.Tools/Icons/VolUp.png
Normal file
BIN
FSI.BT.Tools/Icons/VolUp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
26
FSI.BT.Tools/RadialMenu/CmdProvider.cs
Normal file
26
FSI.BT.Tools/RadialMenu/CmdProvider.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using AutoCompleteTextBox.Editors;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FSI.BT.Tools.RadialMenu
|
||||||
|
{
|
||||||
|
public class CmdProvider : ISuggestionProvider
|
||||||
|
{
|
||||||
|
private readonly ObservableCollection<string> _cmds;
|
||||||
|
|
||||||
|
public IEnumerable GetSuggestions(string filter)
|
||||||
|
{
|
||||||
|
return _cmds.Where(x => x.StartsWith(filter, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdProvider(ref ObservableCollection<string> cmds)
|
||||||
|
{
|
||||||
|
this._cmds = cmds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
442
FSI.BT.Tools/RadialMenu/MainViewModel.cs
Normal file
442
FSI.BT.Tools/RadialMenu/MainViewModel.cs
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
using FSI.Lib.MVVM;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace FSI.BT.Tools.RadialMenu
|
||||||
|
{
|
||||||
|
public class MainViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public MainViewModel(Window win, List<string> cmds)
|
||||||
|
{
|
||||||
|
_window = win;
|
||||||
|
_window.Deactivated += _window_Deactivated;
|
||||||
|
_isOpenHome = true;
|
||||||
|
|
||||||
|
Cmds = new();
|
||||||
|
|
||||||
|
if (cmds != null)
|
||||||
|
{
|
||||||
|
foreach (string cmd in cmds)
|
||||||
|
Cmds.Add(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cmds.Add("Rdp.Mgt");
|
||||||
|
Provider = new(ref _cmds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ObservableCollection<string> _cmds;
|
||||||
|
|
||||||
|
public ObservableCollection<string> Cmds
|
||||||
|
{
|
||||||
|
get { return _cmds; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_cmds = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CmdProvider _cmdProvider;
|
||||||
|
|
||||||
|
public CmdProvider Provider
|
||||||
|
{
|
||||||
|
get { return _cmdProvider; }
|
||||||
|
set { _cmdProvider = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private string _cmd;
|
||||||
|
|
||||||
|
public string Cmd
|
||||||
|
{
|
||||||
|
get { return _cmd; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_cmd = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Window _window;
|
||||||
|
|
||||||
|
public Window Window
|
||||||
|
{
|
||||||
|
get { return _window; }
|
||||||
|
set { _window = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Home
|
||||||
|
private bool _isOpenHome = true;
|
||||||
|
public bool IsOpenHome
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenHome;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenHome = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand CloseRadialMenuHome
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() => _window.Visibility = Visibility.Hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuHome
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenHome = true;
|
||||||
|
IsOpenEpl =
|
||||||
|
IsOpenTools =
|
||||||
|
IsOpenSie =
|
||||||
|
IsOpenApps =
|
||||||
|
IsOpenPlantLinksPl1 =
|
||||||
|
IsOpenPlantLinksPl2 =
|
||||||
|
IsOpenPlantLinksPl3 =
|
||||||
|
IsOpenAppsVncRdp =
|
||||||
|
IsOpenLinks = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Epl
|
||||||
|
|
||||||
|
private bool _isOpenEpl = false;
|
||||||
|
public bool IsOpenEpl
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenEpl;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenEpl = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuEpl
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenEpl = true;
|
||||||
|
IsOpenHome = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Tools
|
||||||
|
|
||||||
|
private bool _isOpenTools = false;
|
||||||
|
public bool IsOpenTools
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenTools;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenTools = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuTools
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenTools = true;
|
||||||
|
IsOpenHome = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Siemens
|
||||||
|
|
||||||
|
private bool _isOpenSie = false;
|
||||||
|
public bool IsOpenSie
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenSie;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenSie = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuSie
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenSie = true;
|
||||||
|
IsOpenHome = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Links
|
||||||
|
|
||||||
|
private bool _isOpenLinks = false;
|
||||||
|
public bool IsOpenLinks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenLinks;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenLinks = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuLinks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenLinks = true;
|
||||||
|
IsOpenPlantLinks =
|
||||||
|
IsOpenHome = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Anlagen Links
|
||||||
|
|
||||||
|
private bool _isOpenPlantLinks = false;
|
||||||
|
public bool IsOpenPlantLinks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenPlantLinks;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenPlantLinks = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuPlantLinks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenPlantLinks = true;
|
||||||
|
IsOpenPlantLinksPl1 =
|
||||||
|
IsOpenPlantLinksPl2 =
|
||||||
|
IsOpenPlantLinksPl3 =
|
||||||
|
IsOpenLinks = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Anlagen Links Pl1
|
||||||
|
|
||||||
|
private bool _isOpenPlantLinksPl1 = false;
|
||||||
|
public bool IsOpenPlantLinksPl1
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenPlantLinksPl1;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenPlantLinksPl1 = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuPlantLinksPl1
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenPlantLinksPl1 = true;
|
||||||
|
IsOpenPlantLinks = false;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Anlagen Links Pl2
|
||||||
|
|
||||||
|
private bool _isOpenPlantLinksPl2 = false;
|
||||||
|
public bool IsOpenPlantLinksPl2
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenPlantLinksPl2;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenPlantLinksPl2 = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuPlantLinksPl2
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenPlantLinksPl2 = true;
|
||||||
|
IsOpenPlantLinks = false;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Anlagen Links Pl3
|
||||||
|
|
||||||
|
private bool _isOpenPlantLinksPl3 = false;
|
||||||
|
public bool IsOpenPlantLinksPl3
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenPlantLinksPl3;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenPlantLinksPl3 = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuPlantLinksPl3
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenPlantLinksPl3 = true;
|
||||||
|
IsOpenPlantLinks = false;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Apps
|
||||||
|
|
||||||
|
private bool _isOpenApps = false;
|
||||||
|
public bool IsOpenApps
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenApps;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenApps = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuApps
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenApps = true;
|
||||||
|
IsOpenAppsVncRdp =
|
||||||
|
IsOpenHome = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Apps RDP VNC
|
||||||
|
|
||||||
|
private bool _isOpenAppsVncRdp = false;
|
||||||
|
public bool IsOpenAppsVncRdp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isOpenAppsVncRdp;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isOpenAppsVncRdp = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRadialMenuAppsVncRdp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
IsOpenAppsVncRdp = true;
|
||||||
|
IsOpenApps = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void _window_Deactivated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_window.Visibility = Visibility.Hidden;
|
||||||
|
|
||||||
|
IsOpenHome = true;
|
||||||
|
IsOpenEpl =
|
||||||
|
IsOpenTools =
|
||||||
|
IsOpenSie =
|
||||||
|
IsOpenLinks =
|
||||||
|
IsOpenApps =
|
||||||
|
IsOpenPlantLinksPl1 =
|
||||||
|
IsOpenPlantLinksPl2 =
|
||||||
|
IsOpenPlantLinksPl3 =
|
||||||
|
IsOpenAppsVncRdp =
|
||||||
|
IsOpenPlantLinks = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
FSI.BT.Tools/ResourceManager.cs
Normal file
6
FSI.BT.Tools/ResourceManager.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace FSI.BT.Tools
|
||||||
|
{
|
||||||
|
internal class ResourceManager
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ namespace FSI.BT.Tools.Settings
|
|||||||
IEnumerable<Folder.IFolder> Folders { get; }
|
IEnumerable<Folder.IFolder> Folders { get; }
|
||||||
|
|
||||||
IEnumerable<TxtToClip.ITxtToClip> TxtToClip { get; }
|
IEnumerable<TxtToClip.ITxtToClip> TxtToClip { get; }
|
||||||
|
IEnumerable<RdpMgt.IRdp> Rdps { get; }
|
||||||
|
|
||||||
Lib.Guis.SieTiaWinCCMsgMgt.IInterface WinCC { get; set; }
|
Lib.Guis.SieTiaWinCCMsgMgt.IInterface WinCC { get; set; }
|
||||||
|
|
||||||
|
|||||||
13
FSI.BT.Tools/Settings/RdpMgt.cs
Normal file
13
FSI.BT.Tools/Settings/RdpMgt.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FSI.BT.Tools.Settings
|
||||||
|
{
|
||||||
|
public class RdpMgt
|
||||||
|
{
|
||||||
|
public interface IRdp : Lib.Guis.Rdp.Mgt.IInterface { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -277,49 +277,49 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl1Pls",
|
"Cmd": "Pl1.Pls",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://10.10.200.2/SKPL1Web/index.aspx"
|
"http://10.10.200.2/SKPL1Web/index.aspx"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl1Lst",
|
"Cmd": "Pl1.Lst",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://desiaugetwf.fondium.org/web/Seiten/Leistungsdaten_FuG.aspx?Fkt=PL1"
|
"http://desiaugetwf.fondium.org/web/Seiten/Leistungsdaten_FuG.aspx?Fkt=PL1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl2Als",
|
"Cmd": "Pl2.Als",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://10.10.213.234:84/emb_1/index.html"
|
"http://10.10.213.234:84/emb_1/index.html"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl2Pls",
|
"Cmd": "Pl2.Pls",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://10.10.213.4/SKPL2Web/index.aspx"
|
"http://10.10.213.4/SKPL2Web/index.aspx"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl2Lst",
|
"Cmd": "Pl2.Lst",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://desiaugetwf/web/Seiten/Leistungsdaten_PL2.aspx"
|
"http://desiaugetwf/web/Seiten/Leistungsdaten_PL2.aspx"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl2Nc",
|
"Cmd": "Pl2.Nc",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://10.10.213.4/SKPL2Web/Seiten/Taktzeiten_PopUp.aspx"
|
"http://10.10.213.4/SKPL2Web/Seiten/Taktzeiten_PopUp.aspx"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl2Key",
|
"Cmd": "Pl2.Key",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://10.10.213.4/skkeymanager-pl2"
|
"http://10.10.213.4/skkeymanager-pl2"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Cmd": "Pl2Alg",
|
"Cmd": "Pl2.Alg",
|
||||||
"Urls": [
|
"Urls": [
|
||||||
"http://10.10.213.4/SKPL2Web/index.aspx",
|
"http://10.10.213.4/SKPL2Web/index.aspx",
|
||||||
"http://10.10.213.234:84/emb_1/index.html",
|
"http://10.10.213.234:84/emb_1/index.html",
|
||||||
@@ -355,6 +355,34 @@
|
|||||||
"Urls": [
|
"Urls": [
|
||||||
"https://mingle-portal.eu1.inforcloudsuite.com/FONDIUM_prd"
|
"https://mingle-portal.eu1.inforcloudsuite.com/FONDIUM_prd"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Cmd": "Epl.Pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Cmd": "Epl.Prj"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Cmd": "Rdp.Mgt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Cmd": "Epl.PdfMgt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Cmd": "Calc",
|
||||||
|
"Exe": [
|
||||||
|
{
|
||||||
|
"ExePath": "ExtTools\\kalk\\net6.0\\kalk.exe"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Cmd": "Iba.Pda",
|
||||||
|
"Exe": [
|
||||||
|
{
|
||||||
|
"ExePath": "C:\\Program Files (x86)\\iba\\ibaPDA\\Client\\ibaPda.exe"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Folders": [
|
"Folders": [
|
||||||
@@ -424,6 +452,20 @@
|
|||||||
"Txt": "\\\\fondium.org\\DESI$\\AUG_Abteilung\\Betriebstechnik\\EPL\\P8\\Data\\Projekte\\FSI\\"
|
"Txt": "\\\\fondium.org\\DESI$\\AUG_Abteilung\\Betriebstechnik\\EPL\\P8\\Data\\Projekte\\FSI\\"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"Rdps": [
|
||||||
|
{
|
||||||
|
"Plant": "PL1",
|
||||||
|
"SubPlant": "Alg",
|
||||||
|
"Description": "Programmier PC",
|
||||||
|
"Host": "10.10.200.28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Plant": "Alg",
|
||||||
|
"SubPlant": "Alg",
|
||||||
|
"Description": "Programmier Maier St.",
|
||||||
|
"Host": "10.10.199.92"
|
||||||
|
}
|
||||||
|
],
|
||||||
"WinCC": {
|
"WinCC": {
|
||||||
"AutoStart": false,
|
"AutoStart": false,
|
||||||
"UpdateIntervall": 10,
|
"UpdateIntervall": 10,
|
||||||
|
|||||||
608
FSI.Lib/FSI.Lib/Audio/AudioManager.cs
Normal file
608
FSI.Lib/FSI.Lib/Audio/AudioManager.cs
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace FSI.Lib.Audio
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls audio using the Windows CoreAudio API
|
||||||
|
/// from: http://stackoverflow.com/questions/14306048/controling-volume-mixer
|
||||||
|
/// and: http://netcoreaudio.codeplex.com/
|
||||||
|
/// </summary>
|
||||||
|
public static class AudioManager
|
||||||
|
{
|
||||||
|
#region Master Volume Manipulation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current master volume in scalar values (percentage)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>-1 in case of an error, if successful the value will be between 0 and 100</returns>
|
||||||
|
public static float GetMasterVolume()
|
||||||
|
{
|
||||||
|
IAudioEndpointVolume masterVol = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
masterVol = GetMasterVolumeObject();
|
||||||
|
if (masterVol == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
masterVol.GetMasterVolumeLevelScalar(out float volumeLevel);
|
||||||
|
return volumeLevel * 100;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (masterVol != null)
|
||||||
|
Marshal.ReleaseComObject(masterVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mute state of the master volume.
|
||||||
|
/// While the volume can be muted the <see cref="GetMasterVolume"/> will still return the pre-muted volume value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>false if not muted, true if volume is muted</returns>
|
||||||
|
public static bool GetMasterVolumeMute()
|
||||||
|
{
|
||||||
|
IAudioEndpointVolume masterVol = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
masterVol = GetMasterVolumeObject();
|
||||||
|
if (masterVol == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
masterVol.GetMute(out bool isMuted);
|
||||||
|
return isMuted;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (masterVol != null)
|
||||||
|
Marshal.ReleaseComObject(masterVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the master volume to a specific level
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newLevel">Value between 0 and 100 indicating the desired scalar value of the volume</param>
|
||||||
|
public static void SetMasterVolume(float newLevel)
|
||||||
|
{
|
||||||
|
IAudioEndpointVolume masterVol = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
masterVol = GetMasterVolumeObject();
|
||||||
|
if (masterVol == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
masterVol.SetMasterVolumeLevelScalar(newLevel / 100, Guid.Empty);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (masterVol != null)
|
||||||
|
Marshal.ReleaseComObject(masterVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increments or decrements the current volume level by the <see cref="stepAmount"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stepAmount">Value between -100 and 100 indicating the desired step amount. Use negative numbers to decrease
|
||||||
|
/// the volume and positive numbers to increase it.</param>
|
||||||
|
/// <returns>the new volume level assigned</returns>
|
||||||
|
public static float StepMasterVolume(float stepAmount)
|
||||||
|
{
|
||||||
|
IAudioEndpointVolume masterVol = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
masterVol = GetMasterVolumeObject();
|
||||||
|
if (masterVol == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
float stepAmountScaled = stepAmount / 100;
|
||||||
|
|
||||||
|
// Get the level
|
||||||
|
masterVol.GetMasterVolumeLevelScalar(out float volumeLevel);
|
||||||
|
|
||||||
|
// Calculate the new level
|
||||||
|
float newLevel = volumeLevel + stepAmountScaled;
|
||||||
|
newLevel = Math.Min(1, newLevel);
|
||||||
|
newLevel = Math.Max(0, newLevel);
|
||||||
|
|
||||||
|
masterVol.SetMasterVolumeLevelScalar(newLevel, Guid.Empty);
|
||||||
|
|
||||||
|
// Return the new volume level that was set
|
||||||
|
return newLevel * 100;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (masterVol != null)
|
||||||
|
Marshal.ReleaseComObject(masterVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mute or unmute the master volume
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMuted">true to mute the master volume, false to unmute</param>
|
||||||
|
public static void SetMasterVolumeMute(bool isMuted)
|
||||||
|
{
|
||||||
|
IAudioEndpointVolume masterVol = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
masterVol = GetMasterVolumeObject();
|
||||||
|
if (masterVol == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
masterVol.SetMute(isMuted, Guid.Empty);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (masterVol != null)
|
||||||
|
Marshal.ReleaseComObject(masterVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Switches between the master volume mute states depending on the current state
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>the current mute state, true if the volume was muted, false if unmuted</returns>
|
||||||
|
public static bool ToggleMasterVolumeMute()
|
||||||
|
{
|
||||||
|
IAudioEndpointVolume masterVol = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
masterVol = GetMasterVolumeObject();
|
||||||
|
if (masterVol == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
masterVol.GetMute(out bool isMuted);
|
||||||
|
masterVol.SetMute(!isMuted, Guid.Empty);
|
||||||
|
|
||||||
|
return !isMuted;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (masterVol != null)
|
||||||
|
Marshal.ReleaseComObject(masterVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IAudioEndpointVolume GetMasterVolumeObject()
|
||||||
|
{
|
||||||
|
IMMDeviceEnumerator deviceEnumerator = null;
|
||||||
|
IMMDevice speakers = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
|
||||||
|
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
|
||||||
|
|
||||||
|
Guid IID_IAudioEndpointVolume = typeof(IAudioEndpointVolume).GUID;
|
||||||
|
speakers.Activate(ref IID_IAudioEndpointVolume, 0, IntPtr.Zero, out object o);
|
||||||
|
IAudioEndpointVolume masterVol = (IAudioEndpointVolume)o;
|
||||||
|
|
||||||
|
return masterVol;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (speakers != null) Marshal.ReleaseComObject(speakers);
|
||||||
|
if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Individual Application Volume Manipulation
|
||||||
|
|
||||||
|
public static float? GetApplicationVolume(int pid)
|
||||||
|
{
|
||||||
|
ISimpleAudioVolume volume = GetVolumeObject(pid);
|
||||||
|
if (volume == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
volume.GetMasterVolume(out float level);
|
||||||
|
Marshal.ReleaseComObject(volume);
|
||||||
|
return level * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool? GetApplicationMute(int pid)
|
||||||
|
{
|
||||||
|
ISimpleAudioVolume volume = GetVolumeObject(pid);
|
||||||
|
if (volume == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
volume.GetMute(out bool mute);
|
||||||
|
Marshal.ReleaseComObject(volume);
|
||||||
|
return mute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetApplicationVolume(int pid, float level)
|
||||||
|
{
|
||||||
|
ISimpleAudioVolume volume = GetVolumeObject(pid);
|
||||||
|
if (volume == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Guid guid = Guid.Empty;
|
||||||
|
volume.SetMasterVolume(level / 100, ref guid);
|
||||||
|
Marshal.ReleaseComObject(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetApplicationMute(int pid, bool mute)
|
||||||
|
{
|
||||||
|
ISimpleAudioVolume volume = GetVolumeObject(pid);
|
||||||
|
if (volume == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Guid guid = Guid.Empty;
|
||||||
|
volume.SetMute(mute, ref guid);
|
||||||
|
Marshal.ReleaseComObject(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISimpleAudioVolume GetVolumeObject(int pid)
|
||||||
|
{
|
||||||
|
IMMDeviceEnumerator deviceEnumerator = null;
|
||||||
|
IAudioSessionEnumerator sessionEnumerator = null;
|
||||||
|
IAudioSessionManager2 mgr = null;
|
||||||
|
IMMDevice speakers = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// get the speakers (1st render + multimedia) device
|
||||||
|
deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
|
||||||
|
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
|
||||||
|
|
||||||
|
// activate the session manager. we need the enumerator
|
||||||
|
Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID;
|
||||||
|
speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out object o);
|
||||||
|
mgr = (IAudioSessionManager2)o;
|
||||||
|
|
||||||
|
// enumerate sessions for on this device
|
||||||
|
mgr.GetSessionEnumerator(out sessionEnumerator);
|
||||||
|
sessionEnumerator.GetCount(out int count);
|
||||||
|
|
||||||
|
// search for an audio session with the required process-id
|
||||||
|
ISimpleAudioVolume volumeControl = null;
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
IAudioSessionControl2 ctl = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sessionEnumerator.GetSession(i, out ctl);
|
||||||
|
|
||||||
|
// NOTE: we could also use the app name from ctl.GetDisplayName()
|
||||||
|
ctl.GetProcessId(out int cpid);
|
||||||
|
|
||||||
|
if (cpid == pid)
|
||||||
|
{
|
||||||
|
volumeControl = ctl as ISimpleAudioVolume;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ctl != null) Marshal.ReleaseComObject(ctl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumeControl;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (sessionEnumerator != null) Marshal.ReleaseComObject(sessionEnumerator);
|
||||||
|
if (mgr != null) Marshal.ReleaseComObject(mgr);
|
||||||
|
if (speakers != null) Marshal.ReleaseComObject(speakers);
|
||||||
|
if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Abstracted COM interfaces from Windows CoreAudio API
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||||
|
internal class MMDeviceEnumerator
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum EDataFlow
|
||||||
|
{
|
||||||
|
eRender,
|
||||||
|
eCapture,
|
||||||
|
eAll,
|
||||||
|
EDataFlow_enum_count
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ERole
|
||||||
|
{
|
||||||
|
eConsole,
|
||||||
|
eMultimedia,
|
||||||
|
eCommunications,
|
||||||
|
ERole_enum_count
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IMMDeviceEnumerator
|
||||||
|
{
|
||||||
|
int NotImpl1();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
|
||||||
|
|
||||||
|
// the rest is not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IMMDevice
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
||||||
|
|
||||||
|
// the rest is not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAudioSessionManager2
|
||||||
|
{
|
||||||
|
int NotImpl1();
|
||||||
|
int NotImpl2();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);
|
||||||
|
|
||||||
|
// the rest is not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAudioSessionEnumerator
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int GetCount(out int SessionCount);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSession(int SessionCount, out IAudioSessionControl2 Session);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface ISimpleAudioVolume
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMasterVolume(float fLevel, ref Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMasterVolume(out float pfLevel);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMute(bool bMute, ref Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMute(out bool pbMute);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAudioSessionControl2
|
||||||
|
{
|
||||||
|
// IAudioSessionControl
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl0();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetGroupingParam(out Guid pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl1();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl2();
|
||||||
|
|
||||||
|
// IAudioSessionControl2
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetProcessId(out int pRetVal);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int IsSystemSoundsSession();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int SetDuckingPreference(bool optOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://netcoreaudio.codeplex.com/SourceControl/latest#trunk/Code/CoreAudio/Interfaces/IAudioEndpointVolume.cs
|
||||||
|
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface IAudioEndpointVolume
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl1();
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int NotImpl2();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a count of the channels in the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelCount">The number of channels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetChannelCount(
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 channelCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the master volume level of the audio stream, in decibels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The new master volume level in decibels.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMasterVolumeLevel(
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the master volume level, expressed as a normalized, audio-tapered value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The new master volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMasterVolumeLevelScalar(
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the master volume level of the audio stream, in decibels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The volume level in decibels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMasterVolumeLevel(
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the master volume level, expressed as a normalized, audio-tapered value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMasterVolumeLevelScalar(
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the volume level, in decibels, of the specified channel of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The channel number.</param>
|
||||||
|
/// <param name="level">The new volume level in decibels.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetChannelVolumeLevel(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the normalized, audio-tapered volume level of the specified channel in the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The channel number.</param>
|
||||||
|
/// <param name="level">The new master volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetChannelVolumeLevelScalar(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[In][MarshalAs(UnmanagedType.R4)] float level,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the volume level, in decibels, of the specified channel in the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The zero-based channel number.</param>
|
||||||
|
/// <param name="level">The volume level in decibels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetChannelVolumeLevel(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the normalized, audio-tapered volume level of the specified channel of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelNumber">The zero-based channel number.</param>
|
||||||
|
/// <param name="level">The volume level expressed as a normalized value between 0.0 and 1.0.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetChannelVolumeLevelScalar(
|
||||||
|
[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float level);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the muting state of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMuted">True to mute the stream, or false to unmute the stream.</param>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int SetMute(
|
||||||
|
[In][MarshalAs(UnmanagedType.Bool)] Boolean isMuted,
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the muting state of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMuted">The muting state. True if the stream is muted, false otherwise.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetMute(
|
||||||
|
[Out][MarshalAs(UnmanagedType.Bool)] out Boolean isMuted);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about the current step in the volume range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="step">The current zero-based step index.</param>
|
||||||
|
/// <param name="stepCount">The total number of steps in the volume range.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetVolumeStepInfo(
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 step,
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 stepCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increases the volume level by one step.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int VolumeStepUp(
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decreases the volume level by one step.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventContext">A user context value that is passed to the notification callback.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int VolumeStepDown(
|
||||||
|
[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries the audio endpoint device for its hardware-supported functions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hardwareSupportMask">A hardware support mask that indicates the capabilities of the endpoint.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int QueryHardwareSupport(
|
||||||
|
[Out][MarshalAs(UnmanagedType.U4)] out UInt32 hardwareSupportMask);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the volume range of the audio stream, in decibels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volumeMin">The minimum volume level in decibels.</param>
|
||||||
|
/// <param name="volumeMax">The maximum volume level in decibels.</param>
|
||||||
|
/// <param name="volumeStep">The volume increment level in decibels.</param>
|
||||||
|
/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns>
|
||||||
|
[PreserveSig]
|
||||||
|
int GetVolumeRange(
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float volumeMin,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float volumeMax,
|
||||||
|
[Out][MarshalAs(UnmanagedType.R4)] out float volumeStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||||
<AssemblyVersion>3.0</AssemblyVersion>
|
<AssemblyVersion>3.0</AssemblyVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||||
<PackageReference Include="TextCopy" Version="6.2.1" />
|
<PackageReference Include="TextCopy" Version="6.2.1" />
|
||||||
|
<PackageReference Include="System.ComponentModel.Composition" Version="7.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\RoboSharp\RoboSharp.csproj" />
|
<ProjectReference Include="..\..\RoboSharp\RoboSharp.csproj" />
|
||||||
|
|||||||
181
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/FrmMain.xaml
Normal file
181
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/FrmMain.xaml
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<Window x:Class="FSI.Lib.Guis.Rdp.Mgt.FrmMain"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:FSI.Lib.Guis.Rdp.Mgt"
|
||||||
|
xmlns:control="clr-namespace:FSI.Lib.Wpf.Ctrls.FilterDataGrid"
|
||||||
|
d:DataContext="{d:DesignInstance Type=local:ViewModel}"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
SizeToContent="Width"
|
||||||
|
Height="800"
|
||||||
|
Width="Auto"
|
||||||
|
Icon="../../Icons/FondiumU.ico">
|
||||||
|
<Window.Resources>
|
||||||
|
<ObjectDataProvider x:Key="FilteredData"></ObjectDataProvider>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Window.InputBindings>
|
||||||
|
<KeyBinding Command="{Binding CmdOpen}"
|
||||||
|
Gesture="CTRL+o" />
|
||||||
|
</Window.InputBindings>
|
||||||
|
|
||||||
|
<Grid FocusManager.FocusedElement="{Binding ElementName=tbSearch}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
Margin="0,10"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="2*" />
|
||||||
|
<ColumnDefinition Width="1*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Label Margin="0,0,20,0"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Content="Suche:"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
<TextBox Name="tbSearch"
|
||||||
|
Height="26"
|
||||||
|
MinWidth="200"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Text="{Binding Search, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextChanged="tbSearch_TextChanged" />
|
||||||
|
<Button Width="Auto"
|
||||||
|
Margin="5,0,0,0"
|
||||||
|
Padding="4"
|
||||||
|
Command="{Binding RefreshCommand}"
|
||||||
|
ToolTip="Filter löschen"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Image Source="../../Icons/Cross.png"
|
||||||
|
Height="14" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button Width="Auto"
|
||||||
|
Margin="0 0 20 0"
|
||||||
|
Padding="4"
|
||||||
|
ToolTip="öffen"
|
||||||
|
Command="{Binding CmdOpen}"
|
||||||
|
IsEnabled="{Binding CanExecuteOpen}"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Image Source="../../Icons/Open.png"
|
||||||
|
Height="14" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="0,5">
|
||||||
|
<Label Margin="0,0,20,0"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Content="Quick-Filter:"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
<Button Width="Auto"
|
||||||
|
Margin="5,0,0,0"
|
||||||
|
Padding="4"
|
||||||
|
ToolTip="Filter löschen"
|
||||||
|
Command="{Binding RefreshCommand}"
|
||||||
|
Cursor="Hand">
|
||||||
|
<Image Source="../../Icons/Cross.png"
|
||||||
|
Height="14" />
|
||||||
|
</Button>
|
||||||
|
<Button Content="Alg"
|
||||||
|
Width="Auto"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
ToolTip="Filter auf Alg"
|
||||||
|
Command="{Binding CmdQuickSearch}"
|
||||||
|
CommandParameter="Alg"
|
||||||
|
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||||
|
<Button Content="PL1"
|
||||||
|
Width="Auto"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
ToolTip="Filter auf PL1"
|
||||||
|
Command="{Binding CmdQuickSearch}"
|
||||||
|
CommandParameter="PL1"
|
||||||
|
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||||
|
<Button Content="PL2"
|
||||||
|
Width="Auto"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
ToolTip="Filter auf PL2"
|
||||||
|
Command="{Binding CmdQuickSearch}"
|
||||||
|
CommandParameter="PL2"
|
||||||
|
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||||
|
<Button Content="PL3"
|
||||||
|
Width="Auto"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
ToolTip="Filter auf PL3"
|
||||||
|
Command="{Binding CmdQuickSearch}"
|
||||||
|
CommandParameter="PL3"
|
||||||
|
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||||
|
<Button Content="SMZ"
|
||||||
|
Width="Auto"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
ToolTip="Filter auf SMZ"
|
||||||
|
Command="{Binding CmdQuickSearch}"
|
||||||
|
CommandParameter="SMZ"
|
||||||
|
FocusManager.FocusedElement="{Binding ElementName=tbSearch}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<control:FilterDataGrid x:Name="FilterDataGrid"
|
||||||
|
Grid.Row="2"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
DateFormatString="d"
|
||||||
|
FilterLanguage="German"
|
||||||
|
ItemsSource="{Binding FilteredData, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectedItem="{Binding SelectedData, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
ShowElapsedTime="false"
|
||||||
|
ShowRowsCount="True"
|
||||||
|
ShowStatusBar="True">
|
||||||
|
|
||||||
|
<control:FilterDataGrid.InputBindings>
|
||||||
|
<MouseBinding MouseAction="RightDoubleClick"
|
||||||
|
Command="{Binding CmdCopyToClip}" />
|
||||||
|
|
||||||
|
</control:FilterDataGrid.InputBindings>
|
||||||
|
<control:FilterDataGrid.Columns>
|
||||||
|
|
||||||
|
<control:DataGridTemplateColumn FieldName="Plant"
|
||||||
|
Header="Anlage"
|
||||||
|
IsColumnFiltered="True"
|
||||||
|
SortMemberPath="Plant">
|
||||||
|
<control:DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate DataType="local:Interface">
|
||||||
|
<TextBlock Text="{Binding Plant}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</control:DataGridTemplateColumn.CellTemplate>
|
||||||
|
</control:DataGridTemplateColumn>
|
||||||
|
|
||||||
|
<control:DataGridTextColumn Binding="{Binding SubPlant}"
|
||||||
|
Header="Teilanlagen"
|
||||||
|
IsReadOnly="True"
|
||||||
|
IsColumnFiltered="True" />
|
||||||
|
|
||||||
|
<control:DataGridTextColumn Binding="{Binding Description}"
|
||||||
|
Header="Beschreibung"
|
||||||
|
IsReadOnly="True"
|
||||||
|
IsColumnFiltered="True" />
|
||||||
|
|
||||||
|
<control:DataGridTextColumn Binding="{Binding Host}"
|
||||||
|
Header="Host"
|
||||||
|
IsReadOnly="True"
|
||||||
|
IsColumnFiltered="True" />
|
||||||
|
|
||||||
|
</control:FilterDataGrid.Columns>
|
||||||
|
</control:FilterDataGrid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
|
|
||||||
55
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/FrmMain.xaml.cs
Normal file
55
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/FrmMain.xaml.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace FSI.Lib.Guis.Rdp.Mgt
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für FrmMain.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class FrmMain : Window
|
||||||
|
{
|
||||||
|
public ViewModel Rdps { get; set; }
|
||||||
|
public bool CloseAtLostFocus { get; set; }
|
||||||
|
public IEnumerable<IInterface> InputData { get; set; }
|
||||||
|
|
||||||
|
public string Exe { get; set; }
|
||||||
|
|
||||||
|
public FrmMain()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Loaded += Main_Loaded;
|
||||||
|
Deactivated += FrmMain_Deactivated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrmMain_Deactivated(object sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
if (CloseAtLostFocus)
|
||||||
|
Visibility = Visibility.Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Main_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
Title = "FSI Text in die Zwischenablage kopieren";
|
||||||
|
Title += " v" + Assembly.GetExecutingAssembly().GetName().Version; // Version in Titel eintragen
|
||||||
|
|
||||||
|
Rdps = new ViewModel(new DataProvider())
|
||||||
|
{
|
||||||
|
InputData = InputData,
|
||||||
|
Exe = Exe,
|
||||||
|
};
|
||||||
|
DataContext = Rdps;
|
||||||
|
|
||||||
|
Rdps.Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tbSearch_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
tbSearch.Select(tbSearch.Text.Length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
30
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/Model.cs
Normal file
30
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/Model.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FSI.Lib.Guis.Rdp.Mgt
|
||||||
|
{
|
||||||
|
public interface IInterface
|
||||||
|
{
|
||||||
|
string Plant { get; set; }
|
||||||
|
string SubPlant { get; set; }
|
||||||
|
string Description { get; set; }
|
||||||
|
string Host { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Model : IInterface
|
||||||
|
{
|
||||||
|
private string _plant;
|
||||||
|
private string _subPlant;
|
||||||
|
private string _description;
|
||||||
|
private string _host;
|
||||||
|
public string Plant { get => _plant; set => _plant = value; }
|
||||||
|
public string SubPlant { get => _subPlant; set => _subPlant = value; }
|
||||||
|
public string Description { get => _description; set => _description = value; }
|
||||||
|
public string Host { get => _host; set => _host = value; }
|
||||||
|
public string DescriptionDtl { get => _plant + " " + SubPlant + " " + Description + " " + Host; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDataProvider
|
||||||
|
{
|
||||||
|
IEnumerable<Model> Load(IEnumerable<IInterface> datas);
|
||||||
|
}
|
||||||
|
}
|
||||||
187
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/ViewModel.cs
Normal file
187
FSI.Lib/FSI.Lib/Guis/Rdp.Mgt/ViewModel.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using FSI.Lib.MVVM;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NLog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace FSI.Lib.Guis.Rdp.Mgt
|
||||||
|
{
|
||||||
|
public class ViewModel : MVVM.ViewModelBase
|
||||||
|
{
|
||||||
|
readonly IDataProvider _dataProvider;
|
||||||
|
private string _search;
|
||||||
|
private ICollectionView _collView;
|
||||||
|
|
||||||
|
public ICommand RefreshCommand => new DelegateCommand(RefreshData);
|
||||||
|
private ICommand _cmdQuickSearch;
|
||||||
|
private ICommand _cmdOpen;
|
||||||
|
|
||||||
|
public Logger _log;
|
||||||
|
|
||||||
|
public ViewModel(IDataProvider dataProvider)
|
||||||
|
{
|
||||||
|
Datas = new ObservableCollection<Model>();
|
||||||
|
_dataProvider = dataProvider;
|
||||||
|
|
||||||
|
_cmdQuickSearch = new RelayCommand<object>(ExecuteQuickSearch, CanExecuteQuickSearch);
|
||||||
|
_cmdOpen = new RelayCommand<object>(ExecuteOpen, CanExecuteOpen);
|
||||||
|
|
||||||
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<Model> Datas { get; }
|
||||||
|
public ObservableCollection<Model> FilteredData { get; set; }
|
||||||
|
|
||||||
|
public Model SelectedData { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<IInterface> InputData { get; set; }
|
||||||
|
|
||||||
|
public string Exe { get; set; }
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
var datas = _dataProvider.Load(InputData);
|
||||||
|
Datas.Clear();
|
||||||
|
|
||||||
|
if (datas != null)
|
||||||
|
{
|
||||||
|
foreach (Model data in datas)
|
||||||
|
{
|
||||||
|
Datas.Add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilteredData = new ObservableCollection<Model>(Datas);
|
||||||
|
_collView = CollectionViewSource.GetDefaultView(FilteredData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Search
|
||||||
|
{
|
||||||
|
get => _search;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_search = value;
|
||||||
|
|
||||||
|
_collView.Filter = e =>
|
||||||
|
{
|
||||||
|
var item = (Model)e;
|
||||||
|
|
||||||
|
return ((item.Plant?.StartsWith(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||||
|
|| (item.SubPlant?.StartsWith(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||||
|
#if NET472
|
||||||
|
|| (item.Path?.Contains(_search) ?? false)
|
||||||
|
|| (item.DescriptionDtl?.Contains(_search) ?? false)
|
||||||
|
#elif NET6_0
|
||||||
|
|| (item.Host?.Contains(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||||
|
|| (item.DescriptionDtl?.Contains(_search, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
_collView.Refresh();
|
||||||
|
FilteredData = new ObservableCollection<Model>(_collView.OfType<Model>().ToList());
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QuickSearch(string search)
|
||||||
|
{
|
||||||
|
Search = search + " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshData(object obj)
|
||||||
|
{
|
||||||
|
Search = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanExecuteQuickSearch(object obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteQuickSearch(object obj)
|
||||||
|
{
|
||||||
|
QuickSearch(obj.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand CmdQuickSearch
|
||||||
|
{
|
||||||
|
get { return _cmdQuickSearch; }
|
||||||
|
set => _cmdQuickSearch = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanExecuteOpen(object obj)
|
||||||
|
{
|
||||||
|
if (SelectedData.Host != null)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteOpen(object obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
Process process = new();
|
||||||
|
process.StartInfo.FileName = Environment.ExpandEnvironmentVariables(Exe);
|
||||||
|
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(Environment.ExpandEnvironmentVariables(Exe));
|
||||||
|
process.StartInfo.Arguments = "/v:" + SelectedData.Host + " " + "/f";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
_log.Info("Anwendung \"{0}\" wurde mit den Argumenten \"{1}\" gestartet", Exe, process.StartInfo.Arguments);
|
||||||
|
}
|
||||||
|
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 740)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.StartInfo.UseShellExecute = true;
|
||||||
|
process.StartInfo.Verb = "runas";
|
||||||
|
process.Start();
|
||||||
|
_log.Info("Anwendung \"{0}\" wurde mit den Argumenten \"{1}\" als Admin gestartet", Exe, process.StartInfo.Arguments);
|
||||||
|
}
|
||||||
|
catch (Exception ex2)
|
||||||
|
{
|
||||||
|
_log.Info("Anwendung konnte durch folgenden Fehler \"{0}\" nicht gestartet werden.", ex2.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand CmdOpen
|
||||||
|
{
|
||||||
|
get { return _cmdOpen; }
|
||||||
|
set => _cmdOpen = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DataProvider : IDataProvider
|
||||||
|
{
|
||||||
|
public IEnumerable<Model> Load(IEnumerable<IInterface> datas)
|
||||||
|
{
|
||||||
|
var newDatas = new ObservableCollection<Model>();
|
||||||
|
|
||||||
|
foreach (IInterface data in datas)
|
||||||
|
{
|
||||||
|
Model newData = new();
|
||||||
|
|
||||||
|
newData.Plant = data.Plant;
|
||||||
|
newData.SubPlant = data.SubPlant;
|
||||||
|
newData.Description = data.Description;
|
||||||
|
newData.Host = data.Host;
|
||||||
|
|
||||||
|
newDatas.Add(newData);
|
||||||
|
}
|
||||||
|
|
||||||
|
newDatas = new ObservableCollection<Model>(newDatas.OrderBy(i => i.DescriptionDtl));
|
||||||
|
return newDatas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,13 +32,11 @@ namespace FSI.Lib.Guis.SieTiaWinCCMsgMgt
|
|||||||
static extern bool IsWindowVisible(IntPtr hWnd);
|
static extern bool IsWindowVisible(IntPtr hWnd);
|
||||||
|
|
||||||
const int BM_CLICK = 0x00F5; //message for Click which is a constant
|
const int BM_CLICK = 0x00F5; //message for Click which is a constant
|
||||||
const int WM_LBUTTONDOWN = 0x0201; //message for Mouse down
|
|
||||||
const int WM_LBUTTONUP = 0x0202; // message for Mouse up
|
|
||||||
|
|
||||||
private CancellationTokenSource tokenSource = null;
|
private CancellationTokenSource tokenSource = null;
|
||||||
private Task task = null;
|
private Task task = null;
|
||||||
|
|
||||||
private BackgroundWorker backgroundWorker;
|
private readonly BackgroundWorker backgroundWorker;
|
||||||
private ICommand _cmdStart;
|
private ICommand _cmdStart;
|
||||||
private ICommand _cmdStop;
|
private ICommand _cmdStop;
|
||||||
|
|
||||||
|
|||||||
27
Kalk/Consolus/ConsoleChar.cs
Normal file
27
Kalk/Consolus/ConsoleChar.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
public struct ConsoleChar
|
||||||
|
{
|
||||||
|
public ConsoleChar(char value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
StyleMarkers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConsoleStyleMarker> StyleMarkers;
|
||||||
|
|
||||||
|
public char Value;
|
||||||
|
|
||||||
|
public static implicit operator ConsoleChar(char c)
|
||||||
|
{
|
||||||
|
return new ConsoleChar(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Kalk/Consolus/ConsoleHelper.cs
Normal file
49
Kalk/Consolus/ConsoleHelper.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
public class ConsoleHelper
|
||||||
|
{
|
||||||
|
public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
|
||||||
|
private static readonly Lazy<bool> _isConsoleSupportingEscapeSequences = new Lazy<bool>(IsConsoleSupportingEscapeSequencesInternal);
|
||||||
|
private static readonly Lazy<bool> _hasInteractiveConsole = new Lazy<bool>(HasInteractiveConsoleInternal);
|
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||||
|
public static bool SupportEscapeSequences => _isConsoleSupportingEscapeSequences.Value;
|
||||||
|
|
||||||
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||||
|
public static bool HasInteractiveConsole => _hasInteractiveConsole.Value;
|
||||||
|
|
||||||
|
private static bool IsConsoleSupportingEscapeSequencesInternal()
|
||||||
|
{
|
||||||
|
if (Console.IsOutputRedirected || Console.IsErrorRedirected || !HasInteractiveConsole)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWindows)
|
||||||
|
{
|
||||||
|
WindowsHelper.EnableAnsiEscapeOnWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
var left = Console.CursorLeft;
|
||||||
|
var top = Console.CursorTop;
|
||||||
|
ConsoleStyle.Reset.Render(Console.Out);
|
||||||
|
bool support = true;
|
||||||
|
if (left != Console.CursorLeft || top != Console.CursorTop)
|
||||||
|
{
|
||||||
|
support = false;
|
||||||
|
// Remove previous sequence
|
||||||
|
Console.SetCursorPosition(left, top);
|
||||||
|
Console.Write(" "); // Clear \x1b[0m = 4 characters
|
||||||
|
Console.SetCursorPosition(left, top);
|
||||||
|
}
|
||||||
|
return support;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasInteractiveConsoleInternal() => IsWindows ? WindowsHelper.HasConsoleWindows() : Environment.UserInteractive;
|
||||||
|
}
|
||||||
|
}
|
||||||
217
Kalk/Consolus/ConsoleRenderer.cs
Normal file
217
Kalk/Consolus/ConsoleRenderer.cs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
public class ConsoleRenderer
|
||||||
|
{
|
||||||
|
private readonly ConsoleTextWriter _consoleWriter;
|
||||||
|
private int _previousDisplayLength;
|
||||||
|
private int _cursorIndex;
|
||||||
|
private int _beforeEditLineSize;
|
||||||
|
|
||||||
|
public ConsoleRenderer()
|
||||||
|
{
|
||||||
|
SupportEscapeSequences = ConsoleHelper.SupportEscapeSequences;
|
||||||
|
_consoleWriter = new ConsoleTextWriter(Console.Out);
|
||||||
|
Prompt = new ConsoleText();
|
||||||
|
EditLine = new ConsoleText();
|
||||||
|
AfterEditLine = new ConsoleText();
|
||||||
|
SelectionIndex = -1;
|
||||||
|
EnableCursorChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SupportEscapeSequences { get; set; }
|
||||||
|
|
||||||
|
public ConsoleTextWriter ConsoleWriter => _consoleWriter;
|
||||||
|
|
||||||
|
public int LineTop
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var currentLine = (_beforeEditLineSize + CursorIndex) / Console.BufferWidth;
|
||||||
|
return Console.CursorTop - currentLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsClean => _previousDisplayLength == 0;
|
||||||
|
|
||||||
|
public bool EnableCursorChanged { get; set; }
|
||||||
|
|
||||||
|
public int CursorIndex
|
||||||
|
{
|
||||||
|
get => _cursorIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 0 || value > EditLine.Count) throw new ArgumentOutOfRangeException(nameof(value), $"Value must be >= 0 and <= {EditLine.Count}");
|
||||||
|
|
||||||
|
var lineTop = LineTop;
|
||||||
|
bool changed = _cursorIndex != value;
|
||||||
|
_cursorIndex = value;
|
||||||
|
UpdateCursorPosition(lineTop);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
NotifyCursorChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action CursorChanged { get; set; }
|
||||||
|
|
||||||
|
private void NotifyCursorChanged()
|
||||||
|
{
|
||||||
|
var cursorChanged = CursorChanged;
|
||||||
|
if (EnableCursorChanged) CursorChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasSelection => SelectionIndex >= 0 && CursorIndex != SelectionIndex;
|
||||||
|
|
||||||
|
public int SelectionStartIndex => HasSelection ? SelectionIndex < CursorIndex ? SelectionIndex : CursorIndex : -1;
|
||||||
|
|
||||||
|
public int SelectionEndIndex => HasSelection ? SelectionIndex < CursorIndex ? CursorIndex : SelectionIndex : -1;
|
||||||
|
|
||||||
|
public int SelectionIndex { get; private set; }
|
||||||
|
|
||||||
|
public ConsoleText Prompt { get; }
|
||||||
|
|
||||||
|
public ConsoleText EditLine { get; }
|
||||||
|
|
||||||
|
public ConsoleText AfterEditLine { get; }
|
||||||
|
|
||||||
|
public Action BeforeRender { get; set; }
|
||||||
|
|
||||||
|
public void BeginSelection()
|
||||||
|
{
|
||||||
|
if (SelectionIndex >= 0) return;
|
||||||
|
SelectionIndex = CursorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndSelection()
|
||||||
|
{
|
||||||
|
if (SelectionIndex < 0) return;
|
||||||
|
SelectionIndex = -1;
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSelection()
|
||||||
|
{
|
||||||
|
if (!HasSelection) return;
|
||||||
|
|
||||||
|
var start = SelectionStartIndex;
|
||||||
|
var count = SelectionEndIndex - start;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (start >= EditLine.Count) break;
|
||||||
|
EditLine.RemoveAt(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorIndex = start;
|
||||||
|
SelectionIndex = -1;
|
||||||
|
Render(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
AfterEditLine.Clear();
|
||||||
|
EditLine.Clear();
|
||||||
|
_cursorIndex = 0;
|
||||||
|
_beforeEditLineSize = 0;
|
||||||
|
_previousDisplayLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render(int? newCursorIndex = null, bool reset = false)
|
||||||
|
{
|
||||||
|
// Invoke before rendering to update the highlighting
|
||||||
|
BeforeRender?.Invoke();
|
||||||
|
|
||||||
|
Console.CursorVisible = false;
|
||||||
|
Console.SetCursorPosition(0, LineTop);
|
||||||
|
|
||||||
|
if (SupportEscapeSequences)
|
||||||
|
{
|
||||||
|
ConsoleStyle.Reset.Render(_consoleWriter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
Prompt.Render(_consoleWriter);
|
||||||
|
|
||||||
|
EditLine.SelectionStart = SelectionStartIndex;
|
||||||
|
EditLine.SelectionEnd = SelectionEndIndex;
|
||||||
|
EditLine.Render(_consoleWriter, SupportEscapeSequences);
|
||||||
|
|
||||||
|
AfterEditLine.Render(_consoleWriter);
|
||||||
|
|
||||||
|
// Fill remaining space with space
|
||||||
|
var newLength = _consoleWriter.VisibleCharacterCount;
|
||||||
|
var visualLength = newLength;
|
||||||
|
if (newLength < _previousDisplayLength)
|
||||||
|
{
|
||||||
|
if (SupportEscapeSequences)
|
||||||
|
{
|
||||||
|
ConsoleStyle.Reset.Render(_consoleWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = newLength; i < _previousDisplayLength; i++)
|
||||||
|
{
|
||||||
|
_consoleWriter.Write(' ');
|
||||||
|
}
|
||||||
|
visualLength = _previousDisplayLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SupportEscapeSequences)
|
||||||
|
{
|
||||||
|
ConsoleStyle.Reset.Render(_consoleWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
_consoleWriter.Commit();
|
||||||
|
|
||||||
|
if (!SupportEscapeSequences)
|
||||||
|
{
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
_previousDisplayLength = newLength;
|
||||||
|
|
||||||
|
if (!reset)
|
||||||
|
{
|
||||||
|
// Calculate the current line based on the visual
|
||||||
|
var lineTop = Console.CursorTop - (visualLength - 1) / Console.BufferWidth;
|
||||||
|
|
||||||
|
_beforeEditLineSize = EditLine.VisibleCharacterStart;
|
||||||
|
if (newCursorIndex.HasValue)
|
||||||
|
{
|
||||||
|
bool changed = _cursorIndex != newCursorIndex.Value;
|
||||||
|
_cursorIndex = newCursorIndex.Value;
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
NotifyCursorChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cursorIndex > EditLine.Count) _cursorIndex = EditLine.Count;
|
||||||
|
|
||||||
|
UpdateCursorPosition(lineTop);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.CursorVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCursorPosition(int lineTop)
|
||||||
|
{
|
||||||
|
var position = _beforeEditLineSize + _cursorIndex;
|
||||||
|
|
||||||
|
var deltaX = position % Console.BufferWidth;
|
||||||
|
var deltaY = position / Console.BufferWidth;
|
||||||
|
Console.SetCursorPosition(deltaX, lineTop + deltaY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
750
Kalk/Consolus/ConsoleRepl.cs
Normal file
750
Kalk/Consolus/ConsoleRepl.cs
Normal file
@@ -0,0 +1,750 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
public class ConsoleRepl : ConsoleRenderer
|
||||||
|
{
|
||||||
|
// http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
|
|
||||||
|
private int _stackIndex;
|
||||||
|
private readonly BlockingCollection<ConsoleKeyInfo> _keys;
|
||||||
|
private Thread _thread;
|
||||||
|
|
||||||
|
public static bool IsSelf()
|
||||||
|
{
|
||||||
|
if (ConsoleHelper.IsWindows)
|
||||||
|
{
|
||||||
|
return WindowsHelper.IsSelfConsoleWindows();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleRepl()
|
||||||
|
{
|
||||||
|
HasInteractiveConsole = ConsoleHelper.HasInteractiveConsole;
|
||||||
|
Prompt.Append(">>> ");
|
||||||
|
_stackIndex = -1;
|
||||||
|
History = new List<string>();
|
||||||
|
ExitOnNextEval = false;
|
||||||
|
PendingTextToEnter = new Queue<string>();
|
||||||
|
_keys = new BlockingCollection<ConsoleKeyInfo>();
|
||||||
|
|
||||||
|
if (HasInteractiveConsole)
|
||||||
|
{
|
||||||
|
Console.TreatControlCAsInput = true;
|
||||||
|
Console.CursorVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasInteractiveConsole { get; }
|
||||||
|
|
||||||
|
public bool ExitOnNextEval { get; set; }
|
||||||
|
|
||||||
|
public Func<CancellationTokenSource> GetCancellationTokenSource { get; set; }
|
||||||
|
|
||||||
|
public PreProcessKeyDelegate TryPreProcessKey { get; set; }
|
||||||
|
|
||||||
|
public delegate bool PreProcessKeyDelegate(ConsoleKeyInfo key, ref int cursorIndex);
|
||||||
|
|
||||||
|
public Action<ConsoleKeyInfo> PostProcessKey { get; set; }
|
||||||
|
|
||||||
|
public List<string> History { get; }
|
||||||
|
|
||||||
|
public Action<string> SetClipboardTextImpl { get; set; }
|
||||||
|
|
||||||
|
public Func<string> GetClipboardTextImpl { get; set; }
|
||||||
|
|
||||||
|
public string LocalClipboard { get; set; }
|
||||||
|
|
||||||
|
public Func<string, bool, bool> OnTextValidatingEnter { get; set; }
|
||||||
|
|
||||||
|
public Action<string> OnTextValidatedEnter { get; set; }
|
||||||
|
|
||||||
|
private Queue<string> PendingTextToEnter { get; }
|
||||||
|
|
||||||
|
public bool Evaluating { get; private set; }
|
||||||
|
|
||||||
|
public void Begin()
|
||||||
|
{
|
||||||
|
CursorIndex = 0;
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void End()
|
||||||
|
{
|
||||||
|
CursorIndex = EditLine.Count;
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnqueuePendingTextToEnter(string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
PendingTextToEnter.Enqueue(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSelection()
|
||||||
|
{
|
||||||
|
if (SelectionIndex >= 0)
|
||||||
|
{
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnicodeCategory GetCharCategory(char c)
|
||||||
|
{
|
||||||
|
if (c == '_' || (c >= '0' && c <= '9')) return UnicodeCategory.LowercaseLetter;
|
||||||
|
c = char.ToLowerInvariant(c);
|
||||||
|
return char.GetUnicodeCategory(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveLeft(bool word = false)
|
||||||
|
{
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
if (cursorIndex > 0)
|
||||||
|
{
|
||||||
|
if (word)
|
||||||
|
{
|
||||||
|
UnicodeCategory? category = null;
|
||||||
|
|
||||||
|
// Remove any space before
|
||||||
|
while (cursorIndex > 0)
|
||||||
|
{
|
||||||
|
cursorIndex--;
|
||||||
|
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
|
||||||
|
if (newCategory != UnicodeCategory.SpaceSeparator)
|
||||||
|
{
|
||||||
|
category = newCategory;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursorIndex > 0)
|
||||||
|
{
|
||||||
|
cursorIndex--;
|
||||||
|
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
|
||||||
|
|
||||||
|
if (category.HasValue)
|
||||||
|
{
|
||||||
|
if (newCategory != category.Value)
|
||||||
|
{
|
||||||
|
cursorIndex++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
category = newCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CursorIndex = cursorIndex;
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int FindNextWordRight(int cursorIndex)
|
||||||
|
{
|
||||||
|
var category = GetCharCategory(EditLine[cursorIndex].Value);
|
||||||
|
|
||||||
|
while (cursorIndex < EditLine.Count)
|
||||||
|
{
|
||||||
|
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
|
||||||
|
if (newCategory != category)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursorIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursorIndex < EditLine.Count)
|
||||||
|
{
|
||||||
|
var newCategory = GetCharCategory(EditLine[cursorIndex].Value);
|
||||||
|
if (newCategory != UnicodeCategory.SpaceSeparator)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursorIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveRight(bool word = false)
|
||||||
|
{
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
if (cursorIndex < EditLine.Count)
|
||||||
|
{
|
||||||
|
if (word)
|
||||||
|
{
|
||||||
|
cursorIndex = FindNextWordRight(cursorIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CursorIndex = cursorIndex;
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopySelectionToClipboard()
|
||||||
|
{
|
||||||
|
if (!HasSelection) return;
|
||||||
|
|
||||||
|
var text = new StringBuilder();
|
||||||
|
var from = SelectionIndex < CursorIndex ? SelectionIndex : CursorIndex;
|
||||||
|
var to = SelectionIndex < CursorIndex ? CursorIndex - 1 : SelectionIndex - 1;
|
||||||
|
|
||||||
|
for (int i = from; i <= to; i++)
|
||||||
|
{
|
||||||
|
text.Append(EditLine[i].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool useLocalClipboard = true;
|
||||||
|
|
||||||
|
var textToClip = text.ToString();
|
||||||
|
|
||||||
|
if (SetClipboardTextImpl != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetClipboardTextImpl(textToClip);
|
||||||
|
useLocalClipboard = false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useLocalClipboard)
|
||||||
|
{
|
||||||
|
LocalClipboard = textToClip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetClipboardText()
|
||||||
|
{
|
||||||
|
if (LocalClipboard != null) return LocalClipboard;
|
||||||
|
|
||||||
|
if (GetClipboardTextImpl != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return GetClipboardTextImpl();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void Backspace(bool word)
|
||||||
|
{
|
||||||
|
if (HasSelection)
|
||||||
|
{
|
||||||
|
RemoveSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
if (cursorIndex == 0) return;
|
||||||
|
|
||||||
|
if (word)
|
||||||
|
{
|
||||||
|
MoveLeft(true);
|
||||||
|
var newCursorIndex = CursorIndex;
|
||||||
|
var length = cursorIndex - CursorIndex;
|
||||||
|
EditLine.RemoveRangeAt(newCursorIndex, length);
|
||||||
|
cursorIndex = newCursorIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorIndex--;
|
||||||
|
EditLine.RemoveAt(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Render(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Delete(bool word)
|
||||||
|
{
|
||||||
|
if (HasSelection)
|
||||||
|
{
|
||||||
|
RemoveSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
if (cursorIndex < EditLine.Count)
|
||||||
|
{
|
||||||
|
if (word)
|
||||||
|
{
|
||||||
|
var count = FindNextWordRight(cursorIndex) - cursorIndex;
|
||||||
|
EditLine.RemoveRangeAt(cursorIndex, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditLine.RemoveAt(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLine(string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
|
||||||
|
EndSelection();
|
||||||
|
|
||||||
|
// Don't update if it is already empty
|
||||||
|
if (EditLine.Count == 0 && string.IsNullOrEmpty(text)) return;
|
||||||
|
|
||||||
|
EditLine.ReplaceBy(text);
|
||||||
|
Render(EditLine.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
Write(text, 0, text.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string text, int index, int length)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
var cursorIndex = CursorIndex + length;
|
||||||
|
EditLine.InsertRange(CursorIndex, text, index, length);
|
||||||
|
Render(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ConsoleStyle style)
|
||||||
|
{
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
EditLine.EnableStyleAt(cursorIndex, style);
|
||||||
|
Render(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(char c)
|
||||||
|
{
|
||||||
|
if (SelectionIndex >= 0)
|
||||||
|
{
|
||||||
|
RemoveSelection();
|
||||||
|
}
|
||||||
|
EndSelection();
|
||||||
|
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
EditLine.Insert(cursorIndex, c);
|
||||||
|
cursorIndex++;
|
||||||
|
Render(cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Enter(bool force)
|
||||||
|
{
|
||||||
|
if (EnterInternal(force))
|
||||||
|
{
|
||||||
|
while (PendingTextToEnter.Count > 0)
|
||||||
|
{
|
||||||
|
var newTextToEnter = PendingTextToEnter.Dequeue();
|
||||||
|
EditLine.Clear();
|
||||||
|
EditLine.Append(newTextToEnter);
|
||||||
|
if (!EnterInternal(force)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EnterInternal(bool hasControl)
|
||||||
|
{
|
||||||
|
if (HasSelection)
|
||||||
|
{
|
||||||
|
EndSelection();
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
|
||||||
|
var text = EditLine.Count == 0 ? string.Empty : EditLine.ToString();
|
||||||
|
|
||||||
|
// Try to validate the string
|
||||||
|
if (OnTextValidatingEnter != null)
|
||||||
|
{
|
||||||
|
bool isValid = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Evaluating = true;
|
||||||
|
isValid = OnTextValidatingEnter(text, hasControl);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Evaluating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
Render();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNotEmpty = !IsClean || EditLine.Count > 0 || AfterEditLine.Count > 0;
|
||||||
|
if (isNotEmpty)
|
||||||
|
{
|
||||||
|
Render(reset: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate enter validation
|
||||||
|
OnTextValidatedEnter?.Invoke(text);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
History.Add(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stackIndex = -1;
|
||||||
|
|
||||||
|
if (!ExitOnNextEval)
|
||||||
|
{
|
||||||
|
Render(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
Console.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Exit()
|
||||||
|
{
|
||||||
|
End();
|
||||||
|
|
||||||
|
Render(reset: true);
|
||||||
|
|
||||||
|
Console.Write($"{ConsoleStyle.BrightRed}^Z");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
if (!ConsoleHelper.IsWindows)
|
||||||
|
{
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExitOnNextEval = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
if (IsWindows)
|
||||||
|
{
|
||||||
|
// Clear any previous running thread
|
||||||
|
if (_thread != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_thread.Abort();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
_thread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_thread = new Thread(ThreadReadKeys) {IsBackground = true, Name = "Consolus.ThreadReadKeys"};
|
||||||
|
_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
_stackIndex = -1;
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
|
||||||
|
|
||||||
|
Render();
|
||||||
|
while (!ExitOnNextEval)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConsoleKeyInfo key;
|
||||||
|
key = IsWindows ? _keys.Take() : Console.ReadKey(true);
|
||||||
|
|
||||||
|
ProcessKey(key);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AfterEditLine.Clear();
|
||||||
|
AfterEditLine.Append("\n");
|
||||||
|
AfterEditLine.Begin(ConsoleStyle.Red);
|
||||||
|
AfterEditLine.Append(ex.Message);
|
||||||
|
Render(); // re-display the current line with the exception
|
||||||
|
|
||||||
|
// Display the next line
|
||||||
|
//Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThreadReadKeys()
|
||||||
|
{
|
||||||
|
while (!ExitOnNextEval)
|
||||||
|
{
|
||||||
|
var key = Console.ReadKey(true);
|
||||||
|
if (Evaluating && (key.Modifiers & ConsoleModifiers.Control) != 0 && key.Key == ConsoleKey.C)
|
||||||
|
{
|
||||||
|
GetCancellationTokenSource()?.Cancel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_keys.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PasteClipboard()
|
||||||
|
{
|
||||||
|
var clipboard = GetClipboardText();
|
||||||
|
if (clipboard != null)
|
||||||
|
{
|
||||||
|
clipboard = clipboard.TrimEnd();
|
||||||
|
int previousIndex = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int matchIndex = clipboard.IndexOf('\n', previousIndex);
|
||||||
|
int index = matchIndex;
|
||||||
|
bool exit = false;
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = clipboard.Length;
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index > 0 && index < clipboard.Length && clipboard[index - 1] == '\r')
|
||||||
|
{
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
Write(clipboard, previousIndex, index - previousIndex);
|
||||||
|
|
||||||
|
if (exit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
previousIndex = matchIndex + 1;
|
||||||
|
// Otherwise we have a new line
|
||||||
|
Enter(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ProcessKey(ConsoleKeyInfo key)
|
||||||
|
{
|
||||||
|
_isStandardAction = false;
|
||||||
|
_hasShift = (key.Modifiers & ConsoleModifiers.Shift) != 0;
|
||||||
|
|
||||||
|
// Only support selection if we have support for escape sequences
|
||||||
|
if (SupportEscapeSequences && _hasShift)
|
||||||
|
{
|
||||||
|
BeginSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to pre-process key
|
||||||
|
var cursorIndex = CursorIndex;
|
||||||
|
if (TryPreProcessKey != null && TryPreProcessKey(key, ref cursorIndex))
|
||||||
|
{
|
||||||
|
if (!_isStandardAction)
|
||||||
|
{
|
||||||
|
if (SelectionIndex >= 0)
|
||||||
|
{
|
||||||
|
RemoveSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndSelection();
|
||||||
|
|
||||||
|
Render(cursorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (key.KeyChar >= ' ')
|
||||||
|
{
|
||||||
|
Write(key.KeyChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove selection if shift is no longer selected
|
||||||
|
if (!_hasShift)
|
||||||
|
{
|
||||||
|
EndSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-process key
|
||||||
|
if (PostProcessKey != null)
|
||||||
|
{
|
||||||
|
PostProcessKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DebugCursorPosition(string text = null)
|
||||||
|
{
|
||||||
|
Console.Title = $"x:{Console.CursorLeft} y:{Console.CursorTop} (Size w:{Console.BufferWidth} h:{Console.BufferHeight}){(text == null ? string.Empty: " " + text)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoHistory(bool next)
|
||||||
|
{
|
||||||
|
var newStackIndex = _stackIndex + (next ? -1 : 1);
|
||||||
|
if (newStackIndex < 0 || newStackIndex >= History.Count)
|
||||||
|
{
|
||||||
|
if (newStackIndex < 0)
|
||||||
|
{
|
||||||
|
SetLine(string.Empty);
|
||||||
|
_stackIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_stackIndex = newStackIndex;
|
||||||
|
var index = (History.Count - 1) - _stackIndex;
|
||||||
|
|
||||||
|
SetLine(History[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool _hasShift;
|
||||||
|
private bool _isStandardAction;
|
||||||
|
|
||||||
|
public void Action(ConsoleAction action)
|
||||||
|
{
|
||||||
|
_isStandardAction = true;
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case ConsoleAction.Exit:
|
||||||
|
Exit();
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CursorLeft:
|
||||||
|
MoveLeft(false);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CursorRight:
|
||||||
|
MoveRight(false);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CursorLeftWord:
|
||||||
|
MoveLeft(true);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CursorRightWord:
|
||||||
|
MoveRight(true);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CursorStartOfLine:
|
||||||
|
Begin();
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CursorEndOfLine:
|
||||||
|
End();
|
||||||
|
break;
|
||||||
|
case ConsoleAction.HistoryPrevious:
|
||||||
|
GoHistory(false);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.HistoryNext:
|
||||||
|
GoHistory(true);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteCharacterLeft:
|
||||||
|
Backspace(false);
|
||||||
|
_stackIndex = -1;
|
||||||
|
_hasShift = false;
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteCharacterLeftAndCopy:
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteCharacterRight:
|
||||||
|
Delete(false);
|
||||||
|
_stackIndex = -1;
|
||||||
|
_hasShift = false;
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteCharacterRightAndCopy:
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteWordLeft:
|
||||||
|
Backspace(true);
|
||||||
|
_stackIndex = -1;
|
||||||
|
_hasShift = false;
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteWordRight:
|
||||||
|
Delete(true);
|
||||||
|
_stackIndex = -1;
|
||||||
|
_hasShift = false;
|
||||||
|
break;
|
||||||
|
case ConsoleAction.Completion:
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteTextRightAndCopy:
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteWordRightAndCopy:
|
||||||
|
break;
|
||||||
|
case ConsoleAction.DeleteWordLeftAndCopy:
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CopySelection:
|
||||||
|
CopySelectionToClipboard();
|
||||||
|
break;
|
||||||
|
case ConsoleAction.CutSelection:
|
||||||
|
CopySelectionToClipboard();
|
||||||
|
RemoveSelection();
|
||||||
|
break;
|
||||||
|
case ConsoleAction.PasteClipboard:
|
||||||
|
PasteClipboard();
|
||||||
|
break;
|
||||||
|
case ConsoleAction.ValidateLine:
|
||||||
|
Enter(false);
|
||||||
|
break;
|
||||||
|
case ConsoleAction.ForceValidateLine:
|
||||||
|
Enter(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(action), action, $"Invalid action {action}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public enum ConsoleAction
|
||||||
|
{
|
||||||
|
Exit,
|
||||||
|
CursorLeft,
|
||||||
|
CursorRight,
|
||||||
|
CursorLeftWord,
|
||||||
|
CursorRightWord,
|
||||||
|
CursorStartOfLine,
|
||||||
|
CursorEndOfLine,
|
||||||
|
HistoryPrevious,
|
||||||
|
HistoryNext,
|
||||||
|
DeleteCharacterLeft,
|
||||||
|
DeleteCharacterLeftAndCopy,
|
||||||
|
DeleteCharacterRight,
|
||||||
|
DeleteCharacterRightAndCopy,
|
||||||
|
DeleteWordLeft,
|
||||||
|
DeleteWordRight,
|
||||||
|
Completion,
|
||||||
|
DeleteTextRightAndCopy,
|
||||||
|
DeleteWordRightAndCopy,
|
||||||
|
DeleteWordLeftAndCopy,
|
||||||
|
CopySelection,
|
||||||
|
CutSelection,
|
||||||
|
PasteClipboard,
|
||||||
|
ValidateLine,
|
||||||
|
ForceValidateLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
228
Kalk/Consolus/ConsoleStyle.cs
Normal file
228
Kalk/Consolus/ConsoleStyle.cs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
public enum ConsoleStyleKind
|
||||||
|
{
|
||||||
|
Color,
|
||||||
|
Format,
|
||||||
|
Reset,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public readonly struct ConsoleStyle : IEquatable<ConsoleStyle>
|
||||||
|
{
|
||||||
|
/// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|
||||||
|
public static readonly ConsoleStyle Black = new ConsoleStyle("\u001b[30m");
|
||||||
|
public static readonly ConsoleStyle Red = new ConsoleStyle("\u001b[31m");
|
||||||
|
public static readonly ConsoleStyle Green = new ConsoleStyle("\u001b[32m");
|
||||||
|
public static readonly ConsoleStyle Yellow = new ConsoleStyle("\u001b[33m");
|
||||||
|
public static readonly ConsoleStyle Blue = new ConsoleStyle("\u001b[34m");
|
||||||
|
public static readonly ConsoleStyle Magenta = new ConsoleStyle("\u001b[35m");
|
||||||
|
public static readonly ConsoleStyle Cyan = new ConsoleStyle("\u001b[36m");
|
||||||
|
public static readonly ConsoleStyle White = new ConsoleStyle("\u001b[37m");
|
||||||
|
public static readonly ConsoleStyle BrightBlack = new ConsoleStyle("\u001b[30;1m");
|
||||||
|
public static readonly ConsoleStyle BrightRed = new ConsoleStyle("\u001b[31;1m");
|
||||||
|
public static readonly ConsoleStyle BrightGreen = new ConsoleStyle("\u001b[32;1m");
|
||||||
|
public static readonly ConsoleStyle BrightYellow = new ConsoleStyle("\u001b[33;1m");
|
||||||
|
public static readonly ConsoleStyle BrightBlue = new ConsoleStyle("\u001b[34;1m");
|
||||||
|
public static readonly ConsoleStyle BrightMagenta = new ConsoleStyle("\u001b[35;1m");
|
||||||
|
public static readonly ConsoleStyle BrightCyan = new ConsoleStyle("\u001b[36;1m");
|
||||||
|
public static readonly ConsoleStyle BrightWhite = new ConsoleStyle("\u001b[37;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBlack = new ConsoleStyle("\u001b[40m");
|
||||||
|
public static readonly ConsoleStyle BackgroundRed = new ConsoleStyle("\u001b[41m");
|
||||||
|
public static readonly ConsoleStyle BackgroundGreen = new ConsoleStyle("\u001b[42m");
|
||||||
|
public static readonly ConsoleStyle BackgroundYellow = new ConsoleStyle("\u001b[43m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBlue = new ConsoleStyle("\u001b[44m");
|
||||||
|
public static readonly ConsoleStyle BackgroundMagenta = new ConsoleStyle("\u001b[45m");
|
||||||
|
public static readonly ConsoleStyle BackgroundCyan = new ConsoleStyle("\u001b[46m");
|
||||||
|
public static readonly ConsoleStyle BackgroundWhite = new ConsoleStyle("\u001b[47m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightBlack = new ConsoleStyle("\u001b[40;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightRed = new ConsoleStyle("\u001b[41;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightGreen = new ConsoleStyle("\u001b[42;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightYellow = new ConsoleStyle("\u001b[43;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightBlue = new ConsoleStyle("\u001b[44;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightMagenta = new ConsoleStyle("\u001b[45;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightCyan = new ConsoleStyle("\u001b[46;1m");
|
||||||
|
public static readonly ConsoleStyle BackgroundBrightWhite = new ConsoleStyle("\u001b[47;1m");
|
||||||
|
public static readonly ConsoleStyle Bold = new ConsoleStyle("\u001b[1m", ConsoleStyleKind.Format);
|
||||||
|
public static readonly ConsoleStyle Underline = new ConsoleStyle("\u001b[4m", ConsoleStyleKind.Format);
|
||||||
|
public static readonly ConsoleStyle Reversed = new ConsoleStyle("\u001b[7m", ConsoleStyleKind.Format);
|
||||||
|
public static readonly ConsoleStyle Reset = new ConsoleStyle("\u001b[0m", ConsoleStyleKind.Reset);
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, ConsoleStyle> MapKnownEscapeToConsoleStyle = new Dictionary<string, ConsoleStyle>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
static ConsoleStyle()
|
||||||
|
{
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Black.EscapeSequence, Black);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Red.EscapeSequence, Red);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Green.EscapeSequence, Green);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Yellow.EscapeSequence, Yellow);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Blue.EscapeSequence, Blue);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Magenta.EscapeSequence, Magenta);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Cyan.EscapeSequence, Cyan);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(White.EscapeSequence, White);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightBlack.EscapeSequence, BrightBlack);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightRed.EscapeSequence, BrightRed);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightGreen.EscapeSequence, BrightGreen);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightYellow.EscapeSequence, BrightYellow);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightBlue.EscapeSequence, BrightBlue);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightMagenta.EscapeSequence, BrightMagenta);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightCyan.EscapeSequence, BrightCyan);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BrightWhite.EscapeSequence, BrightWhite);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBlack.EscapeSequence, BackgroundBlack);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundRed.EscapeSequence, BackgroundRed);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundGreen.EscapeSequence, BackgroundGreen);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundYellow.EscapeSequence, BackgroundYellow);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBlue.EscapeSequence, BackgroundBlue);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundMagenta.EscapeSequence, BackgroundMagenta);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundCyan.EscapeSequence, BackgroundCyan);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundWhite.EscapeSequence, BackgroundWhite);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightBlack.EscapeSequence, BackgroundBrightBlack);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightRed.EscapeSequence, BackgroundBrightRed);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightGreen.EscapeSequence, BackgroundBrightGreen);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightYellow.EscapeSequence, BackgroundBrightYellow);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightBlue.EscapeSequence, BackgroundBrightBlue);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightMagenta.EscapeSequence, BackgroundBrightMagenta);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightCyan.EscapeSequence, BackgroundBrightCyan);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(BackgroundBrightWhite.EscapeSequence, BackgroundBrightWhite);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Bold.EscapeSequence, Bold);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Underline.EscapeSequence, Underline);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Reversed.EscapeSequence, Reversed);
|
||||||
|
MapKnownEscapeToConsoleStyle.Add(Reset.EscapeSequence, Reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleStyle(string escapeSequence) : this()
|
||||||
|
{
|
||||||
|
EscapeSequence = escapeSequence;
|
||||||
|
Kind = ConsoleStyleKind.Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleStyle(string escapeSequence, ConsoleStyleKind kind) : this()
|
||||||
|
{
|
||||||
|
EscapeSequence = escapeSequence;
|
||||||
|
Kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsoleStyle(string escapeSequence, ConsoleStyleKind kind, bool isInline)
|
||||||
|
{
|
||||||
|
EscapeSequence = escapeSequence;
|
||||||
|
Kind = kind;
|
||||||
|
IsInline = isInline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConsoleStyle Inline(string escape, ConsoleStyleKind kind = ConsoleStyleKind.Color)
|
||||||
|
{
|
||||||
|
if (escape == null) throw new ArgumentNullException(nameof(escape));
|
||||||
|
if (kind == ConsoleStyleKind.Color)
|
||||||
|
{
|
||||||
|
if (MapKnownEscapeToConsoleStyle.TryGetValue(escape, out var style))
|
||||||
|
{
|
||||||
|
kind = style.Kind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ConsoleStyle(escape, kind, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConsoleStyle Rgb(int r, int g, int b)
|
||||||
|
{
|
||||||
|
return new ConsoleStyle($"\x1b[38;2;{r};{g};{b}m", ConsoleStyleKind.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConsoleStyle BackgroundRgb(int r, int g, int b)
|
||||||
|
{
|
||||||
|
return new ConsoleStyle($"\x1b[48;2;{r};{g};{b}m", ConsoleStyleKind.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly string EscapeSequence;
|
||||||
|
|
||||||
|
public readonly ConsoleStyleKind Kind;
|
||||||
|
|
||||||
|
public readonly bool IsInline;
|
||||||
|
|
||||||
|
|
||||||
|
public void Render(TextWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(EscapeSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETSTANDARD2_0
|
||||||
|
public static implicit operator ConsoleStyle(ConsoleColor color)
|
||||||
|
{
|
||||||
|
switch (color)
|
||||||
|
{
|
||||||
|
case ConsoleColor.Black:
|
||||||
|
return Black;
|
||||||
|
case ConsoleColor.Blue:
|
||||||
|
return BrightBlue;
|
||||||
|
case ConsoleColor.Cyan:
|
||||||
|
return BrightCyan;
|
||||||
|
case ConsoleColor.DarkBlue:
|
||||||
|
return Blue;
|
||||||
|
case ConsoleColor.DarkCyan:
|
||||||
|
return Cyan;
|
||||||
|
case ConsoleColor.DarkGray:
|
||||||
|
return BrightBlack;
|
||||||
|
case ConsoleColor.DarkGreen:
|
||||||
|
return Green;
|
||||||
|
case ConsoleColor.DarkMagenta:
|
||||||
|
return Magenta;
|
||||||
|
case ConsoleColor.DarkRed:
|
||||||
|
return Red;
|
||||||
|
case ConsoleColor.DarkYellow:
|
||||||
|
return Yellow;
|
||||||
|
case ConsoleColor.Gray:
|
||||||
|
return BrightBlack;
|
||||||
|
case ConsoleColor.Green:
|
||||||
|
return BrightGreen;
|
||||||
|
case ConsoleColor.Magenta:
|
||||||
|
return BrightMagenta;
|
||||||
|
case ConsoleColor.Red:
|
||||||
|
return BrightRed;
|
||||||
|
case ConsoleColor.White:
|
||||||
|
return BrightWhite;
|
||||||
|
case ConsoleColor.Yellow:
|
||||||
|
return BrightYellow;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(color), color, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public bool Equals(ConsoleStyle other)
|
||||||
|
{
|
||||||
|
return EscapeSequence == other.EscapeSequence && Kind == other.Kind && IsInline == other.IsInline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is ConsoleStyle other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
return ((((EscapeSequence?.GetHashCode() ?? 0) * 397) ^ (int) Kind) * 397) & IsInline.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(ConsoleStyle left, ConsoleStyle right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(ConsoleStyle left, ConsoleStyle right)
|
||||||
|
{
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return EscapeSequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
716
Kalk/Consolus/ConsoleText.cs
Normal file
716
Kalk/Consolus/ConsoleText.cs
Normal file
@@ -0,0 +1,716 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
using static ConsoleStyle;
|
||||||
|
|
||||||
|
|
||||||
|
public struct ConsoleStyleMarker
|
||||||
|
{
|
||||||
|
public ConsoleStyleMarker(ConsoleStyle style, bool enabled)
|
||||||
|
{
|
||||||
|
Style = style;
|
||||||
|
Enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public readonly ConsoleStyle Style;
|
||||||
|
|
||||||
|
public readonly bool Enabled;
|
||||||
|
|
||||||
|
public static implicit operator ConsoleStyleMarker(ConsoleStyle style)
|
||||||
|
{
|
||||||
|
return new ConsoleStyleMarker(style, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ConsoleText : IList<ConsoleChar>
|
||||||
|
{
|
||||||
|
private readonly List<ConsoleChar> _chars;
|
||||||
|
private readonly List<ConsoleStyleMarker> _leadingStyles;
|
||||||
|
private readonly List<ConsoleStyleMarker> _trailingStyles;
|
||||||
|
private bool _changedCalled;
|
||||||
|
private readonly StringBuilder _currentEscape;
|
||||||
|
private EscapeState _escapeState;
|
||||||
|
|
||||||
|
public ConsoleText()
|
||||||
|
{
|
||||||
|
_chars = new List<ConsoleChar>();
|
||||||
|
_leadingStyles = new List<ConsoleStyleMarker>();
|
||||||
|
_trailingStyles = new List<ConsoleStyleMarker>();
|
||||||
|
SelectionEnd = -1;
|
||||||
|
_currentEscape = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText(string text) : this()
|
||||||
|
{
|
||||||
|
Append(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Changed { get; set; }
|
||||||
|
|
||||||
|
public int VisibleCharacterStart { get; internal set; }
|
||||||
|
|
||||||
|
public int VisibleCharacterEnd { get; internal set; }
|
||||||
|
|
||||||
|
public int SelectionStart { get; set; }
|
||||||
|
|
||||||
|
public int SelectionEnd { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public void Add(ConsoleChar item)
|
||||||
|
{
|
||||||
|
Insert(Count, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
ResetAndSet(null);
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetAndSet(string text)
|
||||||
|
{
|
||||||
|
_leadingStyles.Clear();
|
||||||
|
_chars.Clear();
|
||||||
|
_trailingStyles.Clear();
|
||||||
|
SelectionStart = 0;
|
||||||
|
SelectionEnd = -1;
|
||||||
|
if (text != null)
|
||||||
|
{
|
||||||
|
foreach (var c in text)
|
||||||
|
{
|
||||||
|
_chars.Add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplaceBy(string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
ResetAndSet(text);
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearSelection()
|
||||||
|
{
|
||||||
|
SelectionStart = 0;
|
||||||
|
SelectionEnd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasSelection => SelectionStart >= 0 && SelectionEnd <= Count && SelectionStart <= SelectionEnd;
|
||||||
|
|
||||||
|
public void ClearStyles()
|
||||||
|
{
|
||||||
|
_leadingStyles.Clear();
|
||||||
|
_trailingStyles.Clear();
|
||||||
|
for (var i = 0; i < _chars.Count; i++)
|
||||||
|
{
|
||||||
|
var consoleChar = _chars[i];
|
||||||
|
if (consoleChar.StyleMarkers != null)
|
||||||
|
{
|
||||||
|
consoleChar.StyleMarkers.Clear();
|
||||||
|
_chars[i] = consoleChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ClearStyle(ConsoleStyle style)
|
||||||
|
{
|
||||||
|
var removed = RemoveStyle(style, _leadingStyles);
|
||||||
|
removed = RemoveStyle(style, _trailingStyles) || removed;
|
||||||
|
for (var i = 0; i < _chars.Count; i++)
|
||||||
|
{
|
||||||
|
var consoleChar = _chars[i];
|
||||||
|
if (consoleChar.StyleMarkers != null)
|
||||||
|
{
|
||||||
|
removed = RemoveStyle(style, consoleChar.StyleMarkers) || removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RemoveStyle(ConsoleStyle style, List<ConsoleStyleMarker> markers)
|
||||||
|
{
|
||||||
|
bool styleRemoved = false;
|
||||||
|
if (markers == null) return false;
|
||||||
|
for (var i = markers.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var consoleStyleMarker = markers[i];
|
||||||
|
if (consoleStyleMarker.Style == style)
|
||||||
|
{
|
||||||
|
markers.RemoveAt(i);
|
||||||
|
styleRemoved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return styleRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(ConsoleChar item)
|
||||||
|
{
|
||||||
|
return _chars.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(ConsoleChar[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
_chars.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(ConsoleChar item)
|
||||||
|
{
|
||||||
|
return _chars.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _chars.Count;
|
||||||
|
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
public int IndexOf(ConsoleChar item)
|
||||||
|
{
|
||||||
|
return _chars.IndexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, ConsoleChar item)
|
||||||
|
{
|
||||||
|
InsertInternal(index, item);
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
InsertInternal(index + i, text[i]);
|
||||||
|
}
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum EscapeState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Escape,
|
||||||
|
EscapeCsiParameterBytes,
|
||||||
|
EscapeCsiIntermediateBytes,
|
||||||
|
EscapeCsiFinalByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertInternal(int index, ConsoleChar item)
|
||||||
|
{
|
||||||
|
if (item.Value == '\x1b')
|
||||||
|
{
|
||||||
|
_currentEscape.Append(item.Value);
|
||||||
|
_escapeState = EscapeState.Escape;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_escapeState != EscapeState.None)
|
||||||
|
{
|
||||||
|
bool isCharInvalidValid = false;
|
||||||
|
|
||||||
|
// All sequences start with followed by a char in the range 0x40–0x5F: (ASCII @A–Z[\]^_)
|
||||||
|
var c = item.Value;
|
||||||
|
|
||||||
|
if (_escapeState >= EscapeState.EscapeCsiParameterBytes)
|
||||||
|
{
|
||||||
|
// CSI: ESC [ followed by
|
||||||
|
// - by any number (including none) of "parameter bytes", char in the 0x30–0x3F: (ASCII 0–9:;<=>?)
|
||||||
|
// - then by any number of "intermediate bytes" in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./),
|
||||||
|
// - then finally by a single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~)
|
||||||
|
switch (_escapeState)
|
||||||
|
{
|
||||||
|
case EscapeState.EscapeCsiParameterBytes:
|
||||||
|
if (c >= 0x30 && c <= 0x3F)
|
||||||
|
{
|
||||||
|
_currentEscape.Append(c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
goto case EscapeState.EscapeCsiIntermediateBytes;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EscapeState.EscapeCsiIntermediateBytes:
|
||||||
|
if (c >= 0x20 && c <= 0x2F)
|
||||||
|
{
|
||||||
|
_escapeState = EscapeState.EscapeCsiIntermediateBytes;
|
||||||
|
_currentEscape.Append(c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
goto case EscapeState.EscapeCsiFinalByte;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EscapeState.EscapeCsiFinalByte:
|
||||||
|
if (c >= 0x40 && c <= 0x7E)
|
||||||
|
{
|
||||||
|
_currentEscape.Append(c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isCharInvalidValid = true;
|
||||||
|
}
|
||||||
|
var styleAsText = _currentEscape.ToString();
|
||||||
|
_currentEscape.Length = 0;
|
||||||
|
_escapeState = EscapeState.None;
|
||||||
|
InsertInternal(index, new ConsoleStyleMarker(Inline(styleAsText), true));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_currentEscape.Length == 1)
|
||||||
|
{
|
||||||
|
_currentEscape.Append(c);
|
||||||
|
if (c == '[')
|
||||||
|
{
|
||||||
|
_escapeState = EscapeState.EscapeCsiParameterBytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var styleAsText = _currentEscape.ToString();
|
||||||
|
_currentEscape.Length = 0;
|
||||||
|
_escapeState = EscapeState.None;
|
||||||
|
InsertInternal(index, new ConsoleStyleMarker(Inline(styleAsText), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCharInvalidValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// otherwise the character hasn't been consumed, so we propagate it as a real char.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy any leading/trailing escapes
|
||||||
|
bool isFirstInsert = index == 0 && Count == 0;
|
||||||
|
bool isLastInsert = Count > 0 && index == Count;
|
||||||
|
List<ConsoleStyleMarker> copyFrom = isFirstInsert ? _leadingStyles : isLastInsert ? _trailingStyles : null;
|
||||||
|
if (copyFrom != null && copyFrom.Count > 0)
|
||||||
|
{
|
||||||
|
var escapes = item.StyleMarkers;
|
||||||
|
if (escapes == null)
|
||||||
|
{
|
||||||
|
escapes = new List<ConsoleStyleMarker>();
|
||||||
|
item.StyleMarkers = escapes;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < copyFrom.Count; i++)
|
||||||
|
{
|
||||||
|
escapes.Insert(i, copyFrom[i]);
|
||||||
|
}
|
||||||
|
copyFrom.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_chars.Insert(index, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertRange(int index, string text, int textIndex, int length)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
|
||||||
|
var cursorIndex = index;
|
||||||
|
var end = textIndex + length;
|
||||||
|
for (int i = textIndex; i < end; i++)
|
||||||
|
{
|
||||||
|
var c = text[i];
|
||||||
|
InsertInternal(cursorIndex, c);
|
||||||
|
cursorIndex++;
|
||||||
|
}
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyChanged()
|
||||||
|
{
|
||||||
|
var changed = Changed;
|
||||||
|
|
||||||
|
// Avoid recursive change
|
||||||
|
if (changed != null && !_changedCalled)
|
||||||
|
{
|
||||||
|
_changedCalled = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_changedCalled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
_chars.RemoveAt(index);
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRangeAt(int index, int length)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
_chars.RemoveAt(index);
|
||||||
|
}
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleChar this[int index]
|
||||||
|
{
|
||||||
|
get => _chars[index];
|
||||||
|
set => _chars[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ConsoleStyle style)
|
||||||
|
{
|
||||||
|
Add(style, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ConsoleStyle style, bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled) EnableStyleAt(Count, style);
|
||||||
|
else DisableStyleAt(Count, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText Append(char c)
|
||||||
|
{
|
||||||
|
Add(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText Begin(ConsoleStyle style)
|
||||||
|
{
|
||||||
|
Add(style);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText End(ConsoleStyle style)
|
||||||
|
{
|
||||||
|
Add(style, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText AppendLine(string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
AddInternal(text);
|
||||||
|
Add('\n');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText AppendLine()
|
||||||
|
{
|
||||||
|
Add('\n');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(ConsoleText text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
foreach(var c in text._leadingStyles)
|
||||||
|
{
|
||||||
|
InsertInternal(Count, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var c in text._chars)
|
||||||
|
{
|
||||||
|
InsertInternal(Count, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var c in text._trailingStyles)
|
||||||
|
{
|
||||||
|
InsertInternal(Count, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText Append(ConsoleStyle style, bool enabled)
|
||||||
|
{
|
||||||
|
Add(style, enabled);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleText Append(string text)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
AddInternal(text);
|
||||||
|
NotifyChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddInternal(string text)
|
||||||
|
{
|
||||||
|
foreach (var c in text)
|
||||||
|
{
|
||||||
|
InsertInternal(Count, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableStyleAt(int index, ConsoleStyle style)
|
||||||
|
{
|
||||||
|
InsertInternal(index, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableStyleAt(int index, ConsoleStyle style)
|
||||||
|
{
|
||||||
|
InsertInternal(index, new ConsoleStyleMarker(style, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertInternal(int index, ConsoleStyleMarker marker)
|
||||||
|
{
|
||||||
|
if ((uint)index > (uint)Count) throw new ArgumentOutOfRangeException($"Invalid character index {index} not within range [0, {Count}]");
|
||||||
|
|
||||||
|
var isFirst = index == 0 && Count == 0;
|
||||||
|
var isLast = index == Count;
|
||||||
|
|
||||||
|
List<ConsoleStyleMarker> list;
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
list = _leadingStyles;
|
||||||
|
}
|
||||||
|
else if (isLast)
|
||||||
|
{
|
||||||
|
list = _trailingStyles;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var c = this[index];
|
||||||
|
list = c.StyleMarkers;
|
||||||
|
if (list == null)
|
||||||
|
{
|
||||||
|
list = new List<ConsoleStyleMarker>();
|
||||||
|
c.StyleMarkers = list;
|
||||||
|
this[index] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderLeadingTrailingStyles(TextWriter writer, bool displayStyle, bool leading, RunningStyles runningStyles)
|
||||||
|
{
|
||||||
|
var styles = leading ? _leadingStyles : _trailingStyles;
|
||||||
|
foreach (var consoleStyle in styles)
|
||||||
|
{
|
||||||
|
runningStyles.ApplyStyle(consoleStyle);
|
||||||
|
if (displayStyle)
|
||||||
|
{
|
||||||
|
runningStyles.Render(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render(ConsoleTextWriter writer, bool renderEscape = true)
|
||||||
|
{
|
||||||
|
VisibleCharacterStart = writer.VisibleCharacterCount;
|
||||||
|
|
||||||
|
if (HasSelection)
|
||||||
|
{
|
||||||
|
RenderWithSelection(writer, renderEscape);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var styles = new RunningStyles();
|
||||||
|
if (renderEscape) RenderLeadingTrailingStyles(writer, true, true, styles);
|
||||||
|
RenderInternal(writer, 0, Count, renderEscape, styles);
|
||||||
|
if (renderEscape) RenderLeadingTrailingStyles(writer, true, false, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
VisibleCharacterEnd = writer.VisibleCharacterCount - 1;
|
||||||
|
}
|
||||||
|
private void RenderWithSelection(ConsoleTextWriter writer, bool renderEscape = true)
|
||||||
|
{
|
||||||
|
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||||
|
|
||||||
|
// TODO: TLS cache
|
||||||
|
var pendingStyles = renderEscape ? new RunningStyles() : null;
|
||||||
|
|
||||||
|
if (renderEscape)
|
||||||
|
{
|
||||||
|
RenderLeadingTrailingStyles(writer, true, true, pendingStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display text before without selection
|
||||||
|
RenderInternal(writer, 0, SelectionStart, renderEscape, pendingStyles);
|
||||||
|
|
||||||
|
if (renderEscape)
|
||||||
|
{
|
||||||
|
// Disable any attribute sequences
|
||||||
|
Reset.Render(writer);
|
||||||
|
Reversed.Render(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the string with reverse video
|
||||||
|
RenderInternal(writer, SelectionStart, SelectionEnd, false, pendingStyles);
|
||||||
|
|
||||||
|
if (renderEscape)
|
||||||
|
{
|
||||||
|
// Disable any attribute sequences
|
||||||
|
Reset.Render(writer);
|
||||||
|
|
||||||
|
pendingStyles.Render(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display text after without selection
|
||||||
|
RenderInternal(writer, SelectionEnd, this.Count, renderEscape, pendingStyles);
|
||||||
|
|
||||||
|
if (renderEscape) RenderLeadingTrailingStyles(writer, true, false, pendingStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderInternal(ConsoleTextWriter writer, int start, int end, bool displayStyle, RunningStyles runningStyles)
|
||||||
|
{
|
||||||
|
for(int i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
var c = this[i];
|
||||||
|
if ((displayStyle || runningStyles != null) && c.StyleMarkers != null)
|
||||||
|
{
|
||||||
|
foreach (var esc in c.StyleMarkers)
|
||||||
|
{
|
||||||
|
runningStyles.ApplyStyle(esc);
|
||||||
|
if (displayStyle)
|
||||||
|
{
|
||||||
|
runningStyles.Render(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var charValue = c.Value;
|
||||||
|
|
||||||
|
// Fill the remaining line with space to clear the space
|
||||||
|
if (charValue == '\n')
|
||||||
|
{
|
||||||
|
var toComplete = Console.BufferWidth - writer.VisibleCharacterCount % Console.BufferWidth;
|
||||||
|
for (int j = 0; j < toComplete; j++)
|
||||||
|
{
|
||||||
|
writer.Write(' ');
|
||||||
|
}
|
||||||
|
writer.VisibleCharacterCount += toComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charValue == '\n' || charValue >= ' ')
|
||||||
|
{
|
||||||
|
writer.Write(charValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charValue != '\n' || charValue >= ' ')
|
||||||
|
{
|
||||||
|
writer.VisibleCharacterCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator ConsoleText(string text)
|
||||||
|
{
|
||||||
|
return new ConsoleText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<ConsoleChar> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _chars.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var c in this)
|
||||||
|
{
|
||||||
|
builder.Append(c.Value);
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return ((IEnumerable) _chars).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RunningStyles : Dictionary<ConsoleStyleKind, List<ConsoleStyle>>
|
||||||
|
{
|
||||||
|
public void Render(TextWriter writer)
|
||||||
|
{
|
||||||
|
// Disable any attribute sequences
|
||||||
|
Reset.Render(writer);
|
||||||
|
foreach (var stylePair in this)
|
||||||
|
{
|
||||||
|
var list = stylePair.Value;
|
||||||
|
if (stylePair.Key == ConsoleStyleKind.Color)
|
||||||
|
{
|
||||||
|
if (list.Count > 0)
|
||||||
|
{
|
||||||
|
list[list.Count - 1].Render(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (stylePair.Key == ConsoleStyleKind.Format)
|
||||||
|
{
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
item.Render(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyStyle(ConsoleStyleMarker styleMarker)
|
||||||
|
{
|
||||||
|
var style = styleMarker.Style;
|
||||||
|
switch (style.Kind)
|
||||||
|
{
|
||||||
|
case ConsoleStyleKind.Color:
|
||||||
|
case ConsoleStyleKind.Format:
|
||||||
|
if (!TryGetValue(style.Kind, out var list))
|
||||||
|
{
|
||||||
|
list = new List<ConsoleStyle>();
|
||||||
|
Add(style.Kind, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (styleMarker.Enabled)
|
||||||
|
{
|
||||||
|
list.Add(style);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = list.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var item = list[i];
|
||||||
|
if (item == style)
|
||||||
|
{
|
||||||
|
list.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ConsoleStyleKind.Reset:
|
||||||
|
// An inline reset applies only to inline styles
|
||||||
|
if (style.IsInline)
|
||||||
|
{
|
||||||
|
// Clear only inline styles (and not regular)
|
||||||
|
foreach(var keyPair in this)
|
||||||
|
{
|
||||||
|
var styleList = keyPair.Value;
|
||||||
|
for(int i = styleList.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var styleIn = styleList[i];
|
||||||
|
if (styleIn.IsInline)
|
||||||
|
{
|
||||||
|
styleList.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
Kalk/Consolus/ConsoleTextWriter.cs
Normal file
86
Kalk/Consolus/ConsoleTextWriter.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
public class ConsoleTextWriter : TextWriter
|
||||||
|
{
|
||||||
|
private char[] _buffer;
|
||||||
|
private int _count;
|
||||||
|
|
||||||
|
public ConsoleTextWriter(TextWriter writer)
|
||||||
|
{
|
||||||
|
Backend = writer ?? throw new ArgumentNullException(nameof(writer));
|
||||||
|
_buffer = new char[4096];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Encoding Encoding => Backend.Encoding;
|
||||||
|
|
||||||
|
public TextWriter Backend { get; }
|
||||||
|
|
||||||
|
public void Commit()
|
||||||
|
{
|
||||||
|
VisibleCharacterCount = 0;
|
||||||
|
Backend.Write(_buffer, 0, _count);
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Write(buffer[index + i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(string value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
for (int i = 0; i < value.Length; i++)
|
||||||
|
{
|
||||||
|
Write(value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int VisibleCharacterCount { get; internal set; }
|
||||||
|
|
||||||
|
|
||||||
|
public override void Write(char value)
|
||||||
|
{
|
||||||
|
WriteInternal(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int min)
|
||||||
|
{
|
||||||
|
if (_buffer.Length < min)
|
||||||
|
{
|
||||||
|
int newCapacity = _buffer.Length * 2;
|
||||||
|
// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
|
||||||
|
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
|
||||||
|
if ((uint)newCapacity > int.MaxValue) newCapacity = int.MaxValue;
|
||||||
|
if (newCapacity < min) newCapacity = min;
|
||||||
|
|
||||||
|
var newBuffer = new char[newCapacity];
|
||||||
|
if (_count > 0)
|
||||||
|
{
|
||||||
|
Array.Copy(_buffer, 0,newBuffer, 0, _count);
|
||||||
|
}
|
||||||
|
_buffer = newBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void WriteInternal(char value)
|
||||||
|
{
|
||||||
|
var newCount = _count + 1;
|
||||||
|
if (_buffer.Length < newCount)
|
||||||
|
{
|
||||||
|
EnsureCapacity(newCount);
|
||||||
|
}
|
||||||
|
_buffer[_count] = value;
|
||||||
|
_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Kalk/Consolus/Consolus.csproj
Normal file
10
Kalk/Consolus/Consolus.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="_CheckForGenerateAppxPackageOnBuild" />
|
||||||
|
|
||||||
|
</Project>
|
||||||
71
Kalk/Consolus/WindowsHelper.cs
Normal file
71
Kalk/Consolus/WindowsHelper.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Consolus
|
||||||
|
{
|
||||||
|
internal static class WindowsHelper
|
||||||
|
{
|
||||||
|
private const int STD_OUTPUT_HANDLE = -11;
|
||||||
|
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
|
||||||
|
|
||||||
|
public static void EnableAnsiEscapeOnWindows()
|
||||||
|
{
|
||||||
|
var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
if (iStdOut != IntPtr.Zero && GetConsoleMode(iStdOut, out uint outConsoleMode))
|
||||||
|
{
|
||||||
|
SetConsoleMode(iStdOut, outConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32")]
|
||||||
|
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
|
||||||
|
|
||||||
|
[DllImport("kernel32")]
|
||||||
|
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
|
||||||
|
|
||||||
|
[DllImport("kernel32")]
|
||||||
|
private static extern IntPtr GetStdHandle(int nStdHandle);
|
||||||
|
|
||||||
|
[DllImport("kernel32")]
|
||||||
|
private static extern IntPtr GetConsoleWindow();
|
||||||
|
|
||||||
|
[DllImport("kernel32")]
|
||||||
|
private static extern IntPtr GetCurrentProcessId();
|
||||||
|
|
||||||
|
[DllImport("user32")]
|
||||||
|
private static extern int GetWindowThreadProcessId(IntPtr hWnd, ref IntPtr ProcessId);
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public static bool HasConsoleWindows()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return GetConsoleWindow() != IntPtr.Zero;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Environment.UserInteractive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public static bool IsSelfConsoleWindows()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IntPtr hConsole = GetConsoleWindow();
|
||||||
|
IntPtr hProcessId = IntPtr.Zero;
|
||||||
|
GetWindowThreadProcessId(hConsole, ref hProcessId);
|
||||||
|
var processId = GetCurrentProcessId();
|
||||||
|
return processId == hProcessId;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Kalk/Kalk.Core/Helpers/KalkCsvReader.cs
Normal file
136
Kalk/Kalk.Core/Helpers/KalkCsvReader.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using CsvHelper;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core.Helpers
|
||||||
|
{
|
||||||
|
class KalkCsvReader : IEnumerable<ScriptArray>
|
||||||
|
{
|
||||||
|
private readonly Func<TextReader> _getReader;
|
||||||
|
private readonly bool _readHeaders;
|
||||||
|
|
||||||
|
public KalkCsvReader(Func<TextReader> getReader, bool readHeaders)
|
||||||
|
{
|
||||||
|
_getReader = getReader;
|
||||||
|
_readHeaders = readHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<ScriptArray> GetEnumerator()
|
||||||
|
{
|
||||||
|
return new CsvFileEnumerator(_getReader(), _readHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CsvFileEnumerator : IEnumerator<ScriptArray>
|
||||||
|
{
|
||||||
|
private readonly bool _readHeader;
|
||||||
|
private readonly TextReader _reader;
|
||||||
|
private readonly CsvReader _csvReader;
|
||||||
|
private readonly string[] _headers;
|
||||||
|
|
||||||
|
public CsvFileEnumerator(TextReader reader, bool readHeader)
|
||||||
|
{
|
||||||
|
_readHeader = readHeader;
|
||||||
|
_reader = reader;
|
||||||
|
_csvReader = new CsvReader(_reader, CultureInfo.InvariantCulture);
|
||||||
|
if (_readHeader && _csvReader.Read())
|
||||||
|
{
|
||||||
|
if (_csvReader.ReadHeader())
|
||||||
|
{
|
||||||
|
_headers = _csvReader.Context.Reader.HeaderRecord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_csvReader.Read())
|
||||||
|
{
|
||||||
|
var array = new ScriptArray();
|
||||||
|
Current = array;
|
||||||
|
|
||||||
|
var row = _csvReader.Context.Reader.Parser.Record;
|
||||||
|
if (row != null)
|
||||||
|
{
|
||||||
|
int columnCount = row?.Length ?? 0;
|
||||||
|
|
||||||
|
if (_headers != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < columnCount; i++)
|
||||||
|
{
|
||||||
|
var cell = ParseCell(row[i]);
|
||||||
|
array.Add(cell);
|
||||||
|
if (i < _headers.Length)
|
||||||
|
{
|
||||||
|
array.SetValue(_headers[i], cell, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < columnCount; i++)
|
||||||
|
{
|
||||||
|
var cell = ParseCell(row[i]);
|
||||||
|
array.Add(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ParseCell(string cell)
|
||||||
|
{
|
||||||
|
if (int.TryParse(cell, out var intResult))
|
||||||
|
{
|
||||||
|
return intResult;
|
||||||
|
}
|
||||||
|
if (long.TryParse(cell, out var longResult))
|
||||||
|
{
|
||||||
|
return longResult;
|
||||||
|
}
|
||||||
|
if (BigInteger.TryParse(cell, out var bigIntResult))
|
||||||
|
{
|
||||||
|
return bigIntResult;
|
||||||
|
}
|
||||||
|
if (double.TryParse(cell, out var result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (DateTime.TryParse(cell, out var dateTime))
|
||||||
|
{
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Reset not supported on csv reader");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptArray Current { get; private set; }
|
||||||
|
|
||||||
|
object? IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_csvReader.Dispose();
|
||||||
|
_reader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Kalk/Kalk.Core/Helpers/LineReader.cs
Normal file
57
Kalk/Kalk.Core/Helpers/LineReader.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Kalk.Core.Helpers
|
||||||
|
{
|
||||||
|
class LineReader : IEnumerable<string>
|
||||||
|
{
|
||||||
|
private readonly Func<TextReader> _getReader;
|
||||||
|
|
||||||
|
public LineReader(Func<TextReader> getReader)
|
||||||
|
{
|
||||||
|
this._getReader = getReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<string> GetEnumerator()
|
||||||
|
{
|
||||||
|
return new LineEnumerator(_getReader());
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LineEnumerator : IEnumerator<string>
|
||||||
|
{
|
||||||
|
private readonly TextReader _reader;
|
||||||
|
|
||||||
|
public LineEnumerator(TextReader reader)
|
||||||
|
{
|
||||||
|
_reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
Current = _reader.ReadLine();
|
||||||
|
return Current != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Cannot reset a line reader");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Current { get; private set; }
|
||||||
|
|
||||||
|
object? IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_reader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Kalk/Kalk.Core/Helpers/UnsafeHelpers.cs
Normal file
36
Kalk/Kalk.Core/Helpers/UnsafeHelpers.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core.Helpers
|
||||||
|
{
|
||||||
|
internal static class UnsafeHelpers
|
||||||
|
{
|
||||||
|
public static unsafe object BitCast<T>(KalkNativeBuffer src) where T: unmanaged
|
||||||
|
{
|
||||||
|
var countItem = src.Count / sizeof(T);
|
||||||
|
if (countItem == 0)
|
||||||
|
{
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countItem == 1)
|
||||||
|
{
|
||||||
|
return MemoryMarshal.Cast<byte, T>(src.AsSpan())[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
var array = MemoryMarshal.Cast<byte, T>(src.AsSpan());
|
||||||
|
var scriptArray = new ScriptArray(array.Length);
|
||||||
|
for (int i = 0; i < array.Length; i++)
|
||||||
|
{
|
||||||
|
scriptArray[i] = array[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
Kalk/Kalk.Core/IFileService.cs
Normal file
76
Kalk/Kalk.Core/IFileService.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple file abstraction to mitigate UWP access issues.
|
||||||
|
/// </summary>
|
||||||
|
public interface IFileService
|
||||||
|
{
|
||||||
|
Stream FileOpen(string path, FileMode mode, FileAccess access, FileShare share);
|
||||||
|
|
||||||
|
void DirectoryCreate(string path);
|
||||||
|
|
||||||
|
IEnumerable<string> EnumerateFileSystemEntries(
|
||||||
|
string path,
|
||||||
|
string searchPattern,
|
||||||
|
SearchOption searchOption);
|
||||||
|
|
||||||
|
void DirectoryDelete(string path, bool recursive);
|
||||||
|
|
||||||
|
string FileReadAllText(string path);
|
||||||
|
|
||||||
|
void FileDelete(string path);
|
||||||
|
|
||||||
|
bool FileExists(string path);
|
||||||
|
|
||||||
|
bool DirectoryExists(string path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of file service using .NET API directly (not working on UWP)
|
||||||
|
/// </summary>
|
||||||
|
class DefaultFileService : IFileService
|
||||||
|
{
|
||||||
|
public Stream FileOpen(string path, FileMode mode, FileAccess access, FileShare share)
|
||||||
|
{
|
||||||
|
return new FileStream(path, mode, access, share);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DirectoryCreate(string path)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
|
||||||
|
{
|
||||||
|
return Directory.EnumerateFileSystemEntries(path, searchPattern, searchOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DirectoryDelete(string path, bool recursive)
|
||||||
|
{
|
||||||
|
Directory.Delete(path, recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FileReadAllText(string path)
|
||||||
|
{
|
||||||
|
return File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FileDelete(string path)
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FileExists(string path)
|
||||||
|
{
|
||||||
|
return File.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DirectoryExists(string path)
|
||||||
|
{
|
||||||
|
return Directory.Exists(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Kalk/Kalk.Core/Kalk.Core.csproj
Normal file
51
Kalk/Kalk.Core/Kalk.Core.csproj
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<KalkGitVersion>true</KalkGitVersion>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<NoWarn>$(NoWarn);CS8632</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="core.kalk" />
|
||||||
|
<None Remove="units.kalk" />
|
||||||
|
<EmbeddedResource Include="units.kalk" />
|
||||||
|
<EmbeddedResource Include="core.kalk" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||||
|
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||||
|
<PackageReference Include="System.Runtime.Numerics" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Scriban" Version="5.5.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Consolus\Consolus.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Modules\Vectors\colorspaces.kalk">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Modules\Vectors\TypesModule.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>TypesModule.generated.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Modules\Vectors\TypesModule.generated.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>TypesModule.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
3
Kalk/Kalk.Core/Kalk.Core.csproj.DotSettings
Normal file
3
Kalk/Kalk.Core/Kalk.Core.csproj.DotSettings
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=model/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=types/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
31
Kalk/Kalk.Core/Kalk.Core.sln
Normal file
31
Kalk/Kalk.Core/Kalk.Core.sln
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.32112.339
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kalk.Core", "Kalk.Core.csproj", "{0CB7389A-95B7-4CFF-B1E4-29BCD14B05B6}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolus", "..\Consolus\Consolus.csproj", "{C937AD35-3CAC-4EF7-89E0-D70220FF320E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{0CB7389A-95B7-4CFF-B1E4-29BCD14B05B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0CB7389A-95B7-4CFF-B1E4-29BCD14B05B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0CB7389A-95B7-4CFF-B1E4-29BCD14B05B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0CB7389A-95B7-4CFF-B1E4-29BCD14B05B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C937AD35-3CAC-4EF7-89E0-D70220FF320E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C937AD35-3CAC-4EF7-89E0-D70220FF320E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C937AD35-3CAC-4EF7-89E0-D70220FF320E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C937AD35-3CAC-4EF7-89E0-D70220FF320E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {8D4CA131-59E2-4B7B-B8CA-97587622D4D0}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
30
Kalk/Kalk.Core/KalkAction.cs
Normal file
30
Kalk/Kalk.Core/KalkAction.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public enum KalkAction
|
||||||
|
{
|
||||||
|
Exit,
|
||||||
|
CursorLeft,
|
||||||
|
CursorRight,
|
||||||
|
CursorLeftWord,
|
||||||
|
CursorRightWord,
|
||||||
|
CursorStartOfLine,
|
||||||
|
CursorEndOfLine,
|
||||||
|
HistoryPrevious,
|
||||||
|
HistoryNext,
|
||||||
|
DeleteCharacterLeft,
|
||||||
|
DeleteCharacterLeftAndCopy,
|
||||||
|
DeleteCharacterRight,
|
||||||
|
DeleteCharacterRightAndCopy,
|
||||||
|
DeleteWordLeft,
|
||||||
|
DeleteWordRight,
|
||||||
|
Completion,
|
||||||
|
DeleteTextRightAndCopy,
|
||||||
|
DeleteWordRightAndCopy,
|
||||||
|
DeleteWordLeftAndCopy,
|
||||||
|
CopySelection,
|
||||||
|
CutSelection,
|
||||||
|
PasteClipboard,
|
||||||
|
ValidateLine,
|
||||||
|
ForceValidateLine,
|
||||||
|
}
|
||||||
|
}
|
||||||
90
Kalk/Kalk.Core/KalkActionObject.cs
Normal file
90
Kalk/Kalk.Core/KalkActionObject.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An action object returned by <see cref="KalkEngine.Action"/> and used when evaluating shortcuts.
|
||||||
|
/// </summary>
|
||||||
|
public class KalkActionObject : ScriptObject
|
||||||
|
{
|
||||||
|
public KalkActionObject(string action)
|
||||||
|
{
|
||||||
|
Action = action ?? throw new ArgumentNullException(nameof(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Action
|
||||||
|
{
|
||||||
|
get => GetSafeValue<string>("action");
|
||||||
|
set => SetValue("action", value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Call(Action<KalkAction> run)
|
||||||
|
{
|
||||||
|
var action = Action;
|
||||||
|
if (action == null) return;
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case "cursor_left":
|
||||||
|
run(KalkAction.CursorLeft);
|
||||||
|
break;
|
||||||
|
case "cursor_right":
|
||||||
|
run(KalkAction.CursorRight);
|
||||||
|
break;
|
||||||
|
case "history_previous":
|
||||||
|
run(KalkAction.HistoryPrevious);
|
||||||
|
break;
|
||||||
|
case "history_next":
|
||||||
|
run(KalkAction.HistoryNext);
|
||||||
|
break;
|
||||||
|
case "copy":
|
||||||
|
run(KalkAction.CopySelection);
|
||||||
|
break;
|
||||||
|
case "cut":
|
||||||
|
run(KalkAction.CutSelection);
|
||||||
|
break;
|
||||||
|
case "paste":
|
||||||
|
run(KalkAction.PasteClipboard);
|
||||||
|
break;
|
||||||
|
case "cursor_word_left":
|
||||||
|
run(KalkAction.CursorLeftWord);
|
||||||
|
break;
|
||||||
|
case "cursor_word_right":
|
||||||
|
run(KalkAction.CursorRightWord);
|
||||||
|
break;
|
||||||
|
case "cursor_line_start":
|
||||||
|
run(KalkAction.CursorStartOfLine);
|
||||||
|
break;
|
||||||
|
case "cursor_line_end":
|
||||||
|
run(KalkAction.CursorEndOfLine);
|
||||||
|
break;
|
||||||
|
case "completion":
|
||||||
|
run(KalkAction.Completion);
|
||||||
|
break;
|
||||||
|
case "delete_left":
|
||||||
|
run(KalkAction.DeleteCharacterLeft);
|
||||||
|
break;
|
||||||
|
case "delete_right":
|
||||||
|
run(KalkAction.DeleteCharacterRight);
|
||||||
|
break;
|
||||||
|
case "delete_word_left":
|
||||||
|
run(KalkAction.DeleteWordLeft);
|
||||||
|
break;
|
||||||
|
case "delete_word_right":
|
||||||
|
run(KalkAction.DeleteWordRight);
|
||||||
|
break;
|
||||||
|
case "validate_line":
|
||||||
|
run(KalkAction.ValidateLine);
|
||||||
|
break;
|
||||||
|
case "force_validate_line":
|
||||||
|
run(KalkAction.ForceValidateLine);
|
||||||
|
break;
|
||||||
|
case "exit":
|
||||||
|
run(KalkAction.Exit);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException($"Action `{action}` is not supported", nameof(action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Kalk/Kalk.Core/KalkAliases.cs
Normal file
112
Kalk/Kalk.Core/KalkAliases.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkAliases : ScriptObject, IScriptCustomFunction
|
||||||
|
{
|
||||||
|
public KalkAliases()
|
||||||
|
{
|
||||||
|
Aliases = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, string> Aliases { get; }
|
||||||
|
|
||||||
|
public int RequiredParameterCount => 0;
|
||||||
|
|
||||||
|
public int ParameterCount => 0;
|
||||||
|
|
||||||
|
public ScriptVarParamKind VarParamKind => ScriptVarParamKind.None;
|
||||||
|
|
||||||
|
public Type ReturnType => typeof(object);
|
||||||
|
|
||||||
|
public ScriptParameterInfo GetParameterInfo(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Aliases don't have any parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAlias(KalkAlias alias)
|
||||||
|
{
|
||||||
|
if (alias == null) throw new ArgumentNullException(nameof(alias));
|
||||||
|
Add(alias.Name, alias);
|
||||||
|
|
||||||
|
foreach (string aliasName in alias.Aliases)
|
||||||
|
{
|
||||||
|
Aliases[aliasName] = alias.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetAlias(string name, out string alias)
|
||||||
|
{
|
||||||
|
return Aliases.TryGetValue(name, out alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out KalkAlias value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
if (TryGetValue(null, new SourceSpan(), key, out var valueObj))
|
||||||
|
{
|
||||||
|
value = (KalkAlias) valueObj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly)
|
||||||
|
{
|
||||||
|
// In the case of using KalkSymbols outside of the scripting engine
|
||||||
|
if (context == null) return base.TrySetValue(null, span, member, value, readOnly);
|
||||||
|
|
||||||
|
// Otherwise, we are not allowing to modify this object.
|
||||||
|
throw new ScriptRuntimeException(span, "Aliases object can't be modified directly. You need to use the command `shortcut` instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
if (!(callerContext.Parent is ScriptExpressionStatement))
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display((KalkEngine)context, "Builtin Aliases", filter => !filter.IsUser);
|
||||||
|
Display((KalkEngine) context, "User-defined Aliases", filter => filter.IsUser);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Display(KalkEngine engine, string title, Func<KalkAlias, bool> filter = null, bool addBlankLine = false)
|
||||||
|
{
|
||||||
|
if (engine == null) throw new ArgumentNullException(nameof(engine));
|
||||||
|
if (title == null) throw new ArgumentNullException(nameof(title));
|
||||||
|
|
||||||
|
bool isFirst = true;
|
||||||
|
foreach (var aliasKey in this.Keys.OrderBy(x => x))
|
||||||
|
{
|
||||||
|
var alias = this[aliasKey] as KalkAlias;
|
||||||
|
if (alias == null || (filter != null && !filter(alias))) continue;
|
||||||
|
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine($"# {title}");
|
||||||
|
}
|
||||||
|
else if (addBlankLine)
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine("");
|
||||||
|
}
|
||||||
|
isFirst = false;
|
||||||
|
|
||||||
|
engine.WriteHighlightLine(alias.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
return new ValueTask<object>(Invoke(context, callerContext, arguments, blockStatement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Kalk/Kalk.Core/KalkConfig.cs
Normal file
43
Kalk/Kalk.Core/KalkConfig.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkConfig : ScriptObject
|
||||||
|
{
|
||||||
|
public const int DefaultHelpMinColumn = 30;
|
||||||
|
public const int DefaultHelpMaxColumn = 100;
|
||||||
|
public const string DefaultLimitToString = "auto";
|
||||||
|
public const string DefaultEncoding = "utf-8";
|
||||||
|
|
||||||
|
public const string LimitToStringProp = "limit_to_string";
|
||||||
|
public const string EncodingProp = "encoding";
|
||||||
|
|
||||||
|
public KalkConfig()
|
||||||
|
{
|
||||||
|
HelpMaxColumn = DefaultHelpMaxColumn;
|
||||||
|
LimitToString = DefaultLimitToString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int HelpMaxColumn
|
||||||
|
{
|
||||||
|
get => GetSafeValue<int>("help_max_column", DefaultHelpMaxColumn);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < DefaultHelpMinColumn) value = DefaultHelpMinColumn;
|
||||||
|
SetValue("help_max_column", value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LimitToString
|
||||||
|
{
|
||||||
|
get => GetSafeValue(LimitToStringProp, DefaultLimitToString);
|
||||||
|
set => SetValue(LimitToStringProp, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Encoding
|
||||||
|
{
|
||||||
|
get => GetSafeValue(EncodingProp, DefaultEncoding);
|
||||||
|
set => SetValue(EncodingProp, value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Kalk/Kalk.Core/KalkDisplayMode.cs
Normal file
11
Kalk/Kalk.Core/KalkDisplayMode.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public enum KalkDisplayMode
|
||||||
|
{
|
||||||
|
Raw,
|
||||||
|
|
||||||
|
Standard,
|
||||||
|
|
||||||
|
Developer
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Kalk/Kalk.Core/KalkDisplayModeHelper.cs
Normal file
52
Kalk/Kalk.Core/KalkDisplayModeHelper.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public static class KalkDisplayModeHelper
|
||||||
|
{
|
||||||
|
public const string Raw = "raw";
|
||||||
|
|
||||||
|
public const string Standard = "std";
|
||||||
|
|
||||||
|
public const string Developer = "dev";
|
||||||
|
|
||||||
|
|
||||||
|
public static string ToText(this KalkDisplayMode mode)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case KalkDisplayMode.Raw:
|
||||||
|
return Raw;
|
||||||
|
case KalkDisplayMode.Standard:
|
||||||
|
return Standard;
|
||||||
|
case KalkDisplayMode.Developer:
|
||||||
|
return Developer;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParse(string mode, out KalkDisplayMode fullMode)
|
||||||
|
{
|
||||||
|
fullMode = KalkDisplayMode.Standard;
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case Raw:
|
||||||
|
fullMode = KalkDisplayMode.Raw;
|
||||||
|
return true;
|
||||||
|
case Standard:
|
||||||
|
fullMode = KalkDisplayMode.Standard;
|
||||||
|
return true;
|
||||||
|
case Developer:
|
||||||
|
fullMode = KalkDisplayMode.Developer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KalkDisplayMode SafeParse(string text)
|
||||||
|
{
|
||||||
|
return TryParse(text, out var mode) ? mode : KalkDisplayMode.Standard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1097
Kalk/Kalk.Core/KalkEngine.Functions.General.cs
Normal file
1097
Kalk/Kalk.Core/KalkEngine.Functions.General.cs
Normal file
File diff suppressed because it is too large
Load Diff
270
Kalk/Kalk.Core/KalkEngine.Functions.Units.cs
Normal file
270
Kalk/Kalk.Core/KalkEngine.Functions.Units.cs
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public partial class KalkEngine
|
||||||
|
{
|
||||||
|
public const string CategoryUnits = "Unit Functions";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If used in an expression, returns an object containing all units defined.
|
||||||
|
/// Otherwise it will display units in a friendly format.
|
||||||
|
/// By default, no units are defined. You can define units by using the `unit` function
|
||||||
|
/// and you can also import predefined units or currencies via `import StandardUnits` or
|
||||||
|
/// `import Currencies`.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// ```kalk
|
||||||
|
/// >>> unit(tomato, "A tomato unit", prefix: "decimal")
|
||||||
|
/// # unit(tomato, "A tomato unit", prefix: "decimal")
|
||||||
|
/// out = tomato
|
||||||
|
/// >>> unit(ketchup, "A ketchup unit", kup, 5 tomato, prefix: "decimal")
|
||||||
|
/// # unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
|
||||||
|
/// out = kup
|
||||||
|
/// >>> units
|
||||||
|
/// # User Defined Units
|
||||||
|
/// unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
|
||||||
|
/// - yottaketchup/Ykup, zettaketchup/Zkup, exaketchup/Ekup, petaketchup/Pkup, teraketchup/Tkup,
|
||||||
|
/// gigaketchup/Gkup, megaketchup/Mkup, kiloketchup/kkup, hectoketchup/hkup, decaketchup/dakup,
|
||||||
|
/// deciketchup/dkup, centiketchup/ckup, milliketchup/mkup, microketchup/µkup, nanoketchup/nkup,
|
||||||
|
/// picoketchup/pkup, femtoketchup/fkup, attoketchup/akup, zeptoketchup/zkup, yoctoketchup/ykup
|
||||||
|
///
|
||||||
|
/// unit(tomato, "A tomato unit", tomato, prefix: "decimal")
|
||||||
|
/// - yottatomato/Ytomato, zettatomato/Ztomato, exatomato/Etomato, petatomato/Ptomato,
|
||||||
|
/// teratomato/Ttomato, gigatomato/Gtomato, megatomato/Mtomato, kilotomato/ktomato,
|
||||||
|
/// hectotomato/htomato, decatomato/datomato, decitomato/dtomato, centitomato/ctomato,
|
||||||
|
/// millitomato/mtomato, microtomato/µtomato, nanotomato/ntomato, picotomato/ptomato,
|
||||||
|
/// femtotomato/ftomato, attotomato/atomato, zeptotomato/ztomato, yoctotomato/ytomato
|
||||||
|
/// ```
|
||||||
|
/// </example>
|
||||||
|
[KalkExport("units", CategoryUnits)]
|
||||||
|
public KalkUnits Units { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts from one value unit to a destination unit.
|
||||||
|
///
|
||||||
|
/// The pipe operator |> can be used between the src and destination unit to make it
|
||||||
|
/// more readable. Example: `105 g |> to kg`
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="src">The source value with units.</param>
|
||||||
|
/// <param name="dst">The destination unit.</param>
|
||||||
|
/// <returns>The result of the calculation.</returns>
|
||||||
|
/// <example>
|
||||||
|
/// ```kalk
|
||||||
|
/// >>> import StandardUnits
|
||||||
|
/// # 1294 units successfully imported from module `StandardUnits`.
|
||||||
|
/// >>> 10 kg/s |> to kg/h
|
||||||
|
/// # ((10 * kg) / s) |> to(kg / h)
|
||||||
|
/// out = 36000 * kg / h
|
||||||
|
/// >>> 50 kg/m |> to g/km
|
||||||
|
/// # ((50 * kg) / m) |> to(g / km)
|
||||||
|
/// out = 50000000 * g / km
|
||||||
|
/// ```
|
||||||
|
/// </example>
|
||||||
|
[KalkExport("to", CategoryUnits)]
|
||||||
|
public KalkExpression ConvertTo(KalkExpression src, KalkExpression dst)
|
||||||
|
{
|
||||||
|
return src.ConvertTo(this, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a unit with the specified name and characteristics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Long name of the unit.</param>
|
||||||
|
/// <param name="description">A description of the unit. This value is optional.</param>
|
||||||
|
/// <param name="symbol">Short name (symbol) of the unit. This value is optional.</param>
|
||||||
|
/// <param name="value">The expression value of this unit. This value is optional.</param>
|
||||||
|
/// <param name="plural">The plural name of this unit. This value is optional.</param>
|
||||||
|
/// <param name="prefix">A comma list separated of prefix kinds:
|
||||||
|
/// - "decimal": Defines the twenty prefixes for the International System of Units (SI). Example: Yotta/Y, kilo/k, milli/m...
|
||||||
|
/// - "binary": Defines the binary prefixes. See https://en.wikipedia.org/wiki/Binary_prefix. Example: kibbi/Ki, mebi/Mi...
|
||||||
|
/// - Individual prefixes:
|
||||||
|
/// Decimal prefixes:
|
||||||
|
/// - `Y` - `Yotta` (10^24)
|
||||||
|
/// - `Z` - `Zetta` (10^21)
|
||||||
|
/// - `E` - `Exa` (10^18)
|
||||||
|
/// - `P` - `Peta` (10^15)
|
||||||
|
/// - `T` - `Tera` (10^12)
|
||||||
|
/// - `G` - `Giga` (10^9)
|
||||||
|
/// - `M` - `Mega` (10^6)
|
||||||
|
/// - `k` - `kilo` (10^3)
|
||||||
|
/// - `h` - `hecto` (10^2)
|
||||||
|
/// - `da` - `deca` (10^1)
|
||||||
|
/// - `d` - `deci` (10^)-1
|
||||||
|
/// - `c` - `centi` (10^)-2
|
||||||
|
/// - `m` - `milli` (10^)-3
|
||||||
|
/// - `µ` - `micro` (10^-6)
|
||||||
|
/// - `n` - `nano` (10^)-9
|
||||||
|
/// - `p` - `pico` (10^)-12
|
||||||
|
/// - `f` - `femto` (10^)-15
|
||||||
|
/// - `a` - `atto` (10^)-18
|
||||||
|
/// - `z` - `zepto` (10^)-21
|
||||||
|
/// - `y` - `yocto` (10^)-24
|
||||||
|
///
|
||||||
|
/// Binary prefixes:
|
||||||
|
/// - `Ki` - `Kibi` (2^10)
|
||||||
|
/// - `Mi` - `Mibi` (2^20)
|
||||||
|
/// - `Gi` - `Gibi` (2^30)
|
||||||
|
/// - `Ti` - `Tibi` (2^40)
|
||||||
|
/// - `Pi` - `Pibi` (2^50)
|
||||||
|
/// - `Ei` - `Eibi` (2^60)
|
||||||
|
/// - `Zi` - `Zibi` (2^70)
|
||||||
|
/// - `Yi` - `Yibi` (2^80)
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The associated unit object.</returns>
|
||||||
|
/// <example>
|
||||||
|
/// ```kalk
|
||||||
|
/// >>> unit(tomato, "A tomato unit", prefix: "decimal")
|
||||||
|
/// # unit(tomato, "A tomato unit", prefix: "decimal")
|
||||||
|
/// out = tomato
|
||||||
|
/// >>> unit(ketchup, "A ketchup unit", kup, 5 tomato, prefix: "decimal")
|
||||||
|
/// # unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
|
||||||
|
/// out = kup
|
||||||
|
/// >>> 4 kup
|
||||||
|
/// # 4 * kup
|
||||||
|
/// out = 20 * tomato
|
||||||
|
/// >>> tomato
|
||||||
|
/// unit(tomato, "A tomato unit", tomato, prefix: "decimal")
|
||||||
|
/// - yottatomato/Ytomato, zettatomato/Ztomato, exatomato/Etomato, petatomato/Ptomato,
|
||||||
|
/// teratomato/Ttomato, gigatomato/Gtomato, megatomato/Mtomato, kilotomato/ktomato,
|
||||||
|
/// hectotomato/htomato, decatomato/datomato, decitomato/dtomato, centitomato/ctomato,
|
||||||
|
/// millitomato/mtomato, microtomato/µtomato, nanotomato/ntomato, picotomato/ptomato,
|
||||||
|
/// femtotomato/ftomato, attotomato/atomato, zeptotomato/ztomato, yoctotomato/ytomato
|
||||||
|
/// >>> ketchup
|
||||||
|
/// unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
|
||||||
|
/// - yottaketchup/Ykup, zettaketchup/Zkup, exaketchup/Ekup, petaketchup/Pkup, teraketchup/Tkup,
|
||||||
|
/// gigaketchup/Gkup, megaketchup/Mkup, kiloketchup/kkup, hectoketchup/hkup, decaketchup/dakup,
|
||||||
|
/// deciketchup/dkup, centiketchup/ckup, milliketchup/mkup, microketchup/µkup, nanoketchup/nkup,
|
||||||
|
/// picoketchup/pkup, femtoketchup/fkup, attoketchup/akup, zeptoketchup/zkup, yoctoketchup/ykup
|
||||||
|
/// ```
|
||||||
|
/// </example>
|
||||||
|
///
|
||||||
|
[KalkExport("unit", CategoryUnits)]
|
||||||
|
public KalkExpression DefineUserUnit(ScriptVariable name, string description = null, ScriptVariable symbol = null, KalkExpression value = null, string plural = null, string prefix = null)
|
||||||
|
{
|
||||||
|
if (name == null || string.IsNullOrEmpty(name.Name)) throw new ArgumentNullException(nameof(name));
|
||||||
|
return RegisterUnit(new KalkUnit(name.Name), description, symbol?.Name, value, plural, prefix, isUser: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkExpression RegisterUnit(KalkUnit unit, string description = null, string symbol = null, KalkExpression value = null, string plural = null, string prefix = null, bool isUser = false)
|
||||||
|
{
|
||||||
|
if (unit == null) throw new ArgumentNullException(nameof(unit));
|
||||||
|
var name = unit.Name;
|
||||||
|
symbol ??= name;
|
||||||
|
|
||||||
|
// Override isUser
|
||||||
|
if (_registerAsSystem) isUser = false;
|
||||||
|
|
||||||
|
CheckVariableAvailable(name, nameof(name), false);
|
||||||
|
|
||||||
|
var prefixList = new List<KalkUnitPrefix>();
|
||||||
|
if (prefix != null)
|
||||||
|
{
|
||||||
|
var prefixes = prefix.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach(var prefixItem in prefixes)
|
||||||
|
{
|
||||||
|
if (prefixItem == "decimal")
|
||||||
|
{
|
||||||
|
prefixList.AddRange(KalkUnitPrefix.GetDecimals());
|
||||||
|
}
|
||||||
|
else if (prefixItem == "binary")
|
||||||
|
{
|
||||||
|
prefixList.AddRange(KalkUnitPrefix.GetBinaries());
|
||||||
|
}
|
||||||
|
else if (KalkUnitPrefix.TryGet(prefixItem, out var descriptor))
|
||||||
|
{
|
||||||
|
prefixList.Add(descriptor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"The prefix `{prefixItem}` does not exist.", nameof(prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefixList = prefixList.Distinct().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-check all prefix with name/symbol
|
||||||
|
foreach (var prefixDesc in prefixList)
|
||||||
|
{
|
||||||
|
var prefixWithName = $"{prefixDesc.Name}{name}";
|
||||||
|
CheckVariableAvailable(prefixWithName, nameof(name), false);
|
||||||
|
|
||||||
|
var prefixWithSymbol = $"{prefixDesc.Prefix}{symbol}";
|
||||||
|
CheckVariableAvailable(prefixWithSymbol, nameof(name), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
unit.Description = description;
|
||||||
|
unit.Symbol = symbol;
|
||||||
|
unit.Value = value;
|
||||||
|
unit.IsUser = isUser;
|
||||||
|
unit.Prefix = prefix;
|
||||||
|
if (plural != null)
|
||||||
|
{
|
||||||
|
unit.Plural = plural;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit.Symbol != unit.Name)
|
||||||
|
{
|
||||||
|
CheckVariableAvailable(unit.Symbol, nameof(symbol), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit.Plural != unit.Name)
|
||||||
|
{
|
||||||
|
CheckVariableAvailable(unit.Plural, nameof(plural), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we are all done after checking everything
|
||||||
|
|
||||||
|
Units.Add(name, unit);
|
||||||
|
|
||||||
|
|
||||||
|
if (unit.Symbol != unit.Name)
|
||||||
|
{
|
||||||
|
Units.Add(unit.Symbol, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit.Plural != unit.Name)
|
||||||
|
{
|
||||||
|
Units.Add(unit.Plural, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register prefixes
|
||||||
|
foreach (var prefixDesc in prefixList)
|
||||||
|
{
|
||||||
|
var prefixWithName = $"{prefixDesc.Name}{unit.Name}";
|
||||||
|
var prefixWithSymbol = $"{prefixDesc.Prefix}{unit.Symbol}";
|
||||||
|
|
||||||
|
var unitPrefix = new KalkUnit(prefixWithName)
|
||||||
|
{
|
||||||
|
Description = description,
|
||||||
|
Symbol = prefixWithSymbol,
|
||||||
|
Value = new KalkBinaryExpression(Math.Pow(prefixDesc.Base, prefixDesc.Exponent), ScriptBinaryOperator.Multiply, unit),
|
||||||
|
IsUser = isUser,
|
||||||
|
Parent = unit,
|
||||||
|
};
|
||||||
|
unit.Derived.Add(unitPrefix);
|
||||||
|
|
||||||
|
Units.Add(prefixWithName, unitPrefix);
|
||||||
|
Units.Add(prefixWithSymbol, unitPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckVariableAvailable(string name, string nameOf, bool prefix)
|
||||||
|
{
|
||||||
|
if (Units.ContainsKey(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(prefix ? $"The name with prefix `{name}` is already used by another unit." : $"The name `{name}` is already used by another unit.", nameOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Builtins.ContainsKey(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(prefix ? $"The name with prefix `{name}` is already used a builtin variable or function." : $"The name `{name}` is already used a builtin variable or function.", nameOf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
312
Kalk/Kalk.Core/KalkEngine.Functions.cs
Normal file
312
Kalk/Kalk.Core/KalkEngine.Functions.cs
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Kalk.Core.Modules;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public partial class KalkEngine
|
||||||
|
{
|
||||||
|
private bool _registerAsSystem;
|
||||||
|
|
||||||
|
private const string CoreFileName = "core.kalk";
|
||||||
|
|
||||||
|
private MiscModule _miscModule;
|
||||||
|
|
||||||
|
private void RegisterFunctions()
|
||||||
|
{
|
||||||
|
RegisterFunctionsAuto();
|
||||||
|
|
||||||
|
// builtins
|
||||||
|
GetOrCreateModule<MathModule>();
|
||||||
|
_miscModule = GetOrCreateModule<MiscModule>();
|
||||||
|
GetOrCreateModule<MemoryModule>();
|
||||||
|
GetOrCreateModule<TypesModule>();
|
||||||
|
GetOrCreateModule<VectorModule>();
|
||||||
|
|
||||||
|
var allModule = GetOrCreateModule<AllModule>();
|
||||||
|
allModule.Modules.Add(GetOrCreateModule<HardwareIntrinsicsModule>());
|
||||||
|
FileModule = GetOrCreateModule<FileModule>();
|
||||||
|
allModule.Modules.Add(FileModule);
|
||||||
|
allModule.Modules.Add(GetOrCreateModule<CsvModule>());
|
||||||
|
allModule.Modules.Add(GetOrCreateModule<StringModule>());
|
||||||
|
allModule.Modules.Add(GetOrCreateModule<CurrencyModule>());
|
||||||
|
allModule.Modules.Add(GetOrCreateModule<StandardUnitsModule>());
|
||||||
|
allModule.Modules.Add(GetOrCreateModule<WebModule>());
|
||||||
|
|
||||||
|
// Register last the system file
|
||||||
|
// shortcut(exit, "ctrl+z", action("exit"))
|
||||||
|
// Setup the exit depending on OS
|
||||||
|
//EvaluateTextImpl($"shortcut(exit, '{(OperatingSystem.IsWindows() ? "ctrl+z" : "ctrl+d")}', action('exit'))", string.Empty);
|
||||||
|
LoadCoreFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileModule FileModule { get; private set; }
|
||||||
|
|
||||||
|
private void LoadCoreFile()
|
||||||
|
{
|
||||||
|
LoadSystemFileFromResource(CoreFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadUserConfigFile()
|
||||||
|
{
|
||||||
|
if (KalkUserFolder == null) return;
|
||||||
|
const string configFileName = "config.kalk";
|
||||||
|
var userConfigFile = Path.Combine(KalkUserFolder, configFileName);
|
||||||
|
|
||||||
|
//WriteHighlightLine($"# Try loading from {userConfigFile}");
|
||||||
|
|
||||||
|
if (!FileService.FileExists(userConfigFile)) return;
|
||||||
|
|
||||||
|
using var stream = FileService.FileOpen(userConfigFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
LoadConfigFile($".kalk/{configFileName}", stream, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadSystemFileFromResource(string filename)
|
||||||
|
{
|
||||||
|
var stream = typeof(KalkEngine).Assembly.GetManifestResourceStream($"{typeof(KalkEngine).Namespace}.{filename}");
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"The resource {filename} was not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadConfigFile(filename, stream, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadConfigFile(string filename, Stream stream, bool isSystem)
|
||||||
|
{
|
||||||
|
// Register all units
|
||||||
|
bool previousEchoEnabled = EchoEnabled;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_registerAsSystem = isSystem;
|
||||||
|
EchoEnabled = false;
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
var text = reader.ReadToEnd();
|
||||||
|
EvaluateTextImpl(text, filename, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WriteErrorLine($"Unable to load file {filename}. Reason:\n{ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EchoEnabled = previousEchoEnabled;
|
||||||
|
_registerAsSystem = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction(string name, Action action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1>(string name, Action<T1> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1, T2>(string name, Action<T1, T2> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1, T2, T3>(string name, Action<T1, T2, T3> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1, T2, T3, T4>(string name, Action<T1, T2, T3, T4> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterConstant(string name, object value, string category = null)
|
||||||
|
{
|
||||||
|
RegisterVariable(name, value, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Delegate del, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, new DelegateCustomFunction(del), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkMatrix, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkMatrix, KalkMatrix> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkVector, KalkVector, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, object, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, bool> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<double, double> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, double> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, long> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<double, double, double> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkDoubleValue, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, Wrap(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkLongValue, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, Wrap(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkIntValue, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, Wrap(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<KalkCompositeValue, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, Wrap(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, int> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkVector<int>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkVector<KalkBool>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkVector<float>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkVector<double>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkMatrix<KalkBool>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkMatrix<int>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkMatrix<double>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkMatrix<float>> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkColorRgb> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
public void RegisterFunction(string name, Func<object[], KalkColorRgba> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
public void RegisterFunction(string name, Func<ScriptVariable, int, object[], object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, float> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, Func<object, object> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFunction(string name, IScriptCustomFunction func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterVariable(name, func, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterVariable(string name, object value, string category = null)
|
||||||
|
{
|
||||||
|
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||||
|
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||||
|
var names = name.Split(',');
|
||||||
|
|
||||||
|
KalkDescriptor descriptor = null;
|
||||||
|
|
||||||
|
foreach (var subName in names)
|
||||||
|
{
|
||||||
|
Builtins.SetValue(subName, value, true);
|
||||||
|
|
||||||
|
if (descriptor == null || !Descriptors.TryGetValue(names[0], out descriptor))
|
||||||
|
{
|
||||||
|
descriptor = new KalkDescriptor();
|
||||||
|
}
|
||||||
|
Descriptors.Add(subName, descriptor);
|
||||||
|
descriptor.Names.Add(subName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelegateCustomFunction Wrap(Func<KalkDoubleValue, object> func)
|
||||||
|
{
|
||||||
|
return DelegateCustomFunction.CreateFunc(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelegateCustomFunction Wrap(Func<KalkIntValue, object> func)
|
||||||
|
{
|
||||||
|
return DelegateCustomFunction.CreateFunc(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelegateCustomFunction Wrap(Func<KalkCompositeValue, object> func)
|
||||||
|
{
|
||||||
|
return DelegateCustomFunction.CreateFunc(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelegateCustomFunction Wrap(Func<KalkLongValue, object> func)
|
||||||
|
{
|
||||||
|
return DelegateCustomFunction.CreateFunc(func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
815
Kalk/Kalk.Core/KalkEngine.Highlight.cs
Normal file
815
Kalk/Kalk.Core/KalkEngine.Highlight.cs
Normal file
@@ -0,0 +1,815 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using Consolus;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public partial class KalkEngine
|
||||||
|
{
|
||||||
|
public const int DefaultLimitToStringNoAuto = 4096;
|
||||||
|
|
||||||
|
private static readonly TokenType[] MatchPairs = new[]
|
||||||
|
{
|
||||||
|
TokenType.OpenParen,
|
||||||
|
TokenType.CloseParen,
|
||||||
|
TokenType.OpenBracket,
|
||||||
|
TokenType.CloseBracket,
|
||||||
|
TokenType.OpenBrace,
|
||||||
|
TokenType.CloseBrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private readonly HashSet<string> ScriptKeywords = new HashSet<string>()
|
||||||
|
{
|
||||||
|
"if",
|
||||||
|
"else",
|
||||||
|
"end",
|
||||||
|
"for",
|
||||||
|
"in",
|
||||||
|
"case",
|
||||||
|
"when",
|
||||||
|
"while",
|
||||||
|
"break",
|
||||||
|
"continue",
|
||||||
|
"func",
|
||||||
|
"import",
|
||||||
|
"readonly",
|
||||||
|
"with",
|
||||||
|
"capture",
|
||||||
|
"ret",
|
||||||
|
"wrap",
|
||||||
|
"do",
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly HashSet<string> ValueKeywords = new HashSet<string>()
|
||||||
|
{
|
||||||
|
"null",
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static List<string> SplitStringBySpaceAndKeepSpace(string text)
|
||||||
|
{
|
||||||
|
// normalize line endings with \n
|
||||||
|
text = text.Replace("\r\n", "\n").Replace("\r", "\n");
|
||||||
|
var list = new List<string>();
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
bool isCurrentWhiteSpace = false;
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
var c = text[i];
|
||||||
|
if (char.IsWhiteSpace(c))
|
||||||
|
{
|
||||||
|
if (builder.Length > 0)
|
||||||
|
{
|
||||||
|
list.Add(builder.ToString());
|
||||||
|
builder.Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We put "\n" separately
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
list.Add("\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCurrentWhiteSpace = true;
|
||||||
|
}
|
||||||
|
else if (isCurrentWhiteSpace)
|
||||||
|
{
|
||||||
|
list.Add(builder.ToString());
|
||||||
|
builder.Length = 0;
|
||||||
|
isCurrentWhiteSpace = false;
|
||||||
|
}
|
||||||
|
builder.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder.Length > 0)
|
||||||
|
{
|
||||||
|
list.Add(builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void WriteHighlightVariableAndValueToConsole(string name, object value)
|
||||||
|
{
|
||||||
|
if (value is ScriptFunction function && !function.IsAnonymous)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($"{value}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int previousLimit = LimitToString;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int limit = 0;
|
||||||
|
var limitStr = Config.LimitToString;
|
||||||
|
if (limitStr == "auto")
|
||||||
|
{
|
||||||
|
if (HasInteractiveConsole && IsOutputSupportHighlighting)
|
||||||
|
{
|
||||||
|
limit = Console.WindowWidth * Console.WindowHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
limit = DefaultLimitToStringNoAuto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (limitStr == null || !int.TryParse(limitStr, out limit))
|
||||||
|
{
|
||||||
|
limit = DefaultLimitToStringNoAuto;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit < 0) limit = 0;
|
||||||
|
LimitToString = limit;
|
||||||
|
WriteHighlightLine($"{name} = {ObjectToString(value, true)}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LimitToString = previousLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteValueWithDisplayMode(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteValueWithDisplayMode(object value)
|
||||||
|
{
|
||||||
|
// If the type is displayable
|
||||||
|
if (value is IKalkDisplayable displayable)
|
||||||
|
{
|
||||||
|
displayable.Display(this, CurrentDisplay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise supports the default.
|
||||||
|
switch (CurrentDisplay)
|
||||||
|
{
|
||||||
|
case KalkDisplayMode.Raw:
|
||||||
|
case KalkDisplayMode.Standard:
|
||||||
|
return;
|
||||||
|
case KalkDisplayMode.Developer:
|
||||||
|
if (value is int i32)
|
||||||
|
{
|
||||||
|
WriteInt32(i32);
|
||||||
|
}
|
||||||
|
else if (value is uint u32)
|
||||||
|
{
|
||||||
|
WriteUInt32(u32);
|
||||||
|
}
|
||||||
|
else if (value is short i16)
|
||||||
|
{
|
||||||
|
WriteInt16(i16);
|
||||||
|
}
|
||||||
|
else if (value is ushort u16)
|
||||||
|
{
|
||||||
|
WriteUInt16(u16);
|
||||||
|
}
|
||||||
|
else if (value is sbyte i8)
|
||||||
|
{
|
||||||
|
WriteInt8(i8);
|
||||||
|
}
|
||||||
|
else if (value is byte u8)
|
||||||
|
{
|
||||||
|
WriteUInt8(u8);
|
||||||
|
}
|
||||||
|
else if (value is long i64)
|
||||||
|
{
|
||||||
|
WriteInt64(i64);
|
||||||
|
}
|
||||||
|
else if (value is ulong u64)
|
||||||
|
{
|
||||||
|
WriteUInt64(u64);
|
||||||
|
}
|
||||||
|
else if (value is float f32)
|
||||||
|
{
|
||||||
|
WriteFloat(f32);
|
||||||
|
}
|
||||||
|
else if (value is double f64)
|
||||||
|
{
|
||||||
|
WriteDouble(f64, "IEEE 754 - double - 64-bit");
|
||||||
|
}
|
||||||
|
else if (value is decimal dec)
|
||||||
|
{
|
||||||
|
WriteDouble((double)dec, "Decimal 128-bit displayed as IEEE 754 - double - 64-bit");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt32(int i32)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # int - 32-bit");
|
||||||
|
WriteInt32((uint)i32, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt16(short i16)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # short - 16-bit");
|
||||||
|
WriteInt16((ushort)i16, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt16(ushort u16)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # ushort - 16-bit");
|
||||||
|
WriteInt16(u16, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt32(uint u32)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # uint - 32-bit");
|
||||||
|
WriteInt32(u32, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt8(byte u8)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # byte - 8-bit");
|
||||||
|
WriteInt8Raw(u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt8(sbyte u8)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # sbyte - 8-bit");
|
||||||
|
WriteInt8Raw((byte)u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt32(uint u32, bool forFloat)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" = 0x_{u32 >> 16:X4}_{u32 & 0xFFFF:X4}");
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// Prints the hexa version
|
||||||
|
// # 0x____3____F____F____8____0____0____0____0
|
||||||
|
builder.Append("0x");
|
||||||
|
for (int i = 7; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append("____");
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u32 >> (i * 4)));
|
||||||
|
builder.Append(v.ToString("X1"));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
|
||||||
|
if (forFloat)
|
||||||
|
{
|
||||||
|
// Prints the float IEE754 mask
|
||||||
|
// # seee eeee efff ffff ffff ffff ffff ffff
|
||||||
|
WriteHighlightLine($" # seee eeee efff ffff ffff ffff ffff ffff");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the binary version
|
||||||
|
// # 0b_0011_1111_1111_1000_0000_0000_0000_0000
|
||||||
|
builder.Length = 0;
|
||||||
|
builder.Append("0b");
|
||||||
|
for (int i = 7; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append('_');
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u32 >> (i * 4)));
|
||||||
|
var leadingZero = BitOperations.LeadingZeroCount(v) - 28;
|
||||||
|
builder.Append('0', leadingZero);
|
||||||
|
if (v != 0) builder.Append(Convert.ToString(v, 2));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal void WriteInt16(uint u16, bool forHalf)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" = 0x_{u16:X4}");
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// Prints the hexa version
|
||||||
|
// # 0x____3____F____F____8
|
||||||
|
builder.Append("0x");
|
||||||
|
for (int i = 3; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append("____");
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u16 >> (i * 4)));
|
||||||
|
builder.Append(v.ToString("X1"));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
|
||||||
|
if (forHalf)
|
||||||
|
{
|
||||||
|
// Prints the float IEE754 mask
|
||||||
|
// # seee eeff ffff ffff
|
||||||
|
WriteHighlightLine($" # seee eeff ffff ffff");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the binary version
|
||||||
|
// # 0b_0011_1111_1111_1000
|
||||||
|
builder.Length = 0;
|
||||||
|
builder.Append("0b");
|
||||||
|
for (int i = 3; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append('_');
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u16 >> (i * 4)));
|
||||||
|
var leadingZero = BitOperations.LeadingZeroCount(v) - 28;
|
||||||
|
builder.Append('0', leadingZero);
|
||||||
|
if (v != 0) builder.Append(Convert.ToString(v, 2));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WriteInt8Raw(byte u8)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" = 0x_{u8:X2}");
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// Prints the hexa version
|
||||||
|
// # 0x____3____F
|
||||||
|
builder.Append("0x");
|
||||||
|
for (int i = 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append("____");
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u8 >> (i * 4)));
|
||||||
|
builder.Append(v.ToString("X1"));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
|
||||||
|
// Prints the binary version
|
||||||
|
// # 0b_0011_1111
|
||||||
|
builder.Length = 0;
|
||||||
|
builder.Append("0b");
|
||||||
|
for (int i = 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append('_');
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u8 >> (i * 4)));
|
||||||
|
var leadingZero = BitOperations.LeadingZeroCount(v) - 28;
|
||||||
|
builder.Append('0', leadingZero);
|
||||||
|
if (v != 0) builder.Append(Convert.ToString(v, 2));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt64(long i64)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # long - 64-bit");
|
||||||
|
WriteUInt64((ulong)i64, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt64(ulong u64)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" # ulong - 64-bit");
|
||||||
|
WriteUInt64(u64, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt64(ulong u64, bool forDouble)
|
||||||
|
{
|
||||||
|
WriteHighlightLine($" = 0x_{u64 >> 32:X8}_{((uint)u64) & 0xFFFFFFFF:X8}");
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// Prints the hexa version
|
||||||
|
// # 0x____3____F____F____8____0____0____0____0____0____0____0____0____0____0____0____0
|
||||||
|
builder.Append("0x");
|
||||||
|
for (int i = 15; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append("____");
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u64 >> (i * 4)));
|
||||||
|
builder.Append(v.ToString("X1"));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
|
||||||
|
if (forDouble)
|
||||||
|
{
|
||||||
|
// Prints the double IEE754 mask
|
||||||
|
// # seee eeee eeee ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
|
||||||
|
WriteHighlightLine($" # seee eeee eeee ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the binary version
|
||||||
|
// # 0b_0011_1111_1111_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000
|
||||||
|
builder.Length = 0;
|
||||||
|
builder.Append("0b");
|
||||||
|
for (int i = 15; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append('_');
|
||||||
|
|
||||||
|
var v = (byte)(0xF & (u64 >> (i * 4)));
|
||||||
|
var leadingZero = BitOperations.LeadingZeroCount(v) - 28;
|
||||||
|
builder.Append('0', leadingZero);
|
||||||
|
if (v != 0) builder.Append(Convert.ToString(v, 2));
|
||||||
|
}
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteDouble(double f64, string title)
|
||||||
|
{
|
||||||
|
var u64 = (ulong)BitConverter.DoubleToInt64Bits(f64);
|
||||||
|
WriteHighlightLine($" # {title}");
|
||||||
|
WriteHighlightLine($" #");
|
||||||
|
WriteUInt64(u64, true);
|
||||||
|
|
||||||
|
// Prints the 16 bits indices
|
||||||
|
WriteHighlightLine($" # 63 48 32 16 0");
|
||||||
|
|
||||||
|
// Prints the binary version
|
||||||
|
// # s eeee ffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
WriteHighlightLine($" #");
|
||||||
|
WriteHighlightLine($" # sign exponent |-------------------- fraction --------------------|");
|
||||||
|
// # 1 * 2 ^ (1023 - 1023) * 0b1.1000000000000000000000000000000000000000000000000000
|
||||||
|
var sign = (u64 >> 63);
|
||||||
|
var exponent = (u64 >> 52) & 0b_111_11111111;
|
||||||
|
var fraction = (u64 << 12) >> 12;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(sign != 0 ? " -1" : " 1");
|
||||||
|
builder.Append(" * 2 ^ (");
|
||||||
|
// exponent == 0 => subnormals
|
||||||
|
builder.Append($"{(exponent == 0 ? 1 : exponent),4} - 1023) * 0b{(exponent == 0 ? '0' : '1')}.");
|
||||||
|
var leadingFractionZero = BitOperations.LeadingZeroCount(fraction) - 12;
|
||||||
|
builder.Append('0', leadingFractionZero);
|
||||||
|
if (fraction != 0) builder.Append(Convert.ToString((long)fraction, 2));
|
||||||
|
WriteHighlightLine($" = {builder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteFloat(float f32)
|
||||||
|
{
|
||||||
|
var u32 = (uint)BitConverter.SingleToInt32Bits(f32);
|
||||||
|
WriteHighlightLine($" # IEEE 754 - float - 32-bit");
|
||||||
|
WriteHighlightLine($" #");
|
||||||
|
WriteInt32(u32, true);
|
||||||
|
|
||||||
|
// Prints the 16 bits indices
|
||||||
|
WriteHighlightLine($" # 31 24 16 8 0");
|
||||||
|
|
||||||
|
WriteHighlightLine($" #");
|
||||||
|
WriteHighlightLine($" # sign exponent |------ fraction -----|");
|
||||||
|
// # 1 * 2 ^ (127 - 127) * 0b1.10000000000000000000000
|
||||||
|
|
||||||
|
var sign = (u32 >> 31);
|
||||||
|
var exponent = (u32 >> 23) & 0b_111_11111111;
|
||||||
|
var fraction = (u32 << 9) >> 9;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(sign != 0 ? " -1" : " 1");
|
||||||
|
builder.Append(" * 2 ^ (");
|
||||||
|
// exponent == 0 => subnormals
|
||||||
|
builder.Append($"{(exponent == 0 ? 1 : exponent),3} - 127) * 0b{(exponent == 0 ? '0' : '1')}.");
|
||||||
|
var leadingFractionZero = BitOperations.LeadingZeroCount(fraction) - 9;
|
||||||
|
builder.Append('0', leadingFractionZero);
|
||||||
|
if (fraction != 0) builder.Append(Convert.ToString((long)fraction, 2));
|
||||||
|
WriteHighlightLine($" = {builder}f");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteHighlightAligned(string prefix, string text, string nextPrefix = null)
|
||||||
|
{
|
||||||
|
var list = SplitStringBySpaceAndKeepSpace(text);
|
||||||
|
if (list.Count == 0) return;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
bool lineHasItem = false;
|
||||||
|
|
||||||
|
var maxColumn = IsOutputSupportHighlighting ? Math.Min(Config.HelpMaxColumn, Console.BufferWidth) : Config.HelpMaxColumn;
|
||||||
|
|
||||||
|
if (nextPrefix == null)
|
||||||
|
{
|
||||||
|
nextPrefix = prefix.StartsWith("#") ? "#" + new string(' ', prefix.Length - 1) : new string(' ', prefix.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFirstItem = false;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
while(index < list.Count)
|
||||||
|
{
|
||||||
|
var item = list[index];
|
||||||
|
|
||||||
|
if (builder.Length == 0)
|
||||||
|
{
|
||||||
|
lineHasItem = false;
|
||||||
|
builder.Append(prefix);
|
||||||
|
prefix = nextPrefix;
|
||||||
|
isFirstItem = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextLineLength = builder.Length + item.Length + 2;
|
||||||
|
|
||||||
|
if (item != "\n" && (nextLineLength < maxColumn || !lineHasItem))
|
||||||
|
{
|
||||||
|
if (isFirstItem && string.IsNullOrWhiteSpace(item))
|
||||||
|
{
|
||||||
|
// Don't append a space at the beginning of a line
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append(item);
|
||||||
|
lineHasItem = true;
|
||||||
|
isFirstItem = false;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteHighlightLine(builder.ToString());
|
||||||
|
if (item == "\n") index++;
|
||||||
|
builder.Length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder.Length > 0)
|
||||||
|
{
|
||||||
|
WriteHighlightLine(builder.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal void WriteErrorLine(string scriptText)
|
||||||
|
{
|
||||||
|
if (scriptText == null) throw new ArgumentNullException(nameof(scriptText));
|
||||||
|
|
||||||
|
if (_isInitializing)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Unexpected error while initializing: {scriptText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsOutputSupportHighlighting)
|
||||||
|
{
|
||||||
|
HighlightOutput.Append(ConsoleStyle.Red, true);
|
||||||
|
HighlightOutput.Append(scriptText);
|
||||||
|
HighlightOutput.Append(ConsoleStyle.Red, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BufferedErrorWriter.Write(scriptText);
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousEcho = EchoEnabled;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EchoEnabled = true;
|
||||||
|
WriteHighlightLine(true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EchoEnabled = previousEcho;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WriteHighlightLine(string scriptText, bool highlight = true)
|
||||||
|
{
|
||||||
|
WriteHighlight(scriptText, highlight);
|
||||||
|
WriteHighlightLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WriteHighlightLine(bool error = false)
|
||||||
|
{
|
||||||
|
if (!EchoEnabled) return;
|
||||||
|
|
||||||
|
var writer = error ? BufferedErrorWriter : BufferedOutputWriter;
|
||||||
|
|
||||||
|
if (HasInteractiveConsole && _isFirstWriteForEval)
|
||||||
|
{
|
||||||
|
writer.WriteLine();
|
||||||
|
_isFirstWriteForEval = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsOutputSupportHighlighting)
|
||||||
|
{
|
||||||
|
HighlightOutput.AppendLine();
|
||||||
|
HighlightOutput.Render(writer, true);
|
||||||
|
HighlightOutput.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Commit();
|
||||||
|
Repl?.Reset();
|
||||||
|
}
|
||||||
|
internal void WriteHighlight(string scriptText, bool highlight = true)
|
||||||
|
{
|
||||||
|
if (!EchoEnabled) return;
|
||||||
|
|
||||||
|
if (IsOutputSupportHighlighting)
|
||||||
|
{
|
||||||
|
_tempOutputHighlight.Clear();
|
||||||
|
_tempOutputHighlight.ClearStyles();
|
||||||
|
|
||||||
|
_tempOutputHighlight.Append(scriptText);
|
||||||
|
|
||||||
|
if (highlight)
|
||||||
|
{
|
||||||
|
// Highlight line per line
|
||||||
|
Highlight(_tempOutputHighlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
HighlightOutput.AddRange(_tempOutputHighlight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BufferedOutputWriter.Write(scriptText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Highlight(ConsoleText text, int cursorIndex = -1)
|
||||||
|
{
|
||||||
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||||
|
|
||||||
|
var textStr = text.ToString();
|
||||||
|
var lexer = new Lexer(textStr, options: _lexerOptions);
|
||||||
|
var tokens = lexer.ToList();
|
||||||
|
|
||||||
|
UpdateSyntaxHighlighting(text, textStr, tokens);
|
||||||
|
|
||||||
|
if (cursorIndex >= 0)
|
||||||
|
{
|
||||||
|
UpdateMatchingBraces(cursorIndex, text, tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSyntaxHighlighting(ConsoleText text, string textStr, List<Token> tokens)
|
||||||
|
{
|
||||||
|
if (textStr == null) return;
|
||||||
|
|
||||||
|
bool isPreviousNotDot = true;
|
||||||
|
for (var i = 0; i < tokens.Count; i++)
|
||||||
|
{
|
||||||
|
var token = tokens[i];
|
||||||
|
var styleOpt = GetStyle(i, token, textStr, isPreviousNotDot, tokens);
|
||||||
|
isPreviousNotDot = token.Type != TokenType.Dot;
|
||||||
|
|
||||||
|
if (styleOpt.HasValue)
|
||||||
|
{
|
||||||
|
var style = styleOpt.Value;
|
||||||
|
var start = token.Start.Offset;
|
||||||
|
var end = token.End.Offset;
|
||||||
|
if (start >= 0 && end >= 0 && start <= end && end < text.Count)
|
||||||
|
{
|
||||||
|
text.EnableStyleAt(start, style);
|
||||||
|
text.DisableStyleAt(end + 1, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMatchingBraces(int cursorIndex, ConsoleText text, List<Token> tokens)
|
||||||
|
{
|
||||||
|
// Collect all braces open/close
|
||||||
|
List<(int, int)> matchingBraces = null;
|
||||||
|
List<int> pendingUnpairedBraces = null;
|
||||||
|
List<int> unpairedBraces = null;
|
||||||
|
for (var i = 0; i < tokens.Count; i++)
|
||||||
|
{
|
||||||
|
var token = tokens[i];
|
||||||
|
for (var j = 0; j < MatchPairs.Length; j++)
|
||||||
|
{
|
||||||
|
var match = MatchPairs[j];
|
||||||
|
if (match == token.Type)
|
||||||
|
{
|
||||||
|
if (pendingUnpairedBraces == null)
|
||||||
|
{
|
||||||
|
matchingBraces = new List<(int, int)>();
|
||||||
|
pendingUnpairedBraces = new List<int>();
|
||||||
|
unpairedBraces = new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool processed = false;
|
||||||
|
bool isOpening = (j & 1) == 0;
|
||||||
|
if (pendingUnpairedBraces.Count > 0)
|
||||||
|
{
|
||||||
|
var toMatch = isOpening ? MatchPairs[j + 1] : MatchPairs[j - 1];
|
||||||
|
var leftIndex = pendingUnpairedBraces[pendingUnpairedBraces.Count - 1];
|
||||||
|
if (tokens[leftIndex].Type == toMatch)
|
||||||
|
{
|
||||||
|
pendingUnpairedBraces.RemoveAt(pendingUnpairedBraces.Count - 1);
|
||||||
|
matchingBraces.Add((leftIndex, i));
|
||||||
|
processed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!processed)
|
||||||
|
{
|
||||||
|
if (isOpening)
|
||||||
|
{
|
||||||
|
pendingUnpairedBraces.Add(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Closing that are not matching will never match, put them at the front of the braces that will never match
|
||||||
|
unpairedBraces.Add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingBraces == null) return;
|
||||||
|
|
||||||
|
unpairedBraces.AddRange(pendingUnpairedBraces);
|
||||||
|
|
||||||
|
var tokenIndex = FindTokenIndexFromColumnIndex(cursorIndex, text.Count, tokens);
|
||||||
|
var matchingCurrent = matchingBraces.Where(x => x.Item1 == tokenIndex || x.Item2 == tokenIndex).Select(x => ((int, int)?) x).FirstOrDefault();
|
||||||
|
|
||||||
|
// Color current matching braces
|
||||||
|
if (matchingCurrent.HasValue)
|
||||||
|
{
|
||||||
|
var leftIndex = tokens[matchingCurrent.Value.Item1].Start.Column;
|
||||||
|
var rightIndex = tokens[matchingCurrent.Value.Item2].Start.Column;
|
||||||
|
|
||||||
|
text.EnableStyleAt(leftIndex, ConsoleStyle.Underline);
|
||||||
|
text.EnableStyleAt(leftIndex, ConsoleStyle.Bold);
|
||||||
|
text.DisableStyleAt(leftIndex + 1, ConsoleStyle.Bold);
|
||||||
|
text.DisableStyleAt(leftIndex + 1, ConsoleStyle.Underline);
|
||||||
|
|
||||||
|
text.EnableStyleAt(rightIndex, ConsoleStyle.Underline);
|
||||||
|
text.EnableStyleAt(rightIndex, ConsoleStyle.Bold);
|
||||||
|
text.DisableStyleAt(rightIndex + 1, ConsoleStyle.Bold);
|
||||||
|
text.DisableStyleAt(rightIndex + 1, ConsoleStyle.Underline);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color any braces that are not matching
|
||||||
|
foreach (var invalid in unpairedBraces)
|
||||||
|
{
|
||||||
|
var invalidIndex = tokens[invalid].Start.Column;
|
||||||
|
if (invalid == tokenIndex) text.EnableStyleAt(invalidIndex, ConsoleStyle.Underline);
|
||||||
|
text.EnableStyleAt(invalidIndex, ConsoleStyle.BrightRed);
|
||||||
|
text.DisableStyleAt(invalidIndex + 1, ConsoleStyle.BrightRed);
|
||||||
|
if (invalid == tokenIndex) text.DisableStyleAt(invalidIndex + 1, ConsoleStyle.Underline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindTokenIndexFromColumnIndex(int index, int textCount, List<Token> tokens)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < tokens.Count; i++)
|
||||||
|
{
|
||||||
|
var token = tokens[i];
|
||||||
|
var start = token.Start.Column;
|
||||||
|
var end = token.End.Column;
|
||||||
|
if (start >= 0 && end >= 0 && start <= end && end < textCount)
|
||||||
|
{
|
||||||
|
if (index >= start && index <= end)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsoleStyle? GetStyle(int index, Token token, string text, bool isPreviousNotDot, List<Token> tokens)
|
||||||
|
{
|
||||||
|
switch (token.Type)
|
||||||
|
{
|
||||||
|
case TokenType.Integer:
|
||||||
|
case TokenType.HexaInteger:
|
||||||
|
case TokenType.BinaryInteger:
|
||||||
|
case TokenType.Float:
|
||||||
|
return ConsoleStyle.Bold;
|
||||||
|
case TokenType.String:
|
||||||
|
case TokenType.VerbatimString:
|
||||||
|
return ConsoleStyle.BrightCyan;
|
||||||
|
case TokenType.Comment:
|
||||||
|
case TokenType.CommentMulti:
|
||||||
|
return ConsoleStyle.BrightGreen;
|
||||||
|
case TokenType.Identifier:
|
||||||
|
var key = token.GetText(text);
|
||||||
|
|
||||||
|
if (isPreviousNotDot)
|
||||||
|
{
|
||||||
|
if ((index + 1 < tokens.Count && tokens[index + 1].Type == TokenType.Colon))
|
||||||
|
{
|
||||||
|
return ConsoleStyle.BrightWhite;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ScriptKeywords.Contains(key))
|
||||||
|
{
|
||||||
|
// Handle the case where for ... in, the in should be marked as a keyword
|
||||||
|
if (key != "in" || !(index > 1 && tokens[index - 2].GetText(text) == "for"))
|
||||||
|
{
|
||||||
|
return ConsoleStyle.BrightYellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Builtins.ContainsKey(key) || ValueKeywords.Contains(key))
|
||||||
|
{
|
||||||
|
return ConsoleStyle.Cyan;
|
||||||
|
}
|
||||||
|
else if (Units.ContainsKey(key))
|
||||||
|
{
|
||||||
|
return ConsoleStyle.Yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConsoleStyle.White;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
460
Kalk/Kalk.Core/KalkEngine.Repl.cs
Normal file
460
Kalk/Kalk.Core/KalkEngine.Repl.cs
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Consolus;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Functions;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public partial class KalkEngine
|
||||||
|
{
|
||||||
|
private Stopwatch _clockReplInput;
|
||||||
|
|
||||||
|
public ConsoleRepl Repl { get; private set; }
|
||||||
|
|
||||||
|
public int OnErrorToNextLineMaxDelayInMilliseconds { get; set; }
|
||||||
|
|
||||||
|
private void InitializeRepl()
|
||||||
|
{
|
||||||
|
_clockReplInput = Stopwatch.StartNew();
|
||||||
|
OnErrorToNextLineMaxDelayInMilliseconds = 300;
|
||||||
|
|
||||||
|
OnClearScreen = ClearScreen;
|
||||||
|
|
||||||
|
Repl.GetClipboardTextImpl = GetClipboardText;
|
||||||
|
Repl.SetClipboardTextImpl = SetClipboardText;
|
||||||
|
|
||||||
|
Repl.BeforeRender = OnBeforeRendering;
|
||||||
|
Repl.GetCancellationTokenSource = () => _cancellationTokenSource;
|
||||||
|
Repl.TryPreProcessKey = TryPreProcessKey;
|
||||||
|
Repl.OnTextValidatingEnter = OnTextValidatingEnter;
|
||||||
|
|
||||||
|
Repl.Prompt.Clear();
|
||||||
|
Repl.Prompt.Begin(ConsoleStyle.BrightBlack).Append(">>> ").Append(ConsoleStyle.BrightBlack, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnTextValidatingEnter(string text, bool hasControl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return OnTextValidatingEnterInternal(text, hasControl);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_clockReplInput.Restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnTextValidatingEnterInternal(string text, bool hasControl)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
CancellationToken = _cancellationTokenSource.Token;
|
||||||
|
|
||||||
|
var elapsed = _clockReplInput.ElapsedMilliseconds;
|
||||||
|
Template script = null;
|
||||||
|
|
||||||
|
object result = null;
|
||||||
|
string error = null;
|
||||||
|
int column = -1;
|
||||||
|
bool isCancelled = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script = Parse(text);
|
||||||
|
|
||||||
|
if (script.HasErrors)
|
||||||
|
{
|
||||||
|
var errorBuilder = new StringBuilder();
|
||||||
|
foreach (var message in script.Messages)
|
||||||
|
{
|
||||||
|
if (errorBuilder.Length > 0) errorBuilder.AppendLine();
|
||||||
|
if (column <= 0 && message.Type == ParserMessageType.Error)
|
||||||
|
{
|
||||||
|
column = message.Span.Start.Column;
|
||||||
|
}
|
||||||
|
errorBuilder.Append(message.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = errorBuilder.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.Clear();
|
||||||
|
HighlightOutput.Clear();
|
||||||
|
|
||||||
|
result = EvaluatePage(script.Page);
|
||||||
|
|
||||||
|
if (Repl.ExitOnNextEval)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is ScriptRuntimeException scriptEx)
|
||||||
|
{
|
||||||
|
column = scriptEx.Span.Start.Column;
|
||||||
|
error = scriptEx.OriginalMessage;
|
||||||
|
isCancelled = ex is ScriptAbortException;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.Clear();
|
||||||
|
Repl.AfterEditLine.Append('\n');
|
||||||
|
Repl.AfterEditLine.Begin(ConsoleStyle.Red);
|
||||||
|
Repl.AfterEditLine.Append(error);
|
||||||
|
if (column >= 0 && column <= Repl.EditLine.Count)
|
||||||
|
{
|
||||||
|
Repl.EnableCursorChanged = false;
|
||||||
|
Repl.CursorIndex = column;
|
||||||
|
Repl.EnableCursorChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool emitReturnOnError = hasControl || elapsed < OnErrorToNextLineMaxDelayInMilliseconds || isCancelled;
|
||||||
|
if (emitReturnOnError)
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.Append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return emitReturnOnError;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
Write(script.Page.Span, result);
|
||||||
|
}
|
||||||
|
var resultStr = Output.ToString();
|
||||||
|
var output = Output as StringBuilderOutput;
|
||||||
|
if (output != null)
|
||||||
|
{
|
||||||
|
output.Builder.Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Repl.AfterEditLine.Clear();
|
||||||
|
bool hasOutput = resultStr != string.Empty || HighlightOutput.Count > 0;
|
||||||
|
if (!Repl.IsClean || hasOutput)
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.Append('\n');
|
||||||
|
|
||||||
|
bool hasNextOutput = HighlightOutput.Count > 0;
|
||||||
|
if (hasNextOutput)
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.AddRange(HighlightOutput);
|
||||||
|
HighlightOutput.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultStr != string.Empty)
|
||||||
|
{
|
||||||
|
if (hasNextOutput)
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.AppendLine();
|
||||||
|
}
|
||||||
|
Repl.AfterEditLine.Begin(ConsoleStyle.Bold);
|
||||||
|
Repl.AfterEditLine.Append(resultStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOutput)
|
||||||
|
{
|
||||||
|
Repl.AfterEditLine.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnEnterNextText(string textToEnter)
|
||||||
|
{
|
||||||
|
Repl?.EnqueuePendingTextToEnter(textToEnter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryPreProcessKey(ConsoleKeyInfo arg, ref int cursorIndex)
|
||||||
|
{
|
||||||
|
return OnKey(arg, Repl.EditLine, ref cursorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBeforeRendering()
|
||||||
|
{
|
||||||
|
UpdateSyntaxHighlighting();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSyntaxHighlighting()
|
||||||
|
{
|
||||||
|
if (Repl != null)
|
||||||
|
{
|
||||||
|
Repl.EditLine.ClearStyles();
|
||||||
|
Highlight(Repl.EditLine, Repl.CursorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearScreen()
|
||||||
|
{
|
||||||
|
Repl?.Clear();
|
||||||
|
HighlightOutput.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplExit()
|
||||||
|
{
|
||||||
|
if (Repl == null) return;
|
||||||
|
Repl.ExitOnNextEval = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool OnKey(ConsoleKeyInfo arg, ConsoleText line, ref int cursorIndex)
|
||||||
|
{
|
||||||
|
bool resetMap = true;
|
||||||
|
bool resetCompletion = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool hasShift = (arg.Modifiers & ConsoleModifiers.Shift) != 0;
|
||||||
|
|
||||||
|
// For now, we discard SHIFT entirely, as it is handled separately for range selection
|
||||||
|
// TODO: we should handle them not differently from ctrl/alt, but it's complicated
|
||||||
|
arg = new ConsoleKeyInfo(arg.KeyChar, arg.Key, false, (arg.Modifiers & ConsoleModifiers.Alt) != 0, (arg.Modifiers & ConsoleModifiers.Control) != 0);
|
||||||
|
|
||||||
|
KalkConsoleKey kalkKey = arg;
|
||||||
|
if (cursorIndex >= 0 && cursorIndex <= line.Count)
|
||||||
|
{
|
||||||
|
if (_currentShortcutKeyMap.TryGetValue(kalkKey, out var value))
|
||||||
|
{
|
||||||
|
if (value is KalkShortcutKeyMap map)
|
||||||
|
{
|
||||||
|
_currentShortcutKeyMap = map;
|
||||||
|
resetMap = false; // we don't reset if
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var expression = (ScriptExpression) value;
|
||||||
|
var result = EvaluateExpression(expression);
|
||||||
|
if (result is KalkActionObject command)
|
||||||
|
{
|
||||||
|
// Particular case the completion action, we handle it here
|
||||||
|
if (command.Action == "completion")
|
||||||
|
{
|
||||||
|
// In case of shift we go backward
|
||||||
|
if (OnCompletionRequested(hasShift, line, ref cursorIndex))
|
||||||
|
{
|
||||||
|
resetCompletion = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (OnAction != null)
|
||||||
|
{
|
||||||
|
command.Call(OnAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (result != null)
|
||||||
|
{
|
||||||
|
var resultStr = ObjectToString(result);
|
||||||
|
line.Insert(cursorIndex, resultStr);
|
||||||
|
cursorIndex += resultStr.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (resetMap)
|
||||||
|
{
|
||||||
|
// Restore the root key map in case of an error.
|
||||||
|
_currentShortcutKeyMap = Shortcuts.ShortcutKeyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetCompletion)
|
||||||
|
{
|
||||||
|
ResetCompletion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnCompletionRequested(bool backward, ConsoleText line, ref int cursorIndex)
|
||||||
|
{
|
||||||
|
// Nothing to complete
|
||||||
|
if (cursorIndex == 0) return false;
|
||||||
|
|
||||||
|
// We expect to have at least:
|
||||||
|
// - one letter identifier before the before the cursor
|
||||||
|
// - no letter identifier on the cursor (e.g middle of an existing identifier)
|
||||||
|
if (cursorIndex < line.Count && IsIdentifierLetter(line[cursorIndex].Value) || !IsIdentifierLetter(line[cursorIndex - 1].Value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _currentIndexInCompletionMatchingList
|
||||||
|
if (_currentIndexInCompletionMatchingList < 0)
|
||||||
|
{
|
||||||
|
if (!CollectCompletionList(line, cursorIndex))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to next word
|
||||||
|
_currentIndexInCompletionMatchingList = (_currentIndexInCompletionMatchingList + (backward ? -1 : 1));
|
||||||
|
|
||||||
|
// Wrap the result
|
||||||
|
if (_currentIndexInCompletionMatchingList >= _completionMatchingList.Count) _currentIndexInCompletionMatchingList = 0;
|
||||||
|
if (_currentIndexInCompletionMatchingList < 0) _currentIndexInCompletionMatchingList = _completionMatchingList.Count - 1;
|
||||||
|
|
||||||
|
if (_currentIndexInCompletionMatchingList < 0 || _currentIndexInCompletionMatchingList >= _completionMatchingList.Count) return false;
|
||||||
|
|
||||||
|
var index = _startIndexForCompletion;
|
||||||
|
var newText = _completionMatchingList[_currentIndexInCompletionMatchingList];
|
||||||
|
|
||||||
|
line.RemoveRangeAt(index, cursorIndex - index);
|
||||||
|
line.Insert(index, newText);
|
||||||
|
cursorIndex = index + newText.Length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetCompletion()
|
||||||
|
{
|
||||||
|
_currentIndexInCompletionMatchingList = -1;
|
||||||
|
_completionMatchingList.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CollectCompletionList(ConsoleText line, int cursorIndex)
|
||||||
|
{
|
||||||
|
var text = line.ToString();
|
||||||
|
var lexer = new Lexer(text, options: _lexerOptions);
|
||||||
|
var tokens = lexer.ToList();
|
||||||
|
|
||||||
|
// Find that we are in a correct place
|
||||||
|
var index = FindTokenIndexFromColumnIndex(cursorIndex, text.Length, tokens);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
// If we are in the middle of a comment/integer/float/string
|
||||||
|
// we don't expect to make any completion
|
||||||
|
var token = tokens[index];
|
||||||
|
switch (token.Type)
|
||||||
|
{
|
||||||
|
case TokenType.Comment:
|
||||||
|
case TokenType.CommentMulti:
|
||||||
|
case TokenType.Identifier:
|
||||||
|
case TokenType.IdentifierSpecial:
|
||||||
|
case TokenType.Integer:
|
||||||
|
case TokenType.HexaInteger:
|
||||||
|
case TokenType.BinaryInteger:
|
||||||
|
case TokenType.Float:
|
||||||
|
case TokenType.String:
|
||||||
|
case TokenType.ImplicitString:
|
||||||
|
case TokenType.VerbatimString:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the start of the work to complete
|
||||||
|
_startIndexForCompletion = cursorIndex - 1;
|
||||||
|
while (_startIndexForCompletion >= 0)
|
||||||
|
{
|
||||||
|
var c = text[_startIndexForCompletion];
|
||||||
|
if (!IsIdentifierLetter(c))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startIndexForCompletion--;
|
||||||
|
}
|
||||||
|
_startIndexForCompletion++;
|
||||||
|
|
||||||
|
if (!IsFirstIdentifierLetter(text[_startIndexForCompletion]))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTextToFind = text.Substring(_startIndexForCompletion, cursorIndex - _startIndexForCompletion);
|
||||||
|
|
||||||
|
Collect(startTextToFind, ScriptKeywords, _completionMatchingList);
|
||||||
|
Collect(startTextToFind, ValueKeywords, _completionMatchingList);
|
||||||
|
Collect(startTextToFind, Variables.Keys, _completionMatchingList);
|
||||||
|
Collect(startTextToFind, Builtins.Keys, _completionMatchingList);
|
||||||
|
|
||||||
|
// If we are not able to match anything from builtin and user variables/functions
|
||||||
|
// continue on units
|
||||||
|
if (_completionMatchingList.Count == 0)
|
||||||
|
{
|
||||||
|
Collect(startTextToFind, Units.Keys, _completionMatchingList);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void Collect(string startText, IEnumerable<string> keys, List<string> matchingList)
|
||||||
|
{
|
||||||
|
foreach (var key in keys.OrderBy(x => x))
|
||||||
|
{
|
||||||
|
if (key.StartsWith(startText))
|
||||||
|
{
|
||||||
|
matchingList.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static bool IsIdentifierCharacter(char c)
|
||||||
|
{
|
||||||
|
var newCategory = GetCharCategory(c);
|
||||||
|
|
||||||
|
switch (newCategory)
|
||||||
|
{
|
||||||
|
case UnicodeCategory.UppercaseLetter:
|
||||||
|
case UnicodeCategory.LowercaseLetter:
|
||||||
|
case UnicodeCategory.TitlecaseLetter:
|
||||||
|
case UnicodeCategory.ModifierLetter:
|
||||||
|
case UnicodeCategory.OtherLetter:
|
||||||
|
case UnicodeCategory.NonSpacingMark:
|
||||||
|
case UnicodeCategory.DecimalDigitNumber:
|
||||||
|
case UnicodeCategory.ModifierSymbol:
|
||||||
|
case UnicodeCategory.ConnectorPunctuation:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsFirstIdentifierLetter(char c)
|
||||||
|
{
|
||||||
|
return c == '_' || char.IsLetter(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsIdentifierLetter(char c)
|
||||||
|
{
|
||||||
|
return IsFirstIdentifierLetter(c) || char.IsDigit(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnicodeCategory GetCharCategory(char c)
|
||||||
|
{
|
||||||
|
if (c == '_' || (c >= '0' && c <= '9')) return UnicodeCategory.LowercaseLetter;
|
||||||
|
c = char.ToLowerInvariant(c);
|
||||||
|
return char.GetUnicodeCategory(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
198
Kalk/Kalk.Core/KalkEngine.Run.cs
Normal file
198
Kalk/Kalk.Core/KalkEngine.Run.cs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Consolus;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public partial class KalkEngine
|
||||||
|
{
|
||||||
|
public bool Run(params string[] args)
|
||||||
|
{
|
||||||
|
if (!Console.IsInputRedirected && !Console.IsOutputRedirected && ConsoleHelper.HasInteractiveConsole)
|
||||||
|
{
|
||||||
|
Repl = new ConsoleRepl();
|
||||||
|
HasInteractiveConsole = true;
|
||||||
|
|
||||||
|
InitializeRepl();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ConsoleRepl.IsSelf())
|
||||||
|
{
|
||||||
|
Console.Title = $"kalk {Version}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory.CreateDirectory(KalkUserFolder);
|
||||||
|
|
||||||
|
if (DisplayVersion)
|
||||||
|
{
|
||||||
|
ShowVersion();
|
||||||
|
WriteHighlightLine("# Type `help` for more information and at https://github.com/xoofx/kalk");
|
||||||
|
WriteHighlightLine("# Type `exit` or CTRL+Z to exit from kalk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load user config file after showing the version
|
||||||
|
LoadUserConfigFile();
|
||||||
|
|
||||||
|
if (Repl != null)
|
||||||
|
{
|
||||||
|
return RunInteractive();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return RunNonInteractive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RunInteractive()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnAction = InteractiveOnAction;
|
||||||
|
_clockReplInput.Restart();
|
||||||
|
Repl.Run();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Unexpected exception {ex}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InteractiveOnAction(KalkAction obj)
|
||||||
|
{
|
||||||
|
switch (obj)
|
||||||
|
{
|
||||||
|
case KalkAction.Exit:
|
||||||
|
Repl.Action(ConsoleAction.Exit);
|
||||||
|
break;
|
||||||
|
case KalkAction.CursorLeft:
|
||||||
|
Repl.Action(ConsoleAction.CursorLeft);
|
||||||
|
break;
|
||||||
|
case KalkAction.CursorRight:
|
||||||
|
Repl.Action(ConsoleAction.CursorRight);
|
||||||
|
break;
|
||||||
|
case KalkAction.CursorLeftWord:
|
||||||
|
Repl.Action(ConsoleAction.CursorLeftWord);
|
||||||
|
break;
|
||||||
|
case KalkAction.CursorRightWord:
|
||||||
|
Repl.Action(ConsoleAction.CursorRightWord);
|
||||||
|
break;
|
||||||
|
case KalkAction.CursorStartOfLine:
|
||||||
|
Repl.Action(ConsoleAction.CursorStartOfLine);
|
||||||
|
break;
|
||||||
|
case KalkAction.CursorEndOfLine:
|
||||||
|
Repl.Action(ConsoleAction.CursorEndOfLine);
|
||||||
|
break;
|
||||||
|
case KalkAction.HistoryPrevious:
|
||||||
|
Repl.Action(ConsoleAction.HistoryPrevious);
|
||||||
|
break;
|
||||||
|
case KalkAction.HistoryNext:
|
||||||
|
Repl.Action(ConsoleAction.HistoryNext);
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteCharacterLeft:
|
||||||
|
Repl.Action(ConsoleAction.DeleteCharacterLeft);
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteCharacterLeftAndCopy:
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteCharacterRight:
|
||||||
|
Repl.Action(ConsoleAction.DeleteCharacterRight);
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteCharacterRightAndCopy:
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteWordLeft:
|
||||||
|
Repl.Action(ConsoleAction.DeleteWordLeft);
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteWordRight:
|
||||||
|
Repl.Action(ConsoleAction.DeleteWordRight);
|
||||||
|
break;
|
||||||
|
case KalkAction.Completion:
|
||||||
|
Repl.Action(ConsoleAction.Completion);
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteTextRightAndCopy:
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteWordRightAndCopy:
|
||||||
|
break;
|
||||||
|
case KalkAction.DeleteWordLeftAndCopy:
|
||||||
|
break;
|
||||||
|
case KalkAction.CopySelection:
|
||||||
|
Repl.Action(ConsoleAction.CopySelection);
|
||||||
|
break;
|
||||||
|
case KalkAction.CutSelection:
|
||||||
|
Repl.Action(ConsoleAction.CutSelection);
|
||||||
|
break;
|
||||||
|
case KalkAction.PasteClipboard:
|
||||||
|
Repl.Action(ConsoleAction.PasteClipboard);
|
||||||
|
break;
|
||||||
|
case KalkAction.ValidateLine:
|
||||||
|
Repl.Action(ConsoleAction.ValidateLine);
|
||||||
|
break;
|
||||||
|
case KalkAction.ForceValidateLine:
|
||||||
|
Repl.Action(ConsoleAction.ForceValidateLine);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(obj), obj, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RunNonInteractive()
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
string line;
|
||||||
|
while ((line = InputReader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
if (EchoEnabled && EchoInput) OutputWriter.Write($">>> {line}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var script = Parse(line);
|
||||||
|
|
||||||
|
if (script.HasErrors)
|
||||||
|
{
|
||||||
|
//throw new ScriptParserRuntimeException();
|
||||||
|
var errorBuilder = new StringBuilder();
|
||||||
|
foreach (var message in script.Messages)
|
||||||
|
{
|
||||||
|
if (errorBuilder.Length > 0) errorBuilder.AppendLine();
|
||||||
|
errorBuilder.Append(message.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = errorBuilder.ToString();
|
||||||
|
throw new InvalidOperationException(error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = EvaluatePage(script.Page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is ScriptRuntimeException runtimeEx)
|
||||||
|
{
|
||||||
|
WriteErrorLine(runtimeEx.OriginalMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteErrorLine(ex.Message);
|
||||||
|
}
|
||||||
|
//Console.WriteLine(ex.InnerException);
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasExit) break;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
502
Kalk/Kalk.Core/KalkEngine.cs
Normal file
502
Kalk/Kalk.Core/KalkEngine.cs
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using Consolus;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Functions;
|
||||||
|
using Scriban.Helpers;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public partial class KalkEngine : TemplateContext
|
||||||
|
{
|
||||||
|
private readonly LexerOptions _lexerOptions;
|
||||||
|
private readonly LexerOptions _lexerInterpolatedOptions;
|
||||||
|
private readonly ParserOptions _parserOptions;
|
||||||
|
private object _lastResult = null;
|
||||||
|
private readonly ConsoleText _tempOutputHighlight;
|
||||||
|
private bool _hasResultOrVariableSet = false;
|
||||||
|
private Action _showInputAction = null;
|
||||||
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private readonly bool _isInitializing;
|
||||||
|
private int _startIndexForCompletion;
|
||||||
|
private readonly List<string> _completionMatchingList;
|
||||||
|
private int _currentIndexInCompletionMatchingList;
|
||||||
|
private bool _isFirstWriteForEval;
|
||||||
|
private readonly Dictionary<Type, KalkModule> _modules;
|
||||||
|
private string _localClipboard;
|
||||||
|
private bool _formatting;
|
||||||
|
private readonly CultureInfo _integersCultureInfoWithUnderscore;
|
||||||
|
|
||||||
|
private KalkShortcutKeyMap _currentShortcutKeyMap;
|
||||||
|
|
||||||
|
public KalkEngine() : base(new KalkObjectWithAlias())
|
||||||
|
{
|
||||||
|
_integersCultureInfoWithUnderscore = new CultureInfo(CultureInfo.InvariantCulture.LCID)
|
||||||
|
{
|
||||||
|
NumberFormat =
|
||||||
|
{
|
||||||
|
NumberGroupSeparator = "_",
|
||||||
|
NumberDecimalDigits = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FileService = new DefaultFileService();
|
||||||
|
KalkSettings.Initialize();
|
||||||
|
KalkEngineFolder = AppContext.BaseDirectory;
|
||||||
|
|
||||||
|
// Enforce UTF8 encoding
|
||||||
|
// Console.OutputEncoding = Encoding.UTF8;
|
||||||
|
EnableEngineOutput = true;
|
||||||
|
EchoEnabled = true;
|
||||||
|
DisplayVersion = true;
|
||||||
|
CurrentDisplay = KalkDisplayMode.Standard;
|
||||||
|
KalkUserFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify), ".kalk");
|
||||||
|
|
||||||
|
HighlightOutput = new ConsoleText();
|
||||||
|
InputReader = Console.In;
|
||||||
|
OutputWriter = Console.Out;
|
||||||
|
ErrorWriter = Console.Error;
|
||||||
|
IsOutputSupportHighlighting = ConsoleHelper.SupportEscapeSequences;
|
||||||
|
|
||||||
|
// Fetch version
|
||||||
|
var assemblyInfoVersion = (AssemblyInformationalVersionAttribute)typeof(KalkEngine).Assembly.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute));
|
||||||
|
Version = assemblyInfoVersion.InformationalVersion;
|
||||||
|
|
||||||
|
Builtins = BuiltinObject;
|
||||||
|
((KalkObjectWithAlias)Builtins).Engine = this;
|
||||||
|
|
||||||
|
Units = new KalkUnits(this);
|
||||||
|
Shortcuts = new KalkShortcuts();
|
||||||
|
Aliases = new KalkAliases();
|
||||||
|
_currentShortcutKeyMap = Shortcuts.ShortcutKeyMap;
|
||||||
|
_completionMatchingList = new List<string>();
|
||||||
|
Config = new KalkConfig();
|
||||||
|
Variables = new ScriptVariables(this);
|
||||||
|
Descriptors = new Dictionary<string, KalkDescriptor>();
|
||||||
|
EnableRelaxedMemberAccess = false;
|
||||||
|
_modules = new Dictionary<Type, KalkModule>();
|
||||||
|
TryConverters = new List<TryToObjectDelegate>();
|
||||||
|
ErrorForStatementFunctionAsExpression = true;
|
||||||
|
StrictVariables = true;
|
||||||
|
UseScientific = true;
|
||||||
|
LoopLimit = int.MaxValue; // no limits for loops
|
||||||
|
RecursiveLimit = int.MaxValue; // no limits (still guarded by Scriban)
|
||||||
|
|
||||||
|
// Setup default clipboard methods
|
||||||
|
_localClipboard = string.Empty;
|
||||||
|
GetClipboardText = GetClipboardTextImpl;
|
||||||
|
SetClipboardText = SetClipboardTextImpl;
|
||||||
|
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
PushGlobal(Units);
|
||||||
|
PushGlobal(Variables);
|
||||||
|
|
||||||
|
_parserOptions = new ParserOptions();
|
||||||
|
|
||||||
|
_lexerOptions = new LexerOptions()
|
||||||
|
{
|
||||||
|
KeepTrivia = true,
|
||||||
|
Mode = ScriptMode.ScriptOnly,
|
||||||
|
Lang = ScriptLang.Scientific
|
||||||
|
};
|
||||||
|
_lexerInterpolatedOptions = new LexerOptions()
|
||||||
|
{
|
||||||
|
KeepTrivia = true,
|
||||||
|
Mode = ScriptMode.Default,
|
||||||
|
Lang = ScriptLang.Scientific
|
||||||
|
};
|
||||||
|
_tempOutputHighlight = new ConsoleText();
|
||||||
|
|
||||||
|
// Init last result with 0
|
||||||
|
_lastResult = 0;
|
||||||
|
|
||||||
|
HistoryList = new List<string>();
|
||||||
|
|
||||||
|
_isInitializing = true;
|
||||||
|
RegisterFunctions();
|
||||||
|
_isInitializing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetClipboardTextImpl(string obj)
|
||||||
|
{
|
||||||
|
_localClipboard = obj ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetClipboardTextImpl()
|
||||||
|
{
|
||||||
|
return _localClipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsoleText HighlightOutput { get; }
|
||||||
|
|
||||||
|
public IFileService FileService { get; set; }
|
||||||
|
|
||||||
|
public bool DisplayVersion { get; set; }
|
||||||
|
|
||||||
|
public string Version { get; }
|
||||||
|
|
||||||
|
public string KalkUserFolder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The engine is in testing mode.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTesting { get; set; }
|
||||||
|
|
||||||
|
public string KalkEngineFolder { get; }
|
||||||
|
|
||||||
|
public Func<string, Stream> GetAppContentStream;
|
||||||
|
|
||||||
|
internal ConsoleTextWriter BufferedOutputWriter { get; private set; }
|
||||||
|
|
||||||
|
internal ConsoleTextWriter BufferedErrorWriter { get; private set; }
|
||||||
|
|
||||||
|
public TextReader InputReader { get; set; }
|
||||||
|
|
||||||
|
public TextWriter OutputWriter
|
||||||
|
{
|
||||||
|
get => BufferedOutputWriter?.Backend;
|
||||||
|
set => BufferedOutputWriter = value == null ? null : new ConsoleTextWriter(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter ErrorWriter
|
||||||
|
{
|
||||||
|
get => BufferedErrorWriter?.Backend;
|
||||||
|
set => BufferedErrorWriter = value == null ? null : new ConsoleTextWriter(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EchoEnabled { get; set; }
|
||||||
|
|
||||||
|
public bool EchoInput { get; set; }
|
||||||
|
|
||||||
|
public ScriptObject Builtins { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the config object.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// ```kalk
|
||||||
|
/// >>> config
|
||||||
|
/// # config
|
||||||
|
/// out = {help_max_column: 100, limit_to_string: "auto"}
|
||||||
|
/// ```
|
||||||
|
/// </example>
|
||||||
|
[KalkExport("config", CategoryGeneral)]
|
||||||
|
public KalkConfig Config { get; }
|
||||||
|
|
||||||
|
public Action<string> SetClipboardText { get; set; }
|
||||||
|
|
||||||
|
public Func<string> GetClipboardText { get; set; }
|
||||||
|
|
||||||
|
public ScriptObject Variables { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays all built-in and user-defined aliases.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Aliases are usually used to define equivalent variable names for equivalent mathematical symbols. To create an alias, see the command `alias`.</remarks>
|
||||||
|
/// <example>
|
||||||
|
/// ```kalk
|
||||||
|
/// >>> aliases
|
||||||
|
/// # Builtin Aliases
|
||||||
|
/// alias(alpha, Α, α)
|
||||||
|
/// alias(beta, Β, β)
|
||||||
|
/// alias(chi, Χ, χ)
|
||||||
|
/// alias(delta, Δ, δ)
|
||||||
|
/// alias(epsilon, Ε, ε)
|
||||||
|
/// alias(eta, Η, η)
|
||||||
|
/// alias(gamma, Γ, γ)
|
||||||
|
/// alias(iota, Ι, ι)
|
||||||
|
/// alias(kappa, Κ, κ)
|
||||||
|
/// alias(lambda, Λ, λ)
|
||||||
|
/// alias(mu, Μ, μ)
|
||||||
|
/// alias(nu, Ν, ν)
|
||||||
|
/// alias(omega, Ω, ω)
|
||||||
|
/// alias(omicron, Ο, ο)
|
||||||
|
/// alias(phi, Φ, φ, ϕ)
|
||||||
|
/// alias(pi, Π, π)
|
||||||
|
/// alias(psi, Ψ, ψ)
|
||||||
|
/// alias(rho, Ρ, ρ)
|
||||||
|
/// alias(sigma, Σ, σ)
|
||||||
|
/// alias(tau, Τ, τ)
|
||||||
|
/// alias(theta, Θ, θ, ϑ)
|
||||||
|
/// alias(upsilon, Υ, υ)
|
||||||
|
/// alias(xi, Ξ, ξ)
|
||||||
|
/// alias(zeta, Ζ, ζ)
|
||||||
|
/// ```
|
||||||
|
/// </example>
|
||||||
|
[KalkExport("aliases", CategoryGeneral)]
|
||||||
|
public KalkAliases Aliases { get; }
|
||||||
|
|
||||||
|
private bool EnableEngineOutput { get; set; }
|
||||||
|
|
||||||
|
public bool IsOutputSupportHighlighting { get; set; }
|
||||||
|
|
||||||
|
public bool HasInteractiveConsole { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays all built-in and user-defined keyboard shortcuts.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>To create an keyboard shortcut, see the command `shortcut`.</remarks>
|
||||||
|
/// <example>
|
||||||
|
/// ```kalk
|
||||||
|
/// >>> clear shortcuts
|
||||||
|
/// >>> shortcut(tester, "ctrl+d", '"' + date + '"')
|
||||||
|
/// >>> shortcuts
|
||||||
|
/// # User-defined Shortcuts
|
||||||
|
/// shortcut(tester, "ctrl+d", '"' + date + '"') # ctrl+d => '"' + date + '"'
|
||||||
|
/// ```
|
||||||
|
/// </example>
|
||||||
|
[KalkExport("shortcuts", CategoryGeneral)]
|
||||||
|
public KalkShortcuts Shortcuts { get; }
|
||||||
|
|
||||||
|
public Dictionary<string, KalkDescriptor> Descriptors { get; }
|
||||||
|
|
||||||
|
public Action OnClearScreen { get; set; }
|
||||||
|
|
||||||
|
public bool HasExit { get; private set; }
|
||||||
|
|
||||||
|
public List<string> HistoryList { get; }
|
||||||
|
|
||||||
|
public List<TryToObjectDelegate> TryConverters { get; }
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly Regex MatchHistoryRegex = new Regex(@"^\s*!(\d+)\s*");
|
||||||
|
|
||||||
|
public Template Parse(string text, string filePath = null, bool recordHistory = true, bool interpolated = false)
|
||||||
|
{
|
||||||
|
if (!TryParseSpecialHistoryBangCommand(text, out var template))
|
||||||
|
{
|
||||||
|
var lexerOptions = interpolated ? _lexerInterpolatedOptions : _lexerOptions;
|
||||||
|
if (filePath != null)
|
||||||
|
{
|
||||||
|
// Don't keep trivia when loading from a file as we are not going to format anything
|
||||||
|
// and it will make the parse errors correct (TODO: fix it when also parsing)
|
||||||
|
lexerOptions.KeepTrivia = false;
|
||||||
|
}
|
||||||
|
template = Template.Parse(text, filePath, parserOptions: _parserOptions, lexerOptions: lexerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recordHistory && text.Length != 0 && !template.HasErrors)
|
||||||
|
{
|
||||||
|
if (HistoryList.Count == 0 || HistoryList[^1] != text)
|
||||||
|
{
|
||||||
|
HistoryList.Add(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object EvaluatePage(ScriptPage script)
|
||||||
|
{
|
||||||
|
_isFirstWriteForEval = true;
|
||||||
|
RecordInput(script);
|
||||||
|
return Evaluate(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordInput(ScriptPage toRewrite)
|
||||||
|
{
|
||||||
|
_hasResultOrVariableSet = false;
|
||||||
|
_showInputAction = () =>
|
||||||
|
{
|
||||||
|
var newScript = toRewrite.Format(new ScriptFormatterOptions(this, ScriptLang.Scientific, ScriptFormatterFlags.ExplicitClean));
|
||||||
|
var output = newScript.ToString();
|
||||||
|
WriteHighlightLine($"# {output}");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void RecordSetVariable(string name, object value)
|
||||||
|
{
|
||||||
|
NotifyResultOrVariable();
|
||||||
|
WriteHighlightVariableAndValueToConsole(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLastResult(object textAsObject)
|
||||||
|
{
|
||||||
|
_lastResult = textAsObject;
|
||||||
|
NotifyResultOrVariable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyResultOrVariable()
|
||||||
|
{
|
||||||
|
if (_hasResultOrVariableSet) return;
|
||||||
|
_hasResultOrVariableSet = true;
|
||||||
|
|
||||||
|
_showInputAction?.Invoke();
|
||||||
|
_showInputAction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AcceptUnit(ScriptExpression leftExpression, string unitName)
|
||||||
|
{
|
||||||
|
return !(leftExpression is ScriptVariable variable) || variable.Name != "help";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override TemplateContext Write(SourceSpan span, object textAsObject)
|
||||||
|
{
|
||||||
|
// If we are in formatting mode, we use the normal output
|
||||||
|
if (_formatting) return base.Write(span, textAsObject);
|
||||||
|
|
||||||
|
SetLastResult(textAsObject);
|
||||||
|
if (EnableEngineOutput && EchoEnabled)
|
||||||
|
{
|
||||||
|
WriteHighlightVariableAndValueToConsole("out", textAsObject);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ObjectToString(object value, bool nested = false)
|
||||||
|
{
|
||||||
|
if (value is float f32)
|
||||||
|
{
|
||||||
|
if (float.IsNegativeInfinity(f32)) return "-inf";
|
||||||
|
if (float.IsInfinity(f32)) return "inf";
|
||||||
|
if (float.IsNaN(f32)) return "nan";
|
||||||
|
}
|
||||||
|
else if (value is double f64)
|
||||||
|
{
|
||||||
|
if (double.IsNegativeInfinity(f64)) return "-inf";
|
||||||
|
if (double.IsInfinity(f64)) return "inf";
|
||||||
|
if (double.IsNaN(f64)) return "nan";
|
||||||
|
}
|
||||||
|
else if (value is DateTime && _miscModule != null)
|
||||||
|
{
|
||||||
|
// Output DateTime only if we have the date builtin object accessible (that provides the implementation of the ToString method)
|
||||||
|
return _miscModule.DateObject.ToString((DateTime)value, _miscModule.DateObject.Format, CurrentCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentDisplay != KalkDisplayMode.Raw && (value is int | value is uint || value is ulong || value is long || value is BigInteger || value is short || value is ushort))
|
||||||
|
{
|
||||||
|
return ((IFormattable)value).ToString("N", _integersCultureInfoWithUnderscore);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.ObjectToString(value, nested);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IObjectAccessor GetMemberAccessorImpl(object target)
|
||||||
|
{
|
||||||
|
if (target != null && target.GetType().IsPrimitiveOrDecimal())
|
||||||
|
{
|
||||||
|
return PrimitiveSwizzleAccessor.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetMemberAccessorImpl(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScriptVariables : KalkObjectWithAlias
|
||||||
|
{
|
||||||
|
public ScriptVariables(KalkEngine engine) : base(engine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly)
|
||||||
|
{
|
||||||
|
if (base.TrySetValue(context, span, member, value, readOnly))
|
||||||
|
{
|
||||||
|
if (Engine.EnableEngineOutput)
|
||||||
|
{
|
||||||
|
Engine.RecordSetVariable(member, value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EmptyObject
|
||||||
|
{
|
||||||
|
public static readonly EmptyObject Instance = new EmptyObject();
|
||||||
|
public override string ToString() => string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Import(SourceSpan span, object objectToImport)
|
||||||
|
{
|
||||||
|
if (objectToImport is KalkModule module)
|
||||||
|
{
|
||||||
|
ImportModule(module);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Import(span, objectToImport);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ImportModule(KalkModule module)
|
||||||
|
{
|
||||||
|
module.InternalImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T ToObject<T>(int argIndex, object value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ToObject<T>(CurrentSpan, value);
|
||||||
|
}
|
||||||
|
catch (ScriptRuntimeException ex)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(argIndex, ex.OriginalMessage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(argIndex, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ToObject(int argIndex, object value, Type destinationType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ToObject(CurrentSpan, value, destinationType);
|
||||||
|
}
|
||||||
|
catch (ScriptRuntimeException ex)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(argIndex, ex.OriginalMessage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(argIndex, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ToObject(SourceSpan span, object value, Type destinationType)
|
||||||
|
{
|
||||||
|
var converters = TryConverters;
|
||||||
|
if (converters.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var converter in converters)
|
||||||
|
{
|
||||||
|
if (converter(span, value, destinationType, out var newValue))
|
||||||
|
{
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destinationType == typeof(KalkHalf))
|
||||||
|
{
|
||||||
|
return KalkHalf.FromObject(this, span, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.ToObject(span, value, destinationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ToBool(SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
if (value is KalkBool kb) { return kb; }
|
||||||
|
return base.ToBool(span, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate bool TryToObjectDelegate(SourceSpan span, object value, Type destinationType, out object newValue);
|
||||||
|
}
|
||||||
35862
Kalk/Kalk.Core/KalkEngine.generated.cs
Normal file
35862
Kalk/Kalk.Core/KalkEngine.generated.cs
Normal file
File diff suppressed because it is too large
Load Diff
31
Kalk/Kalk.Core/KalkExportAttribute.cs
Normal file
31
Kalk/Kalk.Core/KalkExportAttribute.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public class KalkExportAttribute : System.Attribute
|
||||||
|
{
|
||||||
|
public KalkExportAttribute(string alias, string category)
|
||||||
|
{
|
||||||
|
Alias = alias;
|
||||||
|
Category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Alias { get; }
|
||||||
|
|
||||||
|
public string Category { get; }
|
||||||
|
|
||||||
|
public bool Functor { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class KalkExportModuleAttribute : System.Attribute
|
||||||
|
{
|
||||||
|
public KalkExportModuleAttribute(string alias)
|
||||||
|
{
|
||||||
|
Alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Alias { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
139
Kalk/Kalk.Core/KalkModule.cs
Normal file
139
Kalk/Kalk.Core/KalkModule.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public abstract class KalkModule : IScriptCustomFunction, IScriptObject
|
||||||
|
{
|
||||||
|
protected KalkModule() : this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KalkModule(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
if (Name == null)
|
||||||
|
{
|
||||||
|
Name = this.GetType().Name;
|
||||||
|
if (Name.EndsWith("Module"))
|
||||||
|
{
|
||||||
|
Name = Name.Substring(0, Name.Length - "Module".Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Descriptors = new Dictionary<string, KalkDescriptor>();
|
||||||
|
Descriptor = new KalkDescriptor() { Names = { Name } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
public KalkEngine Engine { get; private set; }
|
||||||
|
|
||||||
|
public bool IsImported { get; private set; }
|
||||||
|
|
||||||
|
public bool IsBuiltin { get; protected set; }
|
||||||
|
|
||||||
|
public bool IsInitialized { get; private set; }
|
||||||
|
|
||||||
|
public KalkDescriptor Descriptor { get; private set; }
|
||||||
|
|
||||||
|
internal void Initialize(KalkEngine engine)
|
||||||
|
{
|
||||||
|
if (IsInitialized) return;
|
||||||
|
Engine = engine;
|
||||||
|
Initialize();
|
||||||
|
IsInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InternalImport()
|
||||||
|
{
|
||||||
|
if (IsImported) return;
|
||||||
|
|
||||||
|
Import();
|
||||||
|
|
||||||
|
IsImported = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Initialize()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Import()
|
||||||
|
{
|
||||||
|
foreach (var descriptor in Descriptors)
|
||||||
|
{
|
||||||
|
Engine.Descriptors[descriptor.Key] = descriptor.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, KalkDescriptor> Descriptors { get; }
|
||||||
|
|
||||||
|
int IScriptFunctionInfo.RequiredParameterCount => 0;
|
||||||
|
|
||||||
|
int IScriptFunctionInfo.ParameterCount => 0;
|
||||||
|
|
||||||
|
ScriptVarParamKind IScriptFunctionInfo.VarParamKind => ScriptVarParamKind.None;
|
||||||
|
|
||||||
|
Type IScriptFunctionInfo.ReturnType => typeof(object);
|
||||||
|
|
||||||
|
ScriptParameterInfo IScriptFunctionInfo.GetParameterInfo(int index) => throw new NotSupportedException("A module doesn't have any parameters.");
|
||||||
|
|
||||||
|
object IScriptCustomFunction.Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
if (!(callerContext.Parent is ScriptExpressionStatement))
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var engine = (KalkEngine) context;
|
||||||
|
engine.WriteHighlightLine($"# Module {Name}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueTask<object> IScriptCustomFunction.InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
return new ValueTask<object>(((IScriptCustomFunction)this).Invoke(context, callerContext, arguments, blockStatement));
|
||||||
|
}
|
||||||
|
|
||||||
|
int IScriptObject.Count => 0;
|
||||||
|
|
||||||
|
IEnumerable<string> IScriptObject.GetMembers() => Enumerable.Empty<string>();
|
||||||
|
|
||||||
|
bool IScriptObject.Contains(string member) => false;
|
||||||
|
|
||||||
|
bool IScriptObject.IsReadOnly
|
||||||
|
{
|
||||||
|
get => true;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IScriptObject.TryGetValue(TemplateContext context, SourceSpan span, string member, out object value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IScriptObject.CanWrite(string member) => false;
|
||||||
|
|
||||||
|
bool IScriptObject.TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly) => false;
|
||||||
|
|
||||||
|
|
||||||
|
bool IScriptObject.Remove(string member) => false;
|
||||||
|
|
||||||
|
void IScriptObject.SetReadOnly(string member, bool readOnly)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IScriptObject IScriptObject.Clone(bool deep)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("A module does not support the clone operation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
126
Kalk/Kalk.Core/KalkModuleWithFunctions.cs
Normal file
126
Kalk/Kalk.Core/KalkModuleWithFunctions.cs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public abstract class KalkModuleWithFunctions : KalkModule
|
||||||
|
{
|
||||||
|
protected KalkModuleWithFunctions() : this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KalkModuleWithFunctions(string name) : base(name)
|
||||||
|
{
|
||||||
|
Content = new ScriptObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptObject Content { get; }
|
||||||
|
|
||||||
|
protected override void Import()
|
||||||
|
{
|
||||||
|
base.Import();
|
||||||
|
|
||||||
|
// Feed the engine with our new builtins
|
||||||
|
Engine.Builtins.Import(Content);
|
||||||
|
|
||||||
|
DisplayImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DisplayImport()
|
||||||
|
{
|
||||||
|
if (!IsBuiltin && Content.Count > 0)
|
||||||
|
{
|
||||||
|
Engine.WriteHighlightLine($"# {Content.Count} functions successfully imported from module `{Name}`.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void RegisterFunctionsAuto()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterConstant(string name, object value, string category = null)
|
||||||
|
{
|
||||||
|
RegisterVariable(name, value, category);
|
||||||
|
}
|
||||||
|
protected void RegisterAction(string name, Action action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1>(string name, Action<T1> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1, T2>(string name, Action<T1, T2> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1, T2, T3>(string name, Action<T1, T2, T3> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterAction<T1, T2, T3, T4>(string name, Action<T1, T2, T3, T4> action, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.Create(action), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterFunction<T1>(string name, Func<T1> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterFunction<T1, T2>(string name, Func<T1, T2> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterFunction<T1, T2, T3>(string name, Func<T1, T2, T3> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterFunction<T1, T2, T3, T4>(string name, Func<T1, T2, T3, T4> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterFunction<T1, T2, T3, T4, T5>(string name, Func<T1, T2, T3, T4, T5> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterFunction<T1, T2, T3, T4, T5, T6>(string name, Func<T1, T2, T3, T4, T5, T6> func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterCustomFunction(name, DelegateCustomFunction.CreateFunc(func), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterCustomFunction(string name, IScriptCustomFunction func, string category = null)
|
||||||
|
{
|
||||||
|
RegisterVariable(name, func, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RegisterVariable(string name, object value, string category = null)
|
||||||
|
{
|
||||||
|
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||||
|
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||||
|
var names = name.Split(',');
|
||||||
|
|
||||||
|
KalkDescriptor descriptor = null;
|
||||||
|
|
||||||
|
foreach (var subName in names)
|
||||||
|
{
|
||||||
|
Content.SetValue(subName, value, true);
|
||||||
|
|
||||||
|
if (descriptor == null || !Descriptors.TryGetValue(names[0], out descriptor))
|
||||||
|
{
|
||||||
|
descriptor = new KalkDescriptor();
|
||||||
|
}
|
||||||
|
Descriptors.Add(subName, descriptor);
|
||||||
|
descriptor.Names.Add(subName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Kalk/Kalk.Core/KalkObjectWithAlias.cs
Normal file
55
Kalk/Kalk.Core/KalkObjectWithAlias.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkObjectWithAlias : ScriptObject
|
||||||
|
{
|
||||||
|
public KalkObjectWithAlias()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkObjectWithAlias(KalkEngine engine)
|
||||||
|
{
|
||||||
|
Engine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkEngine Engine { get; set; }
|
||||||
|
|
||||||
|
public override bool Contains(string member)
|
||||||
|
{
|
||||||
|
member = Alias(member);
|
||||||
|
return base.Contains(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite(string member)
|
||||||
|
{
|
||||||
|
member = Alias(member);
|
||||||
|
return base.CanWrite(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryGetValue(TemplateContext context, SourceSpan span, string member, out object value)
|
||||||
|
{
|
||||||
|
member = Alias(member);
|
||||||
|
return base.TryGetValue(context, span, member, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly)
|
||||||
|
{
|
||||||
|
member = Alias(member);
|
||||||
|
return base.TrySetValue(context, span, member, value, readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Alias(string name)
|
||||||
|
{
|
||||||
|
// Replace with an alias
|
||||||
|
if (Engine.Aliases.TryGetAlias(name, out var alias))
|
||||||
|
{
|
||||||
|
name = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Kalk/Kalk.Core/KalkSettings.cs
Normal file
12
Kalk/Kalk.Core/KalkSettings.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkSettings
|
||||||
|
{
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Kalk/Kalk.Core/KalkShortcutKeyMap.cs
Normal file
12
Kalk/Kalk.Core/KalkShortcutKeyMap.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkShortcutKeyMap : Dictionary<KalkConsoleKey, object>
|
||||||
|
{
|
||||||
|
public bool TryGetShortcut(KalkConsoleKey key, out object next)
|
||||||
|
{
|
||||||
|
return TryGetValue(key, out next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
Kalk/Kalk.Core/KalkShortcuts.cs
Normal file
176
Kalk/Kalk.Core/KalkShortcuts.cs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkShortcuts : ScriptObject, IScriptCustomFunction
|
||||||
|
{
|
||||||
|
public KalkShortcuts()
|
||||||
|
{
|
||||||
|
ShortcutKeyMap = new KalkShortcutKeyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkShortcutKeyMap ShortcutKeyMap { get; }
|
||||||
|
|
||||||
|
public int RequiredParameterCount => 0;
|
||||||
|
|
||||||
|
public int ParameterCount => 0;
|
||||||
|
|
||||||
|
public ScriptVarParamKind VarParamKind => ScriptVarParamKind.None;
|
||||||
|
|
||||||
|
public Type ReturnType => typeof(object);
|
||||||
|
|
||||||
|
public ScriptParameterInfo GetParameterInfo(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Shortcuts don't have any parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Clear()
|
||||||
|
{
|
||||||
|
base.Clear();
|
||||||
|
ShortcutKeyMap.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveShortcut(string name)
|
||||||
|
{
|
||||||
|
if (!TryGetValue(name, out var shortcut)) return;
|
||||||
|
Remove(name);
|
||||||
|
|
||||||
|
foreach (KalkShortcutKeySequence shortcutKey in shortcut.Keys)
|
||||||
|
{
|
||||||
|
var map = ShortcutKeyMap;
|
||||||
|
KalkConsoleKey consoleKey = default;
|
||||||
|
for (int i = 0; i < shortcutKey.Keys.Count; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
if (!map.TryGetShortcut(consoleKey, out var newMap) || !(newMap is KalkShortcutKeyMap))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
map = (KalkShortcutKeyMap)newMap;
|
||||||
|
}
|
||||||
|
consoleKey = shortcutKey.Keys[i];
|
||||||
|
}
|
||||||
|
map.Remove(consoleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup all maps to remove empty ones
|
||||||
|
CleanupMap(ShortcutKeyMap);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupMap(KalkShortcutKeyMap map)
|
||||||
|
{
|
||||||
|
var keyParis = map.ToList();
|
||||||
|
foreach (var keyPair in keyParis)
|
||||||
|
{
|
||||||
|
if (keyPair.Value is KalkShortcutKeyMap nestedMap)
|
||||||
|
{
|
||||||
|
CleanupMap(nestedMap);
|
||||||
|
if (nestedMap.Count == 0)
|
||||||
|
{
|
||||||
|
map.Remove(keyPair.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSymbolShortcut(KalkShortcut shortcut)
|
||||||
|
{
|
||||||
|
if (shortcut == null) throw new ArgumentNullException(nameof(shortcut));
|
||||||
|
RemoveShortcut(shortcut.Name);
|
||||||
|
Add(shortcut.Name, shortcut);
|
||||||
|
|
||||||
|
foreach (KalkShortcutKeySequence shortcutKey in shortcut.Keys)
|
||||||
|
{
|
||||||
|
var map = ShortcutKeyMap;
|
||||||
|
KalkConsoleKey consoleKey = default;
|
||||||
|
for (int i = 0; i < shortcutKey.Keys.Count; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
if (!map.TryGetShortcut(consoleKey, out var newMap) || !(newMap is KalkShortcutKeyMap))
|
||||||
|
{
|
||||||
|
newMap = new KalkShortcutKeyMap();
|
||||||
|
map[consoleKey] = newMap;
|
||||||
|
}
|
||||||
|
map = (KalkShortcutKeyMap)newMap;
|
||||||
|
}
|
||||||
|
consoleKey = shortcutKey.Keys[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
map[consoleKey] = shortcutKey.Expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out KalkShortcut value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
if (TryGetValue(null, new SourceSpan(), key, out var valueObj))
|
||||||
|
{
|
||||||
|
value = (KalkShortcut) valueObj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly)
|
||||||
|
{
|
||||||
|
// In the case of using KalkSymbols outside of the scripting engine
|
||||||
|
if (context == null) return base.TrySetValue(null, span, member, value, readOnly);
|
||||||
|
|
||||||
|
// Otherwise, we are not allowing to modify this object.
|
||||||
|
throw new ScriptRuntimeException(span, "Shortcuts object can't be modified directly. You need to use the command `shortcut` instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
if (!(callerContext.Parent is ScriptExpressionStatement))
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display((KalkEngine)context, "Builtin Shortcuts", filter => !filter.IsUser);
|
||||||
|
Display((KalkEngine) context, "User-defined Shortcuts", filter => filter.IsUser);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Display(KalkEngine engine, string title, Func<KalkShortcut, bool> filter = null, bool addBlankLine = false)
|
||||||
|
{
|
||||||
|
if (engine == null) throw new ArgumentNullException(nameof(engine));
|
||||||
|
if (title == null) throw new ArgumentNullException(nameof(title));
|
||||||
|
|
||||||
|
var alreadyPrinted = new HashSet<KalkShortcut>();
|
||||||
|
bool isFirst = true;
|
||||||
|
foreach (var unitKey in this.Keys.OrderBy(x => x))
|
||||||
|
{
|
||||||
|
var shortcut = this[unitKey] as KalkShortcut;
|
||||||
|
if (shortcut == null || !alreadyPrinted.Add(shortcut) || (filter != null && !filter(shortcut))) continue;
|
||||||
|
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine($"# {title}");
|
||||||
|
}
|
||||||
|
else if (addBlankLine)
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine("");
|
||||||
|
}
|
||||||
|
isFirst = false;
|
||||||
|
|
||||||
|
engine.WriteHighlightLine(shortcut.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
return new ValueTask<object>(Invoke(context, callerContext, arguments, blockStatement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
Kalk/Kalk.Core/KalkUnits.cs
Normal file
104
Kalk/Kalk.Core/KalkUnits.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkUnits : KalkObjectWithAlias, IScriptCustomFunction
|
||||||
|
{
|
||||||
|
public int RequiredParameterCount => 0;
|
||||||
|
|
||||||
|
public int ParameterCount => 0;
|
||||||
|
|
||||||
|
public ScriptVarParamKind VarParamKind => ScriptVarParamKind.None;
|
||||||
|
|
||||||
|
public Type ReturnType => typeof(object);
|
||||||
|
|
||||||
|
public KalkUnits(KalkEngine engine) : base(engine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptParameterInfo GetParameterInfo(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Units don't have any parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out KalkUnit value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
if (TryGetValue(null, new SourceSpan(), key, out var valueObj))
|
||||||
|
{
|
||||||
|
value = (KalkUnit) valueObj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly)
|
||||||
|
{
|
||||||
|
// In the case of using KalkUnits outside of the scripting engine
|
||||||
|
if (context == null) return base.TrySetValue(null, span, member, value, readOnly);
|
||||||
|
|
||||||
|
// Otherwise, we are not allowing to modify this object.
|
||||||
|
throw new ScriptRuntimeException(span, "Units object can't be modified directly. You need to use the command `unit` instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
if (!(callerContext.Parent is ScriptExpressionStatement))
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var engine = (KalkEngine) context;
|
||||||
|
if (this.All(x => x.Value.GetType() != typeof(KalkUnit)))
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine($"# No Units defined (e.g try `import StandardUnitsModule`)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Display((KalkEngine) context, "Builtin Units", symbol => symbol.GetType() == typeof(KalkUnit) && !symbol.IsUser);
|
||||||
|
Display((KalkEngine) context, "User Defined Units", symbol => symbol.GetType() == typeof(KalkUnit) && symbol.IsUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Display(KalkEngine engine, string title, Func<KalkUnit, bool> filter, bool addBlankLine = true)
|
||||||
|
{
|
||||||
|
if (engine == null) throw new ArgumentNullException(nameof(engine));
|
||||||
|
if (title == null) throw new ArgumentNullException(nameof(title));
|
||||||
|
|
||||||
|
var alreadyPrinted = new HashSet<KalkUnit>();
|
||||||
|
bool isFirst = true;
|
||||||
|
foreach (var unitKey in this.Keys.OrderBy(x => x))
|
||||||
|
{
|
||||||
|
var unit = this[unitKey] as KalkUnit;
|
||||||
|
if (unit == null || !alreadyPrinted.Add(unit) || unit.Parent != null || (filter != null && !filter(unit))) continue;
|
||||||
|
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine($"# {title}");
|
||||||
|
}
|
||||||
|
else if (addBlankLine)
|
||||||
|
{
|
||||||
|
engine.WriteHighlightLine("");
|
||||||
|
}
|
||||||
|
isFirst = false;
|
||||||
|
|
||||||
|
unit.Invoke(engine, engine.CurrentNode, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
return new ValueTask<object>(Invoke(context, callerContext, arguments, blockStatement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Kalk/Kalk.Core/Model/IKalkDisplayable.cs
Normal file
7
Kalk/Kalk.Core/Model/IKalkDisplayable.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public interface IKalkDisplayable
|
||||||
|
{
|
||||||
|
void Display(KalkEngine engine, KalkDisplayMode mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Kalk/Kalk.Core/Model/KalkAlias.cs
Normal file
43
Kalk/Kalk.Core/Model/KalkAlias.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public sealed class KalkAlias : ScriptObject
|
||||||
|
{
|
||||||
|
public KalkAlias(string name, IEnumerable<string> aliases, bool isUser)
|
||||||
|
{
|
||||||
|
if (aliases == null) throw new ArgumentNullException(nameof(aliases));
|
||||||
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
Aliases = new ScriptArray(aliases) {IsReadOnly = true};
|
||||||
|
SetValue("name", Name, true);
|
||||||
|
SetValue("aliases", Aliases, true);
|
||||||
|
IsReadOnly = true;
|
||||||
|
IsUser = isUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public ScriptArray Aliases { get; }
|
||||||
|
|
||||||
|
public bool IsUser { get; }
|
||||||
|
|
||||||
|
public override string ToString(string format, IFormatProvider formatProvider)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append($"alias({Name}, ");
|
||||||
|
for (var i = 0; i < Aliases.Count; i++)
|
||||||
|
{
|
||||||
|
var alias = Aliases[i];
|
||||||
|
if (i > 0) builder.Append(", ");
|
||||||
|
builder.Append(alias);
|
||||||
|
}
|
||||||
|
builder.Append(")");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
Kalk/Kalk.Core/Model/KalkBinaryExpression.cs
Normal file
104
Kalk/Kalk.Core/Model/KalkBinaryExpression.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkBinaryExpression : KalkExpressionWithMembers
|
||||||
|
{
|
||||||
|
private static readonly (string, Func<KalkExpressionWithMembers, object> getter)[] MemberDefs = {
|
||||||
|
("kind", unit => unit.TypeName),
|
||||||
|
("value", unit => ((KalkBinaryExpression)unit).Value),
|
||||||
|
("operator", unit => ((KalkBinaryExpression)unit).OperatorText),
|
||||||
|
("unit", unit => ((KalkBinaryExpression)unit).Unit),
|
||||||
|
};
|
||||||
|
|
||||||
|
public KalkBinaryExpression()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkBinaryExpression(object value, ScriptBinaryOperator @operator, object unit)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Operator = @operator;
|
||||||
|
Unit = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TypeName => "binary expression";
|
||||||
|
|
||||||
|
public object Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the operator. Supported: /, *, ^
|
||||||
|
/// </summary>
|
||||||
|
public ScriptBinaryOperator Operator { get; set; }
|
||||||
|
|
||||||
|
public string OperatorText => Operator.ToText();
|
||||||
|
|
||||||
|
public object Unit { get; set; }
|
||||||
|
|
||||||
|
public override object GetValue() => Value;
|
||||||
|
|
||||||
|
public override KalkUnit GetUnit() => Unit as KalkUnit;
|
||||||
|
|
||||||
|
public override string ToString(string format, IFormatProvider formatProvider)
|
||||||
|
{
|
||||||
|
switch (Operator)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.Power:
|
||||||
|
return (Value is ScriptBinaryExpression) ? $"({string.Format(formatProvider, "{0}",Value)}){OperatorText}{string.Format(formatProvider, "{0}", Unit)}" : $"{string.Format(formatProvider, "{0}", Value)}{OperatorText}{string.Format(formatProvider, "{0}", Unit)}";
|
||||||
|
default:
|
||||||
|
return $"{string.Format(formatProvider, "{0}", Value)} {OperatorText} {string.Format(formatProvider, "{0}", Unit)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (string, Func<KalkExpressionWithMembers, object> getter)[] Members => MemberDefs;
|
||||||
|
|
||||||
|
protected override bool EqualsImpl(TemplateContext context, KalkExpression other)
|
||||||
|
{
|
||||||
|
var otherBin = (KalkBinaryExpression) other;
|
||||||
|
if (Operator != otherBin.Operator) return false;
|
||||||
|
|
||||||
|
if (Value is KalkExpression && otherBin.Value.GetType() != Value.GetType()) return false;
|
||||||
|
if (Unit is KalkExpression && otherBin.Unit.GetType() != Unit.GetType()) return false;
|
||||||
|
|
||||||
|
if (Value is KalkExpression leftExpr)
|
||||||
|
{
|
||||||
|
if (!Equals(context, leftExpr, (KalkExpression) otherBin.Value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var leftValue = context.ToObject<double>(context.CurrentSpan, Value);
|
||||||
|
var otherLeftValue = context.ToObject<double>(context.CurrentSpan, otherBin.Value);
|
||||||
|
if (!KalkValue.AlmostEqual(leftValue, otherLeftValue))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Unit is KalkExpression rightExpr)
|
||||||
|
{
|
||||||
|
if (!Equals(context, rightExpr, (KalkExpression)otherBin.Unit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var rightValue = context.ToObject<double>(context.CurrentSpan, Unit);
|
||||||
|
var otherRightValue = context.ToObject<double>(context.CurrentSpan, otherBin.Unit);
|
||||||
|
if (!KalkValue.AlmostEqual(rightValue, otherRightValue))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
214
Kalk/Kalk.Core/Model/KalkComplex.cs
Normal file
214
Kalk/Kalk.Core/Model/KalkComplex.cs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public struct KalkComplex : IScriptCustomType, IKalkSpannable
|
||||||
|
{
|
||||||
|
private Complex _value;
|
||||||
|
private const double MaxRoundToZero = 1e-14;
|
||||||
|
|
||||||
|
public KalkComplex(double real, double im)
|
||||||
|
{
|
||||||
|
_value = new Complex(real, im);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkComplex(Complex value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TypeName => "complex";
|
||||||
|
|
||||||
|
public double Re => _value.Real;
|
||||||
|
|
||||||
|
public double Im => _value.Imaginary;
|
||||||
|
|
||||||
|
public double Phase => _value.Phase;
|
||||||
|
|
||||||
|
public double Magnitude => _value.Magnitude;
|
||||||
|
|
||||||
|
public bool HasIm => _value.Imaginary > MaxRoundToZero;
|
||||||
|
|
||||||
|
internal Complex Value => _value;
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (leftValue is KalkComplex leftComplex && rightValue is KalkComplex rightComplex)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.Add:
|
||||||
|
result = (KalkComplex) (leftComplex._value + rightComplex._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Subtract:
|
||||||
|
result = (KalkComplex)(leftComplex._value - rightComplex._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Divide:
|
||||||
|
result = (KalkComplex)(leftComplex._value / rightComplex._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.DivideRound:
|
||||||
|
result = (KalkComplex)(leftComplex._value / rightComplex._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Multiply:
|
||||||
|
result = (KalkComplex)(leftComplex._value * rightComplex._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Power:
|
||||||
|
result = (KalkComplex)(Complex.Pow(leftComplex._value, rightComplex._value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rightValue is KalkComplex rightComplex2)
|
||||||
|
{
|
||||||
|
var leftAsDouble = (double)context.ToObject(span, leftValue, typeof(double));
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.Add:
|
||||||
|
result = (KalkComplex)(leftAsDouble + rightComplex2._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Subtract:
|
||||||
|
result = (KalkComplex)(leftAsDouble - rightComplex2._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Divide:
|
||||||
|
result = (KalkComplex)(leftAsDouble / rightComplex2._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.DivideRound:
|
||||||
|
result = (KalkComplex)(leftAsDouble / rightComplex2._value);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Multiply:
|
||||||
|
result = (KalkComplex)(leftAsDouble * rightComplex2._value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (leftValue is KalkComplex leftComplex2)
|
||||||
|
{
|
||||||
|
var rightAsDouble = (double)context.ToObject(span, rightValue, typeof(double));
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.Add:
|
||||||
|
result = (KalkComplex)(leftComplex2._value + rightAsDouble);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Subtract:
|
||||||
|
result = (KalkComplex)(leftComplex2._value - rightAsDouble);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Divide:
|
||||||
|
result = (KalkComplex)(leftComplex2._value / rightAsDouble);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.DivideRound:
|
||||||
|
result = (KalkComplex)(leftComplex2._value / rightAsDouble);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Multiply:
|
||||||
|
result = (KalkComplex)(leftComplex2._value * rightAsDouble);
|
||||||
|
return true;
|
||||||
|
case ScriptBinaryOperator.Power:
|
||||||
|
result = (KalkComplex)(Complex.Pow(leftComplex2._value, rightAsDouble));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptUnaryOperator op, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptUnaryOperator.Negate:
|
||||||
|
result = (KalkComplex) (-((KalkComplex) rightValue)._value);
|
||||||
|
return true;
|
||||||
|
case ScriptUnaryOperator.Plus:
|
||||||
|
result = rightValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator KalkComplex(Complex complex)
|
||||||
|
{
|
||||||
|
return new KalkComplex(complex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
// We round the number to make the display nicer
|
||||||
|
var re = Math.Round(Re, 10, MidpointRounding.AwayFromZero);
|
||||||
|
var im = Math.Round(Im, 10, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
if (Math.Abs(re) > 1e-14) builder.AppendFormat(CultureInfo.CurrentCulture, "{0}", re);
|
||||||
|
|
||||||
|
if (HasIm)
|
||||||
|
{
|
||||||
|
if (builder.Length > 0) builder.Append(" + ");
|
||||||
|
|
||||||
|
if (Math.Abs(Math.Abs(im) - 1.0) < MaxRoundToZero)
|
||||||
|
{
|
||||||
|
builder.Append("i");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendFormat(CultureInfo.CurrentCulture, "{0}i", im);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder.Length == 0) builder.Append("0");
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
return MemoryMarshal.CreateSpan(ref Unsafe.As<KalkComplex, byte>(ref Unsafe.AsRef(this)), Unsafe.SizeOf<KalkComplex>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryConvertTo(TemplateContext context, SourceSpan span, Type type, out object value)
|
||||||
|
{
|
||||||
|
if (type == typeof(object))
|
||||||
|
{
|
||||||
|
value = this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(int))
|
||||||
|
{
|
||||||
|
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to an integer as it has an imaginary part.");
|
||||||
|
value = (int) Re;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(float))
|
||||||
|
{
|
||||||
|
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a float as it has an imaginary part.");
|
||||||
|
value = (float)Re;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(double))
|
||||||
|
{
|
||||||
|
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a double as it has an imaginary part.");
|
||||||
|
value = (double)Re;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(decimal))
|
||||||
|
{
|
||||||
|
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a decimal as it has an imaginary part.");
|
||||||
|
value = (decimal)Re;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(BigInteger))
|
||||||
|
{
|
||||||
|
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a big integer as it has an imaginary part.");
|
||||||
|
value = (BigInteger)Re;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ScriptRuntimeException(span, $"Cannot convert {this} to an {type} as it has an imaginary part.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
217
Kalk/Kalk.Core/Model/KalkCompositeValue.cs
Normal file
217
Kalk/Kalk.Core/Model/KalkCompositeValue.cs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
using System;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Helpers;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkCompositeValue : IScriptConvertibleFrom, IScriptTransformable
|
||||||
|
{
|
||||||
|
public IScriptTransformable Transformable;
|
||||||
|
|
||||||
|
public object Value;
|
||||||
|
|
||||||
|
public KalkCompositeValue()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkCompositeValue(object value)
|
||||||
|
{
|
||||||
|
Transformable = value as IScriptTransformable;
|
||||||
|
Value = Transformable == null ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkCompositeValue(IScriptTransformable transformable)
|
||||||
|
{
|
||||||
|
Transformable = transformable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object TransformArg<TFrom, TTo>(TemplateContext context, Func<TFrom, TTo> apply)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Transform(context, apply);
|
||||||
|
}
|
||||||
|
catch (ScriptRuntimeException ex)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(0, ex.OriginalMessage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(0, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Transform<TFrom, TTo>(TemplateContext context, Func<TFrom, TTo> apply)
|
||||||
|
{
|
||||||
|
if (Transformable != null)
|
||||||
|
{
|
||||||
|
return Transformable.Transform(context, context.CurrentSpan, value =>
|
||||||
|
{
|
||||||
|
var nestedValue = (KalkCompositeValue)context.ToObject(context.CurrentSpan, value, this.GetType());
|
||||||
|
// Recursively apply the transformation with the same value
|
||||||
|
return nestedValue.Transform(context, apply);
|
||||||
|
}, typeof(TTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply(context.ToObject<TFrom>(context.CurrentSpan, Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual Type ElementType => typeof(object);
|
||||||
|
|
||||||
|
public virtual bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit)
|
||||||
|
{
|
||||||
|
if (Transformable != null)
|
||||||
|
{
|
||||||
|
return Transformable.Visit(context, span, visit);
|
||||||
|
}
|
||||||
|
return visit(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType)
|
||||||
|
{
|
||||||
|
if (Transformable != null) return Transformable.Transform(context, span, apply, destType);
|
||||||
|
return apply(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryConvertFrom(TemplateContext context, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
if (value is IScriptTransformable valuable)
|
||||||
|
{
|
||||||
|
if (!CanTransform(valuable))
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(span, $"Cannot transform the value `{value}` between the element type `{valuable.ElementType.ScriptPrettyName()}` and the target function `{ElementType.ScriptPrettyName()}`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Transformable = valuable;
|
||||||
|
}
|
||||||
|
else Value = Accept(context, span, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool CanTransform(IScriptTransformable valuable)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual object Accept(TemplateContext context, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KalkDoubleValue : KalkCompositeValue
|
||||||
|
{
|
||||||
|
public KalkDoubleValue()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkDoubleValue(IScriptTransformable transformable) : base(transformable)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkDoubleValue(object value) : base(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override Type ElementType => typeof(double);
|
||||||
|
|
||||||
|
public override bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return transformType == typeof(double) || transformType == typeof(float) || transformType == typeof(KalkHalf);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanTransform(IScriptTransformable valuable)
|
||||||
|
{
|
||||||
|
return valuable.CanTransform(typeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
return context.ToObject<double>(span, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class KalkTrigDoubleValue : KalkDoubleValue
|
||||||
|
{
|
||||||
|
public KalkTrigDoubleValue()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkTrigDoubleValue(IScriptTransformable transformable) : base(transformable)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkTrigDoubleValue(object value) : base(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Type ElementType => typeof(double);
|
||||||
|
|
||||||
|
public override bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return transformType == typeof(double) || transformType == typeof(float) || transformType == typeof(KalkHalf);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanTransform(IScriptTransformable valuable)
|
||||||
|
{
|
||||||
|
return valuable.CanTransform(typeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
return context.ToObject<double>(span, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KalkLongValue : KalkCompositeValue
|
||||||
|
{
|
||||||
|
public override Type ElementType => typeof(long);
|
||||||
|
|
||||||
|
public override bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return transformType == typeof(long) || transformType == typeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanTransform(IScriptTransformable valuable)
|
||||||
|
{
|
||||||
|
return valuable.CanTransform(typeof(long));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
return context.ToObject<long>(span, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KalkIntValue : KalkCompositeValue
|
||||||
|
{
|
||||||
|
public override Type ElementType => typeof(int);
|
||||||
|
|
||||||
|
public override bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return transformType == typeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanTransform(IScriptTransformable valuable)
|
||||||
|
{
|
||||||
|
return valuable.CanTransform(typeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
return context.ToObject<int>(span, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
233
Kalk/Kalk.Core/Model/KalkConsoleKey.cs
Normal file
233
Kalk/Kalk.Core/Model/KalkConsoleKey.cs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public readonly struct KalkConsoleKey : IEquatable<KalkConsoleKey>
|
||||||
|
{
|
||||||
|
// Maps of name to ConsoleKey
|
||||||
|
private static readonly Dictionary<string, ConsoleKey> MapNameToConsoleKey = new Dictionary<string, ConsoleKey>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Maps of ConsoleKey to name
|
||||||
|
private static readonly Dictionary<ConsoleKey, string> MapConsoleKeyToName = new Dictionary<ConsoleKey, string>();
|
||||||
|
|
||||||
|
static KalkConsoleKey()
|
||||||
|
{
|
||||||
|
var names = Enum.GetNames<ConsoleKey>();
|
||||||
|
var values = Enum.GetValues<ConsoleKey>();
|
||||||
|
|
||||||
|
for (var i = 0; i < names.Length; i++)
|
||||||
|
{
|
||||||
|
var name = StandardMemberRenamer.Rename(names[i]);
|
||||||
|
var key = values[i];
|
||||||
|
if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9)
|
||||||
|
{
|
||||||
|
name = ('0' + (key - ConsoleKey.D0)).ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
MapKey(name, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
MapKey("left", ConsoleKey.LeftArrow);
|
||||||
|
MapKey("right", ConsoleKey.RightArrow);
|
||||||
|
MapKey("up", ConsoleKey.UpArrow);
|
||||||
|
MapKey("down", ConsoleKey.DownArrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MapKey(string name, ConsoleKey key)
|
||||||
|
{
|
||||||
|
MapNameToConsoleKey.Add(name, key);
|
||||||
|
MapConsoleKeyToName[key] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkConsoleKey(ConsoleModifiers modifiers, ConsoleKey key, char keyChar)
|
||||||
|
{
|
||||||
|
Modifiers = modifiers;
|
||||||
|
Key = key;
|
||||||
|
// Normalize keychar, whenever there is a modifier, we don't store the character
|
||||||
|
if (modifiers == ConsoleModifiers.Shift && key >= ConsoleKey.A && key <= ConsoleKey.Z)
|
||||||
|
{
|
||||||
|
Modifiers = 0;
|
||||||
|
KeyChar = keyChar;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We need to force char to 0 when backspace, as it is not consistent between Windows and macOS/Linux
|
||||||
|
KeyChar = Modifiers != 0 || key == ConsoleKey.Backspace ? (char)0 : keyChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleModifiers Modifiers { get; }
|
||||||
|
|
||||||
|
public ConsoleKey Key { get; }
|
||||||
|
|
||||||
|
public char KeyChar { get; }
|
||||||
|
|
||||||
|
public bool Equals(KalkConsoleKey other)
|
||||||
|
{
|
||||||
|
return Key == other.Key &&
|
||||||
|
KeyChar == other.KeyChar &&
|
||||||
|
Modifiers == other.Modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is KalkConsoleKey other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hashCode = (int)Modifiers;
|
||||||
|
hashCode = (hashCode * 397) ^ (int) Key;
|
||||||
|
hashCode = (hashCode * 397) ^ (int) KeyChar;
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(KalkConsoleKey left, KalkConsoleKey right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(KalkConsoleKey left, KalkConsoleKey right)
|
||||||
|
{
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static KalkConsoleKey Parse(string keyText)
|
||||||
|
{
|
||||||
|
if (keyText == null) throw new ArgumentNullException(nameof(keyText));
|
||||||
|
|
||||||
|
ConsoleModifiers modifiers = 0;
|
||||||
|
ConsoleKey key = 0;
|
||||||
|
char keyChar = (char)0;
|
||||||
|
int index = 0;
|
||||||
|
bool expectingPlus = false;
|
||||||
|
while (index < keyText.Length)
|
||||||
|
{
|
||||||
|
if (expectingPlus)
|
||||||
|
{
|
||||||
|
if (keyText[index] != '+')
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unexpected character `{keyText[index]}`. Expecting a + between keys.", nameof(keyText));
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
expectingPlus = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.Compare(keyText, index, "CTRL", 0, "CTRL".Length, true) == 0)
|
||||||
|
{
|
||||||
|
if ((modifiers & ConsoleModifiers.Control) != 0) throw new ArgumentException("Cannot have multiple CTRL keys in a shortcut.", nameof(keyText));
|
||||||
|
modifiers |= ConsoleModifiers.Control;
|
||||||
|
index += "CTRL".Length;
|
||||||
|
expectingPlus = true;
|
||||||
|
}
|
||||||
|
else if (string.Compare(keyText, index, "ALT", 0, "ALT".Length, true) == 0)
|
||||||
|
{
|
||||||
|
if ((modifiers & ConsoleModifiers.Alt) != 0) throw new ArgumentException("Cannot have multiple ALT keys in a shortcut.", nameof(keyText));
|
||||||
|
modifiers |= ConsoleModifiers.Alt;
|
||||||
|
index += "ALT".Length;
|
||||||
|
expectingPlus = true;
|
||||||
|
}
|
||||||
|
else if (string.Compare(keyText, index, "SHIFT", 0, "SHIFT".Length, true) == 0)
|
||||||
|
{
|
||||||
|
// Skip the shift but don't expect it's modifiers
|
||||||
|
index += "SHIFT".Length;
|
||||||
|
expectingPlus = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var remainingText = keyText.Substring(index);
|
||||||
|
expectingPlus = false;
|
||||||
|
if (!MapNameToConsoleKey.TryGetValue(remainingText, out key))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid key found `{remainingText}`. Expecting: {string.Join(", ", Enum.GetNames(typeof(ConsoleKey)))}.", nameof(keyText));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9)
|
||||||
|
{
|
||||||
|
keyChar = (char) ('0' + (int) key - (int) ConsoleKey.D0);
|
||||||
|
}
|
||||||
|
else if (key >= ConsoleKey.A && key <= ConsoleKey.Z)
|
||||||
|
{
|
||||||
|
keyChar = remainingText[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keyChar = key switch
|
||||||
|
{
|
||||||
|
ConsoleKey.Add => '+',
|
||||||
|
ConsoleKey.Attention => '!',
|
||||||
|
ConsoleKey.Divide => '/',
|
||||||
|
ConsoleKey.Multiply => '*',
|
||||||
|
ConsoleKey.Subtract => '-',
|
||||||
|
ConsoleKey.OemPlus => '=',
|
||||||
|
ConsoleKey.OemMinus => '-',
|
||||||
|
ConsoleKey.OemPeriod => ';',
|
||||||
|
ConsoleKey.OemComma => ',',
|
||||||
|
ConsoleKey.Tab => '\t',
|
||||||
|
ConsoleKey.Enter => '\r',
|
||||||
|
ConsoleKey.Spacebar => ' ',
|
||||||
|
ConsoleKey.Backspace => (char)0,
|
||||||
|
ConsoleKey.Escape => (char) 0x1b,
|
||||||
|
_ => keyChar
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KalkConsoleKey(modifiers, key, keyChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
var modifiers = Modifiers;
|
||||||
|
if ((modifiers & ConsoleModifiers.Control) != 0)
|
||||||
|
{
|
||||||
|
builder.Append("ctrl");
|
||||||
|
}
|
||||||
|
if ((modifiers & ConsoleModifiers.Alt) != 0)
|
||||||
|
{
|
||||||
|
if (builder.Length > 0) builder.Append('+');
|
||||||
|
builder.Append("alt");
|
||||||
|
}
|
||||||
|
if ((modifiers & ConsoleModifiers.Shift) != 0)
|
||||||
|
{
|
||||||
|
if (builder.Length > 0) builder.Append('+');
|
||||||
|
builder.Append("shift");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append('+');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Key >= ConsoleKey.A && Key <= ConsoleKey.Z && KeyChar != 0)
|
||||||
|
{
|
||||||
|
builder.Append(KeyChar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append(MapConsoleKeyToName[Key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator KalkConsoleKey(ConsoleKeyInfo keyInfo)
|
||||||
|
{
|
||||||
|
return new KalkConsoleKey(keyInfo.Modifiers, keyInfo.Key, keyInfo.KeyChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator ConsoleKeyInfo(KalkConsoleKey key)
|
||||||
|
{
|
||||||
|
return new ConsoleKeyInfo(key.KeyChar, key.Key, (key.Modifiers & ConsoleModifiers.Shift) != 0, (key.Modifiers & ConsoleModifiers.Alt) != 0, (key.Modifiers & ConsoleModifiers.Control) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Kalk/Kalk.Core/Model/KalkConstructor.cs
Normal file
12
Kalk/Kalk.Core/Model/KalkConstructor.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public abstract class KalkConstructor
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Kalk/Kalk.Core/Model/KalkDescriptor.cs
Normal file
50
Kalk/Kalk.Core/Model/KalkDescriptor.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkDescriptor
|
||||||
|
{
|
||||||
|
public KalkDescriptor()
|
||||||
|
{
|
||||||
|
Names = new List<string>();
|
||||||
|
Params = new List<KalkParamDescriptor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> Names { get;}
|
||||||
|
|
||||||
|
public bool IsCommand { get; set; }
|
||||||
|
|
||||||
|
public string Category { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public List<KalkParamDescriptor> Params { get; }
|
||||||
|
|
||||||
|
public string Syntax { get; set; }
|
||||||
|
|
||||||
|
public string Returns { get; set; }
|
||||||
|
|
||||||
|
public string Remarks { get; set; }
|
||||||
|
|
||||||
|
public string Example { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KalkParamDescriptor
|
||||||
|
{
|
||||||
|
public KalkParamDescriptor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkParamDescriptor(string name, string description)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public bool IsOptional { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
224
Kalk/Kalk.Core/Model/KalkExpression.cs
Normal file
224
Kalk/Kalk.Core/Model/KalkExpression.cs
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MathNet.Numerics.LinearAlgebra.Complex;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Helpers;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class KalkExpression : KalkObject, IScriptCustomBinaryOperation, IScriptCustomUnaryOperation, IFormattable, IScriptTransformable
|
||||||
|
{
|
||||||
|
protected KalkExpression()
|
||||||
|
{
|
||||||
|
OriginalExpression = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract object GetValue();
|
||||||
|
|
||||||
|
public abstract KalkUnit GetUnit();
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null, CultureInfo.CurrentCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string ToString(string format, IFormatProvider formatProvider);
|
||||||
|
|
||||||
|
public static bool Equals(TemplateContext context, KalkExpression left, KalkExpression right)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, right)) return false;
|
||||||
|
if (ReferenceEquals(left, right)) return true;
|
||||||
|
return right.GetType() == left.GetType() && left.EqualsImpl(context, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkExpression OriginalExpression { get; set; }
|
||||||
|
|
||||||
|
public sealed override IScriptObject Clone(bool deep)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("This expression cannot be cloned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool EqualsImpl(TemplateContext context, KalkExpression right);
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
var leftExpr = leftValue as KalkExpression;
|
||||||
|
if (leftExpr is null && !KalkValue.IsNumber(leftValue))
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(leftSpan, "Expecting a number, vector or matrix");
|
||||||
|
}
|
||||||
|
var rightExpr = rightValue as KalkExpression;
|
||||||
|
if (rightExpr is null && !KalkValue.IsNumber(rightValue))
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(rightSpan, "Expecting a number, vector or matrix");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = null;
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.CompareEqual:
|
||||||
|
case ScriptBinaryOperator.CompareNotEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLess:
|
||||||
|
case ScriptBinaryOperator.CompareGreater:
|
||||||
|
if (leftExpr != null && rightExpr != null)
|
||||||
|
{
|
||||||
|
var leftSimplifier = new KalkExpressionSimplifier(context);
|
||||||
|
var (leftValueMultiplier, newLeftExpr) = leftSimplifier.Canonical(leftExpr);
|
||||||
|
|
||||||
|
var rightSimplifier = new KalkExpressionSimplifier(context);
|
||||||
|
var (rightValueMultiplier, newRightExpr) = rightSimplifier.Canonical(rightExpr);
|
||||||
|
|
||||||
|
var exprEquals = Equals(context, newLeftExpr, newRightExpr);
|
||||||
|
if (exprEquals)
|
||||||
|
{
|
||||||
|
var almostEqual = KalkValue.AlmostEqual(leftValueMultiplier, rightValueMultiplier);
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.CompareEqual:
|
||||||
|
result = almostEqual;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareNotEqual:
|
||||||
|
result = !almostEqual;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||||
|
result = almostEqual || ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v1 && v1;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||||
|
result = almostEqual || ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v2 && v2;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareLess:
|
||||||
|
result = ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v3 && v3;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareGreater:
|
||||||
|
result = ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v4 && v4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = op == ScriptBinaryOperator.CompareNotEqual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (op == ScriptBinaryOperator.CompareEqual || op == ScriptBinaryOperator.CompareNotEqual)
|
||||||
|
{
|
||||||
|
result = op == ScriptBinaryOperator.CompareNotEqual;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw NotMatching(leftSpan, leftExpr, rightSpan, rightExpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.Multiply:
|
||||||
|
case ScriptBinaryOperator.Divide:
|
||||||
|
case ScriptBinaryOperator.Power:
|
||||||
|
{
|
||||||
|
if (op != ScriptBinaryOperator.Power || rightExpr == null)
|
||||||
|
{
|
||||||
|
var simplifier = new KalkExpressionSimplifier(context);
|
||||||
|
var (valueMul, valueExpr) = simplifier.Canonical(new KalkBinaryExpression(leftValue, op, rightValue));
|
||||||
|
var valueBinExpr = valueExpr as KalkBinaryExpression;
|
||||||
|
|
||||||
|
result = KalkValue.AlmostEqual(valueMul, 1.0) && valueBinExpr == null ? valueExpr ?? (object)1.0 : valueExpr == null ? valueMul : (object)new KalkBinaryExpression(valueMul, ScriptBinaryOperator.Multiply, valueExpr)
|
||||||
|
{
|
||||||
|
OriginalExpression = new KalkBinaryExpression(leftValue, op, rightValue)
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(rightSpan, "Cannot use a unit as an exponent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.Add:
|
||||||
|
case ScriptBinaryOperator.Subtract:
|
||||||
|
|
||||||
|
if (leftExpr != null && rightExpr != null)
|
||||||
|
{
|
||||||
|
var leftSimplifier = new KalkExpressionSimplifier(context);
|
||||||
|
var (leftValueMultiplier, newLeftExpr) = leftSimplifier.Canonical(leftExpr);
|
||||||
|
|
||||||
|
var rightSimplifier = new KalkExpressionSimplifier(context);
|
||||||
|
var (rightValueMultiplier, newRightExpr) = rightSimplifier.Canonical(rightExpr);
|
||||||
|
|
||||||
|
if (!Equals(context, newLeftExpr, newRightExpr))
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(span, $"Cannot {(op == ScriptBinaryOperator.Add ? "add" : "subtract")} the expression. Units are not matching. The left expression with unit `{newLeftExpr}` is not matching the right expression with unit `{newRightExpr}`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new KalkBinaryExpression(ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier), ScriptBinaryOperator.Multiply, newLeftExpr)
|
||||||
|
{
|
||||||
|
OriginalExpression = new KalkBinaryExpression(leftValue, op, rightValue)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw NotMatching(leftSpan, leftExpr, rightSpan, rightExpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static ScriptRuntimeException NotMatching(SourceSpan leftSpan, KalkExpression leftExpr, SourceSpan rightSpan, KalkExpression rightExpr)
|
||||||
|
{
|
||||||
|
return new ScriptRuntimeException(leftExpr == null ? leftSpan : rightSpan, $"Missing unit for the {(leftExpr == null ? "left" : "right")} expression. It must match the unit of the other expression `{leftExpr ?? rightExpr}`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptUnaryOperator op, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkExpression ConvertTo(TemplateContext context, KalkExpression dst)
|
||||||
|
{
|
||||||
|
var src = this;
|
||||||
|
|
||||||
|
var destExpr = dst.OriginalExpression ?? dst;
|
||||||
|
|
||||||
|
var span = context.CurrentSpan;
|
||||||
|
if (!this.TryEvaluate(context, span, ScriptBinaryOperator.Divide, span, this, span, dst, out var result) || result is KalkExpression)
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(context.CurrentSpan, $"Cannot convert the expression {src} to the unit `{destExpr}`. Units are not matching.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = context.ToObject<double>(span, result);
|
||||||
|
return new KalkBinaryExpression(value, ScriptBinaryOperator.Multiply, dst.OriginalExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type ElementType => typeof(double);
|
||||||
|
|
||||||
|
public bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return transformType == typeof(double) || transformType == typeof(float);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit)
|
||||||
|
{
|
||||||
|
return visit(GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType)
|
||||||
|
{
|
||||||
|
return apply(GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
208
Kalk/Kalk.Core/Model/KalkExpressionSimplifier.cs
Normal file
208
Kalk/Kalk.Core/Model/KalkExpressionSimplifier.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
internal class KalkExpressionSimplifier
|
||||||
|
{
|
||||||
|
private readonly TemplateContext _context;
|
||||||
|
private object _value;
|
||||||
|
private List<KalkBinaryExpression> _powers;
|
||||||
|
|
||||||
|
public KalkExpressionSimplifier(TemplateContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (object, KalkExpression) Canonical(KalkExpression value)
|
||||||
|
{
|
||||||
|
Collect(value);
|
||||||
|
SquashPowers();
|
||||||
|
|
||||||
|
// Compute the final canonical value
|
||||||
|
KalkExpression leftValue = null;
|
||||||
|
if (_powers != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _powers.Count; i++)
|
||||||
|
{
|
||||||
|
var power = _powers[i];
|
||||||
|
var powerValue = _context.ToObject<double>(_context.CurrentSpan, power.Unit);
|
||||||
|
if (powerValue < 0)
|
||||||
|
{
|
||||||
|
powerValue = -powerValue;
|
||||||
|
power.Unit = powerValue;
|
||||||
|
object left = leftValue ?? (object)1.0;
|
||||||
|
leftValue = new KalkBinaryExpression(left, ScriptBinaryOperator.Divide, KalkValue.AlmostEqual(powerValue, 1.0) ? power.Value : power);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var nextMul = KalkValue.AlmostEqual(powerValue, 1.0) ? (KalkUnit) power.Value : (KalkExpression) power;
|
||||||
|
leftValue = leftValue == null ? nextMul : new KalkBinaryExpression(leftValue, ScriptBinaryOperator.Multiply, nextMul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (_value, leftValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SortPowerPerSymbol(KalkBinaryExpression left, KalkBinaryExpression right)
|
||||||
|
{
|
||||||
|
var leftSymbol = (KalkUnit) left.Value;
|
||||||
|
var rightSymbol = (KalkUnit) right.Value;
|
||||||
|
|
||||||
|
Debug.Assert(leftSymbol.Value == null);
|
||||||
|
Debug.Assert(rightSymbol.Value == null);
|
||||||
|
|
||||||
|
return string.Compare(leftSymbol.Symbol, rightSymbol.Symbol, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SortPowerPerPower(KalkBinaryExpression left, KalkBinaryExpression right)
|
||||||
|
{
|
||||||
|
var leftPowerValue = _context.ToObject<double>(_context.CurrentSpan, left.Unit);
|
||||||
|
var rightPowerValue = _context.ToObject<double>(_context.CurrentSpan, right.Unit);
|
||||||
|
|
||||||
|
var comparePower = rightPowerValue.CompareTo(leftPowerValue);
|
||||||
|
if (comparePower != 0) return comparePower;
|
||||||
|
|
||||||
|
// Then compare per symbol
|
||||||
|
return SortPowerPerSymbol(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SquashPowers()
|
||||||
|
{
|
||||||
|
if (_powers == null) return;
|
||||||
|
_powers.Sort(SortPowerPerSymbol);
|
||||||
|
|
||||||
|
// Compress the powers: a^x * a^y = a^(x+y)
|
||||||
|
KalkBinaryExpression previousPower = null;
|
||||||
|
for (var i = 0; i < _powers.Count; i++)
|
||||||
|
{
|
||||||
|
var power = _powers[i];
|
||||||
|
if (previousPower != null && ReferenceEquals(power.Value, previousPower.Value))
|
||||||
|
{
|
||||||
|
var powerResult = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Add, power.Unit, previousPower.Unit);
|
||||||
|
_powers[i - 1] = new KalkBinaryExpression(power.Value, ScriptBinaryOperator.Power, powerResult);
|
||||||
|
_powers.RemoveAt(i);
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPower = power;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any powers a^0.0
|
||||||
|
for (var i = 0; i < _powers.Count; i++)
|
||||||
|
{
|
||||||
|
var power = _powers[i];
|
||||||
|
var powerValue = _context.ToObject<double>(_context.CurrentSpan, power.Unit);
|
||||||
|
if (Math.Abs(powerValue) < (float.Epsilon * 4))
|
||||||
|
{
|
||||||
|
_powers.RemoveAt(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort power per Power first and symbol after
|
||||||
|
// From largest to smallest
|
||||||
|
_powers.Sort(SortPowerPerPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Collect(KalkExpressionSimplifier simplifier)
|
||||||
|
{
|
||||||
|
Collect(simplifier._value);
|
||||||
|
if (simplifier._powers != null)
|
||||||
|
{
|
||||||
|
foreach (var power in simplifier._powers)
|
||||||
|
{
|
||||||
|
Collect(power);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Collect(object value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
|
||||||
|
if (value is KalkUnit symbol)
|
||||||
|
{
|
||||||
|
if (symbol.Value != null)
|
||||||
|
{
|
||||||
|
Collect(symbol.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_powers == null) _powers = new List<KalkBinaryExpression>();
|
||||||
|
_powers.Add(new KalkBinaryExpression(symbol, ScriptBinaryOperator.Power, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value is KalkBinaryExpression binary)
|
||||||
|
{
|
||||||
|
if (binary.Operator == ScriptBinaryOperator.Multiply)
|
||||||
|
{
|
||||||
|
var leftSimplifier = new KalkExpressionSimplifier(_context);
|
||||||
|
leftSimplifier.Collect(binary.Value);
|
||||||
|
Collect(leftSimplifier);
|
||||||
|
|
||||||
|
var rightSimplifier = new KalkExpressionSimplifier(_context);
|
||||||
|
rightSimplifier.Collect(binary.Unit);
|
||||||
|
Collect(rightSimplifier);
|
||||||
|
}
|
||||||
|
else if (binary.Operator == ScriptBinaryOperator.Divide)
|
||||||
|
{
|
||||||
|
Collect(binary.Value);
|
||||||
|
if (binary.Unit is KalkExpression)
|
||||||
|
{
|
||||||
|
Collect(new KalkBinaryExpression(binary.Unit, ScriptBinaryOperator.Power, -1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Divide, 1, binary.Unit);
|
||||||
|
SquashValue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Assert(binary.Operator == ScriptBinaryOperator.Power);
|
||||||
|
if (_powers == null) _powers = new List<KalkBinaryExpression>();
|
||||||
|
|
||||||
|
// (a * b^2) ^ 5
|
||||||
|
// a ^ 5 * b ^ 10
|
||||||
|
var subSimplifier = new KalkExpressionSimplifier(_context);
|
||||||
|
subSimplifier.Collect(binary.Value);
|
||||||
|
|
||||||
|
var subValue = _context.ToObject<double>(_context.CurrentSpan, subSimplifier._value);
|
||||||
|
|
||||||
|
if (!KalkValue.AlmostEqual(subValue, 1.0))
|
||||||
|
{
|
||||||
|
var result = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Power, subSimplifier._value, binary.Unit);
|
||||||
|
SquashValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subSimplifier._powers != null)
|
||||||
|
{
|
||||||
|
foreach (var powerExpression in subSimplifier._powers)
|
||||||
|
{
|
||||||
|
var powerResult = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Multiply, powerExpression.Unit, binary.Unit);
|
||||||
|
var newPower = new KalkBinaryExpression(powerExpression.Value, ScriptBinaryOperator.Power, powerResult);
|
||||||
|
_powers.Add(newPower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SquashValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SquashValue(object value)
|
||||||
|
{
|
||||||
|
_value = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Multiply, _value, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Kalk/Kalk.Core/Model/KalkExpressionWithMembers.cs
Normal file
56
Kalk/Kalk.Core/Model/KalkExpressionWithMembers.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public abstract class KalkExpressionWithMembers : KalkExpression
|
||||||
|
{
|
||||||
|
protected abstract (string, Func<KalkExpressionWithMembers, object> getter)[] Members { get; }
|
||||||
|
|
||||||
|
public override int Count => Members.Length;
|
||||||
|
|
||||||
|
public override IEnumerable<string> GetMembers()
|
||||||
|
{
|
||||||
|
foreach (var memberPair in Members) yield return memberPair.Item1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Contains(string member)
|
||||||
|
{
|
||||||
|
foreach (var memberPair in Members)
|
||||||
|
if (memberPair.Item1 == member)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsReadOnly { get; set; }
|
||||||
|
|
||||||
|
public override bool TryGetValue(TemplateContext context, SourceSpan span, string member, out object value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
foreach (var memberPair in Members)
|
||||||
|
{
|
||||||
|
if (memberPair.Item1 == member)
|
||||||
|
{
|
||||||
|
value = memberPair.Item2(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite(string member) => false;
|
||||||
|
|
||||||
|
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly) => false;
|
||||||
|
|
||||||
|
public override bool Remove(string member) => false;
|
||||||
|
|
||||||
|
public override void SetReadOnly(string member, bool readOnly) => throw new NotSupportedException("A member of Unit is readonly by default and cannot be modified");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
258
Kalk/Kalk.Core/Model/KalkHalf.cs
Normal file
258
Kalk/Kalk.Core/Model/KalkHalf.cs
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a IEE-754 half-float 16-bit
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct KalkHalf : IScriptCustomType, IKalkSpannable, IFormattable, IScriptTransformable, IKalkDisplayable
|
||||||
|
{
|
||||||
|
private readonly Half _value;
|
||||||
|
|
||||||
|
public KalkHalf(float value)
|
||||||
|
{
|
||||||
|
_value = (Half) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkHalf(Half value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TypeName => "half";
|
||||||
|
|
||||||
|
internal Half Value => _value;
|
||||||
|
|
||||||
|
|
||||||
|
public static float[] ToFloatValues(KalkHalf[] values)
|
||||||
|
{
|
||||||
|
if (values == null) return null;
|
||||||
|
var floatValues = new float[values.Length];
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
floatValues[i] = (float)values[i];
|
||||||
|
}
|
||||||
|
return floatValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KalkHalf[] ToHalfValues(float[] values)
|
||||||
|
{
|
||||||
|
if (values == null) return null;
|
||||||
|
var halfValues = new KalkHalf[values.Length];
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
halfValues[i] = (KalkHalf)values[i];
|
||||||
|
}
|
||||||
|
return halfValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (leftValue is KalkHalf leftHalf && rightValue is KalkHalf rightHalf)
|
||||||
|
{
|
||||||
|
result = (KalkHalf)(float)ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, (float) leftHalf, rightSpan, (float)rightHalf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftValue is KalkHalf leftHalf1)
|
||||||
|
{
|
||||||
|
result = (KalkHalf)(float)ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, (float)leftHalf1, rightSpan, rightValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightValue is KalkHalf rightHalf1)
|
||||||
|
{
|
||||||
|
result = (KalkHalf)(float)ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValue, rightSpan, rightHalf1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptUnaryOperator op, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
result = ScriptUnaryExpression.Evaluate(context, span, op, (float)(KalkHalf)rightValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator KalkHalf(Half half)
|
||||||
|
{
|
||||||
|
return new KalkHalf(half);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static explicit operator KalkHalf(float value)
|
||||||
|
{
|
||||||
|
return new KalkHalf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static explicit operator float(KalkHalf half)
|
||||||
|
{
|
||||||
|
return (float)half._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Display(KalkEngine engine, KalkDisplayMode mode)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case KalkDisplayMode.Raw:
|
||||||
|
case KalkDisplayMode.Standard:
|
||||||
|
break;
|
||||||
|
case KalkDisplayMode.Developer:
|
||||||
|
DisplayDetailed(engine);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayDetailed(KalkEngine engine)
|
||||||
|
{
|
||||||
|
var u16 = Unsafe.As<Half, ushort>(ref Unsafe.AsRef(_value));
|
||||||
|
engine.WriteHighlightLine($" # IEEE 754 - half float - 16-bit");
|
||||||
|
engine.WriteHighlightLine($" #");
|
||||||
|
engine.WriteInt16(u16, true);
|
||||||
|
|
||||||
|
// Prints the 16 bits indices
|
||||||
|
engine.WriteHighlightLine($" # 15 8 0");
|
||||||
|
|
||||||
|
engine.WriteHighlightLine($" #");
|
||||||
|
engine.WriteHighlightLine($" # sign exponent |-- fraction -|");
|
||||||
|
// # 1 * 2 ^ (15 - 15) * 0b1.1000000000
|
||||||
|
|
||||||
|
var sign = (u16 >> 15);
|
||||||
|
var exponent = (u16 >> 10) & 0b11111;
|
||||||
|
var fraction = u16 & 0b1111111111;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(sign != 0 ? " -1" : " 1");
|
||||||
|
builder.Append(" * 2 ^ (");
|
||||||
|
builder.Append($"{(exponent == 0 ? 1 : exponent),2} - 15) * 0b{(exponent == 0 ? '0' : '1')}.");
|
||||||
|
for (int i = 9; i >= 0; i--)
|
||||||
|
{
|
||||||
|
builder.Append(((fraction >> i) & 1) == 0 ? '0' : '1');
|
||||||
|
}
|
||||||
|
engine.WriteHighlightLine($" = {builder}f");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||||
|
{
|
||||||
|
return _value.ToString(format, formatProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
return MemoryMarshal.CreateSpan(ref Unsafe.As<KalkHalf, byte>(ref Unsafe.AsRef(this)), Unsafe.SizeOf<KalkHalf>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryConvertTo(TemplateContext context, SourceSpan span, Type type, out object value)
|
||||||
|
{
|
||||||
|
if (type == typeof(KalkDoubleValue))
|
||||||
|
{
|
||||||
|
value = new KalkDoubleValue(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == typeof(KalkTrigDoubleValue))
|
||||||
|
{
|
||||||
|
value = new KalkTrigDoubleValue(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == typeof(object))
|
||||||
|
{
|
||||||
|
value = this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(int))
|
||||||
|
{
|
||||||
|
value = (int)(float)_value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(float))
|
||||||
|
{
|
||||||
|
value = (float)_value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(long))
|
||||||
|
{
|
||||||
|
value = (long)(float)_value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(double))
|
||||||
|
{
|
||||||
|
value = (double)(float)_value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (type == typeof(decimal))
|
||||||
|
{
|
||||||
|
value = (decimal) (float) _value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ScriptRuntimeException(span, $"Cannot convert {this} to an {type} as it has an imaginary part.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanTransform(Type transformType)
|
||||||
|
{
|
||||||
|
return transformType == typeof(float) || transformType == typeof(double) || transformType == typeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType)
|
||||||
|
{
|
||||||
|
if (destType == typeof(KalkHalf))
|
||||||
|
{
|
||||||
|
return new KalkHalf((float) apply((float) _value));
|
||||||
|
}
|
||||||
|
else if (destType == typeof(float))
|
||||||
|
{
|
||||||
|
return (KalkHalf)(float) apply((float) _value);
|
||||||
|
}
|
||||||
|
else if (destType == typeof(double))
|
||||||
|
{
|
||||||
|
return (KalkHalf)(double)apply((float)_value);
|
||||||
|
}
|
||||||
|
else if (destType == typeof(int))
|
||||||
|
{
|
||||||
|
return (KalkHalf)(int)apply((float)_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KalkHalf FromObject(KalkEngine engine, SourceSpan span, object value)
|
||||||
|
{
|
||||||
|
if (value == null) return new KalkHalf();
|
||||||
|
|
||||||
|
if (value is KalkHalf half) return half;
|
||||||
|
|
||||||
|
if (value is IConvertible convertible)
|
||||||
|
{
|
||||||
|
return (KalkHalf) convertible.ToSingle(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ScriptRuntimeException(span, $"The type {engine.GetTypeName(value)} is not convertible to half");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type ElementType => typeof(float);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Kalk/Kalk.Core/Model/KalkMatrixConstructor.cs
Normal file
103
Kalk/Kalk.Core/Model/KalkMatrixConstructor.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Kalk.Core.Helpers;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkMatrixConstructor<T> : KalkConstructor where T : unmanaged
|
||||||
|
{
|
||||||
|
public KalkMatrixConstructor(int row, int column)
|
||||||
|
{
|
||||||
|
RowCount = row;
|
||||||
|
ColumnCount = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int RowCount { get; }
|
||||||
|
|
||||||
|
public int ColumnCount { get; }
|
||||||
|
|
||||||
|
public KalkMatrix<T> Invoke(KalkEngine context, params object[] arguments)
|
||||||
|
{
|
||||||
|
var matrix = new KalkMatrix<T>(RowCount, ColumnCount);
|
||||||
|
if (arguments.Length == 0) return matrix;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int columnIndex = 0;
|
||||||
|
var maxTotal = ColumnCount * RowCount;
|
||||||
|
if (arguments.Length == 1)
|
||||||
|
{
|
||||||
|
var arg0 = arguments[0];
|
||||||
|
if (arg0 is KalkMatrix m)
|
||||||
|
{
|
||||||
|
var values = m.Values;
|
||||||
|
for (int j = 0; j < values.Length; j++)
|
||||||
|
{
|
||||||
|
matrix[index++] = context.ToObject<T>(0, values.GetValue(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg0 is IList list)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < list.Count; j++)
|
||||||
|
{
|
||||||
|
matrix[index++] = context.ToObject<T>(0, list[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var value = context.ToObject<T>(0, arg0);
|
||||||
|
for (int j = 0; j < ColumnCount * RowCount; j++)
|
||||||
|
{
|
||||||
|
matrix[index++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i < arguments.Length; i++)
|
||||||
|
{
|
||||||
|
var arg = arguments[i];
|
||||||
|
var argLength = arg is IList list ? list.Count : 1;
|
||||||
|
|
||||||
|
var length = columnIndex + argLength;
|
||||||
|
if (length > ColumnCount)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(i, $"Invalid number of arguments crossing a row. Expecting {ColumnCount} arguments instead of {length} for {matrix.TypeName}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == ColumnCount)
|
||||||
|
{
|
||||||
|
columnIndex = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
columnIndex += argLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg is IList listToAdd)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < argLength; j++)
|
||||||
|
{
|
||||||
|
matrix[index++] = context.ToObject<T>(i, listToAdd[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var value = context.ToObject<T>(i, arg);
|
||||||
|
matrix[index++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index != maxTotal)
|
||||||
|
{
|
||||||
|
throw new ScriptArgumentException(arguments.Length - 1, $"Invalid number of arguments. Expecting {maxTotal} arguments instead of {index} for {matrix.TypeName}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
472
Kalk/Kalk.Core/Model/KalkNativeBuffer.cs
Normal file
472
Kalk/Kalk.Core/Model/KalkNativeBuffer.cs
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Helpers;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("Count={Count}")]
|
||||||
|
public unsafe class KalkNativeBuffer : IList<byte>, IList, IScriptCustomBinaryOperation, IScriptCustomTypeInfo, IKalkSpannable, IFormattable
|
||||||
|
{
|
||||||
|
private readonly SharedBuffer _buffer;
|
||||||
|
private readonly int _offset;
|
||||||
|
private readonly int _length;
|
||||||
|
|
||||||
|
public KalkNativeBuffer(int size)
|
||||||
|
{
|
||||||
|
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size));
|
||||||
|
_buffer = new SharedBuffer(size);
|
||||||
|
_offset = 0;
|
||||||
|
_length = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KalkNativeBuffer(SharedBuffer buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
_buffer = buffer;
|
||||||
|
_offset = offset;
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TypeName => "buffer";
|
||||||
|
|
||||||
|
public IntPtr GetPointer()
|
||||||
|
{
|
||||||
|
return new IntPtr(_buffer.Buffer + _offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkNativeBuffer(IEnumerable<byte> buffer)
|
||||||
|
{
|
||||||
|
var array = new List<byte>(buffer).ToArray(); ;
|
||||||
|
_buffer = new SharedBuffer(array.Length);
|
||||||
|
array.CopyTo(_buffer.AsSpan());
|
||||||
|
_offset = 0;
|
||||||
|
_length = _buffer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkNativeBuffer(byte[] buffer)
|
||||||
|
{
|
||||||
|
_buffer = new SharedBuffer(buffer.Length);
|
||||||
|
buffer.CopyTo(_buffer.AsSpan());
|
||||||
|
_offset = 0;
|
||||||
|
_length = _buffer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KalkNativeBuffer AsBytes<T>(int byteCount, in T element)
|
||||||
|
{
|
||||||
|
var buffer = new KalkNativeBuffer(byteCount);
|
||||||
|
Unsafe.CopyBlockUnaligned(ref buffer.AsSpan()[0], ref Unsafe.As<T, byte>(ref Unsafe.AsRef(element)), (uint)byteCount);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkNativeBuffer Slice(int index)
|
||||||
|
{
|
||||||
|
AssertIndex(index);
|
||||||
|
return Slice(index, _length - index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkNativeBuffer Slice(int index, int length)
|
||||||
|
{
|
||||||
|
AssertIndex(index);
|
||||||
|
if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
|
||||||
|
if ((uint)(length + index) > (uint)_length) throw new ArgumentOutOfRangeException($"End of slice {index + length - 1} is out of range ({_length}).");
|
||||||
|
|
||||||
|
return new KalkNativeBuffer(_buffer, _offset + index, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<byte> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _length; i++)
|
||||||
|
{
|
||||||
|
yield return _buffer[_offset + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _length; i++)
|
||||||
|
{
|
||||||
|
yield return _buffer[_offset + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(byte item) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||||
|
|
||||||
|
int IList.Add(object? value) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
AsSpan().Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IList.Contains(object? value) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
int IList.IndexOf(object? value) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
void IList.Insert(int index, object? value) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||||
|
|
||||||
|
void IList.Remove(object? value) => throw new NotSupportedException("Cannot remove from a fixed buffer");
|
||||||
|
|
||||||
|
public bool Contains(byte item)
|
||||||
|
{
|
||||||
|
return IndexOf(item) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(byte[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
AsSpan().CopyTo(new Span<byte>(array, arrayIndex, array.Length - arrayIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(byte item) => throw new NotSupportedException("Cannot remove from a fixed buffer");
|
||||||
|
|
||||||
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _length; i++)
|
||||||
|
{
|
||||||
|
array.SetValue(index + i, _buffer[_offset + i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _length;
|
||||||
|
|
||||||
|
bool ICollection.IsSynchronized => false;
|
||||||
|
|
||||||
|
object ICollection.SyncRoot => null;
|
||||||
|
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
object? IList.this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
AssertIndex(index);
|
||||||
|
return _buffer[_offset + index];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
AssertIndex(index);
|
||||||
|
if (value is byte tValue)
|
||||||
|
{
|
||||||
|
this[_offset + index] = tValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this[_offset + index] = Convert.ToByte(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(byte item)
|
||||||
|
{
|
||||||
|
return AsSpan().IndexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, byte item) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||||
|
|
||||||
|
public void RemoveAt(int index) => throw new NotSupportedException("Cannot remove from a fixed buffer");
|
||||||
|
|
||||||
|
bool IList.IsFixedSize => true;
|
||||||
|
|
||||||
|
public byte this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
AssertIndex(index);
|
||||||
|
return _buffer[_offset + index];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
AssertIndex(index);
|
||||||
|
_buffer[_offset + index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertIndex(int index)
|
||||||
|
{
|
||||||
|
if ((uint)index >= (uint)_length) throw new ArgumentOutOfRangeException($"Index {index} is out of range ({_length}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
var leftArray = TryGetArray(leftValue);
|
||||||
|
var rightArray = TryGetArray(rightValue);
|
||||||
|
int intModifier = 0;
|
||||||
|
var intSpan = leftSpan;
|
||||||
|
|
||||||
|
var errorSpan = span;
|
||||||
|
string reason = null;
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.BinaryOr:
|
||||||
|
case ScriptBinaryOperator.BinaryAnd:
|
||||||
|
case ScriptBinaryOperator.CompareEqual:
|
||||||
|
case ScriptBinaryOperator.CompareNotEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLess:
|
||||||
|
case ScriptBinaryOperator.CompareGreater:
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.Multiply:
|
||||||
|
if (leftArray == null && rightArray == null || leftArray != null && rightArray != null)
|
||||||
|
{
|
||||||
|
reason = " Expecting only one array for the left or right argument.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intModifier = context.ToInt(span, leftArray == null ? leftValue : rightValue);
|
||||||
|
if (rightArray == null) intSpan = rightSpan;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.Add:
|
||||||
|
case ScriptBinaryOperator.Divide:
|
||||||
|
case ScriptBinaryOperator.DivideRound:
|
||||||
|
case ScriptBinaryOperator.Modulus:
|
||||||
|
if (leftArray == null)
|
||||||
|
{
|
||||||
|
errorSpan = leftSpan;
|
||||||
|
reason = " Expecting an array for the left argument.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intModifier = context.ToInt(span, rightValue);
|
||||||
|
intSpan = rightSpan;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.ShiftLeft:
|
||||||
|
case ScriptBinaryOperator.ShiftRight:
|
||||||
|
reason = " Cannot modify a fixed buffer.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reason = String.Empty;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intModifier < 0)
|
||||||
|
{
|
||||||
|
errorSpan = intSpan;
|
||||||
|
reason = $" Integer {intModifier} cannot be negative when multiplying";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason != null)
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(errorSpan, $"The operator `{op.ToText()}` is not supported between {leftValue?.GetType().ScriptPrettyName()} and {rightValue?.GetType().ScriptPrettyName()}.{reason}");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.BinaryOr:
|
||||||
|
result = new KalkNativeBuffer(leftArray.Union(rightArray));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.BinaryAnd:
|
||||||
|
result = new KalkNativeBuffer(leftArray.Intersect(rightArray));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.Add:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = Slice(intModifier);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(errorSpan, $"Cannot shift pointer array at index {intModifier}. Reason: {ex.Message}");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.CompareEqual:
|
||||||
|
case ScriptBinaryOperator.CompareNotEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLess:
|
||||||
|
case ScriptBinaryOperator.CompareGreater:
|
||||||
|
result = CompareTo(context, span, op, leftArray, rightArray);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.Multiply:
|
||||||
|
{
|
||||||
|
// array with integer
|
||||||
|
var array = leftArray ?? rightArray;
|
||||||
|
if (intModifier == 0)
|
||||||
|
{
|
||||||
|
result = new KalkNativeBuffer(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newArray = new List<byte>(array);
|
||||||
|
for (int i = 0; i < intModifier; i++)
|
||||||
|
{
|
||||||
|
newArray.AddRange(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new KalkNativeBuffer(newArray);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.Divide:
|
||||||
|
case ScriptBinaryOperator.DivideRound:
|
||||||
|
{
|
||||||
|
// array with integer
|
||||||
|
var array = leftArray ?? rightArray;
|
||||||
|
if (intModifier == 0) throw new ScriptRuntimeException(intSpan, "Cannot divide by 0");
|
||||||
|
|
||||||
|
var newLength = array.Count / intModifier;
|
||||||
|
var newArray = new List<byte>(newLength);
|
||||||
|
for (int i = 0; i < newLength; i++)
|
||||||
|
{
|
||||||
|
newArray.Add(array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new KalkNativeBuffer(newArray);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ScriptBinaryOperator.Modulus:
|
||||||
|
{
|
||||||
|
// array with integer
|
||||||
|
var array = leftArray ?? rightArray;
|
||||||
|
if (intModifier == 0) throw new ScriptRuntimeException(intSpan, "Cannot divide by 0");
|
||||||
|
|
||||||
|
var newArray = new List<byte>(array.Count);
|
||||||
|
for (int i = 0; i < array.Count; i++)
|
||||||
|
{
|
||||||
|
if ((i % intModifier) == 0)
|
||||||
|
{
|
||||||
|
newArray.Add(array[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new KalkNativeBuffer(newArray);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IList<byte> TryGetArray(object rightValue)
|
||||||
|
{
|
||||||
|
var rightArray = rightValue as IList<byte>;
|
||||||
|
return rightArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CompareTo(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, IList<byte> left, IList<byte> right)
|
||||||
|
{
|
||||||
|
// Compare the length first
|
||||||
|
var compare = left.Count.CompareTo(right.Count);
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case ScriptBinaryOperator.CompareEqual:
|
||||||
|
if (compare != 0) return false;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareNotEqual:
|
||||||
|
if (compare != 0) return true;
|
||||||
|
if (left.Count == 0) return false;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareLess:
|
||||||
|
if (compare < 0) return true;
|
||||||
|
if (compare > 0) return false;
|
||||||
|
if (left.Count == 0 && op == ScriptBinaryOperator.CompareLess) return false;
|
||||||
|
break;
|
||||||
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||||
|
case ScriptBinaryOperator.CompareGreater:
|
||||||
|
if (compare < 0) return false;
|
||||||
|
if (compare > 0) return true;
|
||||||
|
if (left.Count == 0 && op == ScriptBinaryOperator.CompareGreater) return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ScriptRuntimeException(span, $"The operator `{op.ToText()}` is not supported between {left?.GetType().ScriptPrettyName()} and {right?.GetType().ScriptPrettyName()}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we need to compare each element
|
||||||
|
for (int i = 0; i < left.Count; i++)
|
||||||
|
{
|
||||||
|
var result = (bool) ScriptBinaryExpression.Evaluate(context, span, op, left[i], right[i]);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<byte> AsSpan()
|
||||||
|
{
|
||||||
|
return new Span<byte>(_buffer.Buffer + _offset, _length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||||
|
{
|
||||||
|
var engine = formatProvider as KalkEngine;
|
||||||
|
if (engine != null)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
bool isSlice = _offset > 0 || _length != this._buffer.Length;
|
||||||
|
if (isSlice)
|
||||||
|
{
|
||||||
|
builder.Append("slice(");
|
||||||
|
}
|
||||||
|
builder.Append("bytebuffer(");
|
||||||
|
builder.Append(engine.ObjectToString(new ScriptRange(this)));
|
||||||
|
builder.Append(")");
|
||||||
|
if (isSlice)
|
||||||
|
{
|
||||||
|
builder.Append($", {_offset.ToString(CultureInfo.InvariantCulture)}");
|
||||||
|
if (_offset + _length < _buffer.Length)
|
||||||
|
{
|
||||||
|
builder.Append($", {_length}");
|
||||||
|
}
|
||||||
|
builder.Append(")");
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
return base.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SharedBuffer
|
||||||
|
{
|
||||||
|
private const int AlignmentInBytes = 64; // 512-bits
|
||||||
|
private readonly byte* _bufferUnaligned;
|
||||||
|
|
||||||
|
public SharedBuffer(int length)
|
||||||
|
{
|
||||||
|
// We allocate enough space before and after the buffer to manage overflow
|
||||||
|
var allocatedSize = length + AlignmentInBytes * 2;
|
||||||
|
_bufferUnaligned = (byte*)Marshal.AllocHGlobal(allocatedSize);
|
||||||
|
Unsafe.InitBlockUnaligned(_bufferUnaligned, 0, (uint)allocatedSize);
|
||||||
|
if (_bufferUnaligned == null) throw new OutOfMemoryException($"Cannot allocate a buffer of {length} bytes.");
|
||||||
|
Buffer = (byte*) ((long) (_bufferUnaligned + AlignmentInBytes - 1) & ~(long) (AlignmentInBytes - 1));
|
||||||
|
Length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
~SharedBuffer()
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal((IntPtr)_bufferUnaligned);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly byte* Buffer;
|
||||||
|
|
||||||
|
public readonly int Length;
|
||||||
|
|
||||||
|
public ref byte this[int index] => ref Buffer[index];
|
||||||
|
|
||||||
|
public Span<byte> AsSpan() => new Span<byte>(Buffer, Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Kalk/Kalk.Core/Model/KalkObject.cs
Normal file
22
Kalk/Kalk.Core/Model/KalkObject.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Parsing;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public abstract class KalkObject : IScriptObject
|
||||||
|
{
|
||||||
|
public abstract int Count { get; }
|
||||||
|
public abstract IEnumerable<string> GetMembers();
|
||||||
|
public abstract bool Contains(string member);
|
||||||
|
public abstract bool IsReadOnly { get; set; }
|
||||||
|
public abstract bool TryGetValue(TemplateContext context, SourceSpan span, string member, out object value);
|
||||||
|
public abstract bool CanWrite(string member);
|
||||||
|
public abstract bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly);
|
||||||
|
public abstract bool Remove(string member);
|
||||||
|
public abstract void SetReadOnly(string member, bool readOnly);
|
||||||
|
public abstract IScriptObject Clone(bool deep);
|
||||||
|
public abstract string TypeName { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Kalk/Kalk.Core/Model/KalkShortcut.cs
Normal file
83
Kalk/Kalk.Core/Model/KalkShortcut.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public sealed class KalkShortcut : ScriptObject
|
||||||
|
{
|
||||||
|
public KalkShortcut(string name, IEnumerable<KalkShortcutKeySequence> shortcuts, bool isUser)
|
||||||
|
{
|
||||||
|
if (shortcuts == null) throw new ArgumentNullException(nameof(shortcuts));
|
||||||
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
Keys = new ScriptArray(shortcuts) {IsReadOnly = true};
|
||||||
|
SetValue("name", Name, true);
|
||||||
|
SetValue("keys", Keys, true);
|
||||||
|
IsReadOnly = true;
|
||||||
|
IsUser = isUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public new ScriptArray Keys { get; }
|
||||||
|
|
||||||
|
public bool IsUser { get; }
|
||||||
|
|
||||||
|
public override string ToString(string format, IFormatProvider formatProvider)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append($"shortcut({Name}, ");
|
||||||
|
|
||||||
|
var mapExpressionToKeySequences = new Dictionary<ScriptExpression, List<KalkShortcutKeySequence>>();
|
||||||
|
|
||||||
|
for (var i = 0; i < Keys.Count; i++)
|
||||||
|
{
|
||||||
|
var shortcut = (KalkShortcutKeySequence)Keys[i];
|
||||||
|
if (!mapExpressionToKeySequences.TryGetValue(shortcut.Expression, out var list))
|
||||||
|
{
|
||||||
|
list = new List<KalkShortcutKeySequence>();
|
||||||
|
mapExpressionToKeySequences.Add(shortcut.Expression, list);
|
||||||
|
}
|
||||||
|
list.Add(shortcut);
|
||||||
|
}
|
||||||
|
|
||||||
|
int seqIndex = 0;
|
||||||
|
foreach (var exprToSequence in mapExpressionToKeySequences)
|
||||||
|
{
|
||||||
|
var expr = exprToSequence.Key;
|
||||||
|
var sequences = exprToSequence.Value;
|
||||||
|
if (seqIndex > 0) builder.Append(", ");
|
||||||
|
builder.Append('"');
|
||||||
|
for (var j = 0; j < sequences.Count; j++)
|
||||||
|
{
|
||||||
|
var sequence = sequences[j];
|
||||||
|
if (j > 0) builder.Append(", ");
|
||||||
|
builder.Append($"{string.Join(" ", sequence.Keys)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append($"\", {expr}");
|
||||||
|
seqIndex++;
|
||||||
|
}
|
||||||
|
builder.Append(")");
|
||||||
|
|
||||||
|
const int align = 60;
|
||||||
|
if (builder.Length < align)
|
||||||
|
{
|
||||||
|
builder.Append(new string(' ', align - builder.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(" # ");
|
||||||
|
for (var i = 0; i < Keys.Count; i++)
|
||||||
|
{
|
||||||
|
var shortcut = (KalkShortcutKeySequence)Keys[i];
|
||||||
|
if (i > 0) builder.Append(", ");
|
||||||
|
builder.Append($"{string.Join(" ", shortcut.Keys)} => {shortcut.Expression}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Kalk/Kalk.Core/Model/KalkShortcutKeySequence.cs
Normal file
60
Kalk/Kalk.Core/Model/KalkShortcutKeySequence.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public sealed class KalkShortcutKeySequence : ScriptObject
|
||||||
|
{
|
||||||
|
public KalkShortcutKeySequence(IEnumerable<KalkConsoleKey> keys, ScriptExpression expression)
|
||||||
|
{
|
||||||
|
if (keys == null) throw new ArgumentNullException(nameof(keys));
|
||||||
|
Keys = new List<KalkConsoleKey>(keys);
|
||||||
|
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
|
||||||
|
var readonlyKeys = new ScriptArray(Keys) {IsReadOnly = true};
|
||||||
|
SetValue("keys", readonlyKeys, true);
|
||||||
|
SetValue("expression", Expression.ToString(), true);
|
||||||
|
IsReadOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public new List<KalkConsoleKey> Keys { get; }
|
||||||
|
|
||||||
|
public ScriptExpression Expression { get; }
|
||||||
|
|
||||||
|
public static List<KalkShortcutKeySequence> Parse(ScriptExpression keys, ScriptExpression expression)
|
||||||
|
{
|
||||||
|
if (keys == null) throw new ArgumentNullException(nameof(keys));
|
||||||
|
|
||||||
|
var keyAsString = keys is ScriptLiteral literal && literal.Value is string strValue? strValue : keys.ToString();
|
||||||
|
|
||||||
|
|
||||||
|
var keySequencesAsText = keyAsString.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var keySequences = new List<KalkShortcutKeySequence>();
|
||||||
|
|
||||||
|
foreach (var keySequence in keySequencesAsText)
|
||||||
|
{
|
||||||
|
var keyList = keySequence.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var resultKeys = new List<KalkConsoleKey>();
|
||||||
|
|
||||||
|
foreach (var keyText in keyList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var key = KalkConsoleKey.Parse(keyText);
|
||||||
|
resultKeys.Add(key);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ScriptRuntimeException(keys.Span, $"Unable to parse key. Reason: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keySequences.Add(new KalkShortcutKeySequence(resultKeys, expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
return keySequences;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
Kalk/Kalk.Core/Model/KalkUnit.cs
Normal file
125
Kalk/Kalk.Core/Model/KalkUnit.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Scriban;
|
||||||
|
using Scriban.Runtime;
|
||||||
|
using Scriban.Syntax;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public class KalkUnit : KalkExpressionWithMembers, IScriptCustomFunction, IScriptCustomImplicitMultiplyPrecedence
|
||||||
|
{
|
||||||
|
private static readonly (string, Func<KalkExpressionWithMembers, object> getter)[] MemberDefs = {
|
||||||
|
("kind", unit => unit.TypeName),
|
||||||
|
("name", unit => ((KalkUnit)unit).Name),
|
||||||
|
("symbol", unit => ((KalkUnit)unit).Symbol),
|
||||||
|
("description", unit => ((KalkUnit)unit).Description),
|
||||||
|
("prefix", unit => ((KalkUnit)unit).Prefix),
|
||||||
|
("plural", unit => ((KalkUnit)unit).Plural),
|
||||||
|
("value", unit => ((KalkUnit)unit).Value),
|
||||||
|
};
|
||||||
|
|
||||||
|
public KalkUnit(string name)
|
||||||
|
{
|
||||||
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
Symbol = name;
|
||||||
|
Plural = name + "s";
|
||||||
|
Derived = new List<KalkUnit>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TypeName => "symbol definition";
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string Symbol { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public string Prefix { get; set; }
|
||||||
|
|
||||||
|
public bool IsUser { get; set; }
|
||||||
|
|
||||||
|
public string Plural { get; set; }
|
||||||
|
|
||||||
|
public KalkExpression Value { get; set; }
|
||||||
|
|
||||||
|
public List<KalkUnit> Derived { get; }
|
||||||
|
|
||||||
|
public KalkUnit Parent { get; set; }
|
||||||
|
|
||||||
|
public override object GetValue() => 1.0;
|
||||||
|
|
||||||
|
public override KalkUnit GetUnit() => this;
|
||||||
|
|
||||||
|
public override string ToString(string format, IFormatProvider formatProvider)
|
||||||
|
{
|
||||||
|
return Symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override (string, Func<KalkExpressionWithMembers, object> getter)[] Members => MemberDefs;
|
||||||
|
|
||||||
|
protected override bool EqualsImpl(TemplateContext context, KalkExpression right)
|
||||||
|
{
|
||||||
|
// If we reach here, that means that the symbols are necessarily not equal
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int RequiredParameterCount => 0;
|
||||||
|
|
||||||
|
public int ParameterCount => 0;
|
||||||
|
|
||||||
|
public ScriptVarParamKind VarParamKind => ScriptVarParamKind.None;
|
||||||
|
|
||||||
|
public Type ReturnType => typeof(object);
|
||||||
|
|
||||||
|
public ScriptParameterInfo GetParameterInfo(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("A Unit symbol does not support arguments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
if (!(callerContext.Parent is ScriptExpressionStatement))
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var engine = (KalkEngine) context;
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Length = 0;
|
||||||
|
builder.Append($"unit({Name}, \"{Description.Replace("\"", @"\""")}\", {Symbol}");
|
||||||
|
if (Value != null)
|
||||||
|
{
|
||||||
|
builder.Append($", {Value.OriginalExpression ?? Value}");
|
||||||
|
}
|
||||||
|
if (Prefix != null)
|
||||||
|
{
|
||||||
|
builder.Append($", prefix: \"{Prefix}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(")");
|
||||||
|
engine.WriteHighlightLine(builder.ToString());
|
||||||
|
|
||||||
|
if (Derived.Count > 0)
|
||||||
|
{
|
||||||
|
builder.Length = 0;
|
||||||
|
for (var i = 0; i < Derived.Count; i++)
|
||||||
|
{
|
||||||
|
var derived = Derived[i];
|
||||||
|
if (i > 0) builder.Append(", ");
|
||||||
|
builder.Append($"{derived.Name}/{derived.Symbol}");
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.WriteHighlightAligned(" - ", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||||
|
{
|
||||||
|
return new ValueTask<object>(Invoke(context, callerContext, arguments, blockStatement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
182
Kalk/Kalk.Core/Model/KalkUnitPrefix.cs
Normal file
182
Kalk/Kalk.Core/Model/KalkUnitPrefix.cs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Kalk.Core
|
||||||
|
{
|
||||||
|
public sealed class KalkUnitPrefix
|
||||||
|
{
|
||||||
|
private static readonly string[] PrefixNames = Enum.GetNames(typeof(KalkUnitPrefixCode));
|
||||||
|
private static readonly Array PrefixValues = Enum.GetValues(typeof(KalkUnitPrefixCode));
|
||||||
|
private static readonly KalkUnitPrefix[] DescriptorArray = new KalkUnitPrefix[PrefixNames.Length - 1];
|
||||||
|
private static readonly List<KalkUnitPrefix> Decimals = new List<KalkUnitPrefix>();
|
||||||
|
private static readonly List<KalkUnitPrefix> Binaries = new List<KalkUnitPrefix>();
|
||||||
|
private static readonly Dictionary<string, KalkUnitPrefix> MapAbbreviationToDescriptor = new Dictionary<string, KalkUnitPrefix>();
|
||||||
|
|
||||||
|
static KalkUnitPrefix()
|
||||||
|
{
|
||||||
|
for (int i = 1; i < PrefixValues.Length; i++)
|
||||||
|
{
|
||||||
|
var toMatch = (KalkUnitPrefixCode)PrefixValues.GetValue(i);
|
||||||
|
var descriptor = GetDescriptor(toMatch);
|
||||||
|
DescriptorArray[i - 1] = descriptor;
|
||||||
|
MapAbbreviationToDescriptor[descriptor.Abbreviation] = descriptor;
|
||||||
|
if (descriptor.Base == 10) Decimals.Add(descriptor);
|
||||||
|
else if (descriptor.Base == 2) Binaries.Add(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkUnitPrefix(KalkUnitPrefixCode code, int exponent)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
Name = code.ToString();
|
||||||
|
Abbreviation = Name[0].ToString(CultureInfo.InvariantCulture);
|
||||||
|
Name = Name.ToLowerInvariant();
|
||||||
|
Prefix = Abbreviation;
|
||||||
|
Base = 10;
|
||||||
|
Exponent = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkUnitPrefix(KalkUnitPrefixCode code, string abbreviation, int exponent) : this(code, abbreviation, abbreviation, 10, exponent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkUnitPrefix(KalkUnitPrefixCode code, string abbreviation, int @base, int exponent) : this(code, abbreviation, abbreviation, @base, exponent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkUnitPrefix(KalkUnitPrefixCode code, string abbreviation, string prefix, int @base, int exponent)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
Name = code.ToString().ToLowerInvariant();
|
||||||
|
Abbreviation = abbreviation;
|
||||||
|
Prefix = prefix;
|
||||||
|
Base = @base;
|
||||||
|
Exponent = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KalkUnitPrefixCode Code { get; }
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string Abbreviation { get; }
|
||||||
|
|
||||||
|
public string Prefix { get; }
|
||||||
|
|
||||||
|
public int Base { get; }
|
||||||
|
|
||||||
|
public int Exponent { get; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Name,10} - {Abbreviation,2} '{Prefix,2}' {Base,2}^{Exponent}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGet(string abbreviation, out KalkUnitPrefix descriptor)
|
||||||
|
{
|
||||||
|
if (abbreviation == null) throw new ArgumentNullException(nameof(abbreviation));
|
||||||
|
|
||||||
|
return MapAbbreviationToDescriptor.TryGetValue(abbreviation, out descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<KalkUnitPrefix> GetDecimals()
|
||||||
|
{
|
||||||
|
return Decimals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<KalkUnitPrefix> GetBinaries()
|
||||||
|
{
|
||||||
|
return Binaries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<KalkUnitPrefix> GetListFromCode(KalkUnitPrefixCode prefixCode)
|
||||||
|
{
|
||||||
|
var descriptors = new List<KalkUnitPrefix>();
|
||||||
|
for (int i = 0; i < PrefixValues.Length; i++)
|
||||||
|
{
|
||||||
|
var toMatch = (KalkUnitPrefixCode)PrefixValues.GetValue(i);
|
||||||
|
if ((toMatch & prefixCode) != 0)
|
||||||
|
{
|
||||||
|
descriptors.Add(DescriptorArray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KalkUnitPrefix GetDescriptor(KalkUnitPrefixCode singlePrefixCode)
|
||||||
|
{
|
||||||
|
switch (singlePrefixCode)
|
||||||
|
{
|
||||||
|
case KalkUnitPrefixCode.Zetta:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Zetta, 24);
|
||||||
|
case KalkUnitPrefixCode.Yotta:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Yotta, 21);
|
||||||
|
case KalkUnitPrefixCode.Exa:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Exa, 18);
|
||||||
|
case KalkUnitPrefixCode.Peta:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Peta, 15);
|
||||||
|
case KalkUnitPrefixCode.Tera:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Tera, 12);
|
||||||
|
case KalkUnitPrefixCode.Giga:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Giga, 9);
|
||||||
|
case KalkUnitPrefixCode.Mega:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Mega, 6);
|
||||||
|
case KalkUnitPrefixCode.kilo:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.kilo, 3);
|
||||||
|
case KalkUnitPrefixCode.hecto:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.hecto, 2);
|
||||||
|
case KalkUnitPrefixCode.deca:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.deca, "da", 10, 1);
|
||||||
|
case KalkUnitPrefixCode.deci:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.deci, -1);
|
||||||
|
case KalkUnitPrefixCode.centi:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.centi, -2);
|
||||||
|
case KalkUnitPrefixCode.milli:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.milli, -3);
|
||||||
|
case KalkUnitPrefixCode.micro:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.micro, "µ", -6);
|
||||||
|
case KalkUnitPrefixCode.nano:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.nano, -9);
|
||||||
|
case KalkUnitPrefixCode.pico:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.pico, -12);
|
||||||
|
case KalkUnitPrefixCode.femto:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.femto, -15);
|
||||||
|
case KalkUnitPrefixCode.atto:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.atto, -18);
|
||||||
|
case KalkUnitPrefixCode.zepto:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.zepto, -21);
|
||||||
|
case KalkUnitPrefixCode.yocto:
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.yocto, -24);
|
||||||
|
|
||||||
|
case KalkUnitPrefixCode.Kibi: // - Ki 2^10 kibibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Kibi, "Ki", 2, 10);
|
||||||
|
case KalkUnitPrefixCode.Mibi: // - Mi 2^20 mibibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Mibi, "Mi", 2, 20);
|
||||||
|
case KalkUnitPrefixCode.Gibi: // - Gi 2^30 gibibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Gibi, "Gi", 2, 30);
|
||||||
|
case KalkUnitPrefixCode.Tibi: // - Ti 2^40 tebibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Tibi, "Ti", 2, 40);
|
||||||
|
case KalkUnitPrefixCode.Pibi: // - Pi 2^50 pebibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Pibi, "Pi", 2, 50);
|
||||||
|
case KalkUnitPrefixCode.Eibi: // - Ei 2^60 exbibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Eibi, "Ei", 2, 60);
|
||||||
|
case KalkUnitPrefixCode.Zibi: // - Zi 2^70 zebibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Zibi, "Zi", 2, 70);
|
||||||
|
case KalkUnitPrefixCode.Yibi: // - Yi 2^80 yobibit
|
||||||
|
return new KalkUnitPrefix(KalkUnitPrefixCode.Yibi, "Yi", 2, 80);
|
||||||
|
|
||||||
|
//case KalkUnitPrefixCode.Kbit: // - Kb 2^10 kilobit
|
||||||
|
// return new KalkUnitPrefix(KalkUnitPrefixCode.Kbit, "Kb", "K", 2, 10);
|
||||||
|
//case KalkUnitPrefixCode.Mbit: // - Mb 2^20 megabit
|
||||||
|
// return new KalkUnitPrefix(KalkUnitPrefixCode.Mbit, "Mb", "M", 2, 20);
|
||||||
|
//case KalkUnitPrefixCode.Gbi: // - Gb 2^30 gigabit
|
||||||
|
// return new KalkUnitPrefix(KalkUnitPrefixCode.Gbi, "Gb", "G", 2, 30);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(singlePrefixCode), singlePrefixCode, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user