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 //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/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 //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/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 //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/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 //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/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 //fondium.org/DESI$/AUG_Abteilung/Betriebstechnik/50_I&R/01_I&R Giesserei/100_Sicherung/E/99_Repositories/fsi/fsi.tools.git (push)
|
||||
FSI.Lib r:/fsi/fsi.lib.git (fetch)
|
||||
FSI.Lib r:/fsi/fsi.lib.git (push)
|
||||
NHotkey r:/fsi/nhotkey.git (fetch)
|
||||
NHotkey r:/fsi/nhotkey.git (push)
|
||||
NotifyIconWpf r:/fsi/notifyiconwpf.git (fetch)
|
||||
NotifyIconWpf r:/fsi/notifyiconwpf.git (push)
|
||||
RadialMenu r:/fsi/radialmenu.git (fetch)
|
||||
RadialMenu r:/fsi/radialmenu.git (push)
|
||||
origin r:/fsi/fsi.bt.tools.git (fetch)
|
||||
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.
|
||||
}
|
||||
|
||||
public string Name => "json";
|
||||
public static string Name => "json";
|
||||
|
||||
public bool CanRead => true;
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Config.Net.Stores
|
||||
|
||||
if (isIndex)
|
||||
{
|
||||
if (!(node is JsonArray ja)) return null;
|
||||
if (node is not JsonArray ja) return null;
|
||||
|
||||
if (partIndex < ja.Count)
|
||||
{
|
||||
@@ -132,7 +132,7 @@ namespace Config.Net.Stores
|
||||
|
||||
string js = _j.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
FileInfo file = new FileInfo(_pathName);
|
||||
FileInfo file = new(_pathName);
|
||||
|
||||
if (file is not null)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoboSharp", "RoboSharp\Robo
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Config.Net", "Config.Net\Config.Net.csproj", "{D5C7AFF9-2226-4CC4-87F6-6303DB60FEA0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoCompleteTextBox", "AutoCompleteTextBox\AutoCompleteTextBox.csproj", "{3162765C-B702-4927-8276-833E9046716D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
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.Wpf;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
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
|
||||
{
|
||||
@@ -23,11 +21,14 @@ namespace FSI.BT.Tools
|
||||
|
||||
|
||||
public void Application_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
{
|
||||
|
||||
Global.Log.Info("Anwendung wurde gestartet!");
|
||||
|
||||
ExtractEmbeddedZip("FSI.BT.Tools.ExtTools.kalk.zip", Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\ExtTools\\");
|
||||
|
||||
// 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>()
|
||||
.UseConfigStore(_store)
|
||||
.Build();
|
||||
@@ -40,9 +41,9 @@ namespace FSI.BT.Tools
|
||||
|
||||
HotkeyManager.Current.AddOrReplace("RadialMenu", RadialMenu, ShowRadialMenu);
|
||||
HotkeyManager.Current.AddOrReplace("TimeStampToClipboard", TimeStamp, TimeStampToClipboard);
|
||||
|
||||
|
||||
Global.FrmRadialMenu = new FrmRadialMenu();
|
||||
|
||||
|
||||
Global.WinCC = new Lib.Guis.SieTiaWinCCMsgMgt.ViewModel()
|
||||
{
|
||||
Data = Global.AppSettings.WinCC
|
||||
@@ -54,7 +55,6 @@ namespace FSI.BT.Tools
|
||||
Data = Global.AppSettings.IbaDirSync
|
||||
};
|
||||
Global.Iba.Init();
|
||||
|
||||
}
|
||||
|
||||
private void ShowRadialMenu(object sender, HotkeyEventArgs e)
|
||||
@@ -75,13 +75,13 @@ namespace FSI.BT.Tools
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void DeCrypt(ref IEnumerable<Settings.StringValue.IStringValueCrypt> values)
|
||||
private static void ExtractEmbeddedZip(string zipName, string destPath)
|
||||
{
|
||||
var valuesToDeCrypt = values.ToList();
|
||||
|
||||
foreach (var value in valuesToDeCrypt.ToList())
|
||||
value.ValueDeCrypt = Lib.DeEncryptString.DeEncrypt.DecryptString(value.Value, AppDomain.CurrentDomain.FriendlyName);
|
||||
|
||||
System.IO.Directory.CreateDirectory(destPath); // Erstellt alle fehlenden Verzeichnisse
|
||||
using Stream _pluginZipResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(zipName);
|
||||
using ZipArchive zip = new(_pluginZipResourceStream);
|
||||
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)
|
||||
@@ -94,5 +94,6 @@ namespace FSI.BT.Tools
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,26 +26,24 @@ namespace FSI.BT.Tools.Commands
|
||||
var cmds = Global.AppSettings.Cmds.ToList();
|
||||
ICmd selectedCmd = null;
|
||||
|
||||
// IEnumerable<Settings.Exe.IExe> files = new List<Settings.Exe.IExe>();
|
||||
IExe selectedFile;
|
||||
|
||||
|
||||
switch ((string)parameter)
|
||||
{
|
||||
|
||||
case "EplPrj":
|
||||
//selectedFile = GetApp(Global.AppSettings.Apps.Epl);
|
||||
//Lib.Guis.Prj.Mgt.FrmMain frmMainEplPrj = new()
|
||||
//{
|
||||
// ShowPdf = false,
|
||||
// CloseAtLostFocus = true,
|
||||
// WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
// Path = FSI.BT.Tools.Settings.AppSettings.GetFolderByName(Global.AppSettings.Folders, "EplPrj").path,
|
||||
// EplExe = selectedFile.ExePath,
|
||||
//};
|
||||
//frmMainEplPrj.Show();
|
||||
|
||||
case "Epl.Prj":
|
||||
Lib.Guis.Prj.Mgt.FrmMain frmMainEplPrj = new()
|
||||
{
|
||||
ShowPdf = false,
|
||||
CloseAtLostFocus = true,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
Path = FSI.BT.Tools.Settings.AppSettings.GetFolderByName(Global.AppSettings.Folders, "EplPrj").path,
|
||||
EplExe = GetExeByCmdName("Epl").ExePath,
|
||||
};
|
||||
frmMainEplPrj.Show();
|
||||
return;
|
||||
|
||||
case "EplPdf":
|
||||
case "Epl.Pdf":
|
||||
Lib.Guis.Prj.Mgt.FrmMain frmMainEplPdf = new()
|
||||
{
|
||||
ShowPdf = true,
|
||||
@@ -56,7 +54,7 @@ namespace FSI.BT.Tools.Commands
|
||||
frmMainEplPdf.Show();
|
||||
return;
|
||||
|
||||
case "EplPdfMgt":
|
||||
case "Epl.PdfMgt":
|
||||
Lib.Guis.Pdf.Mgt.FrmMain frmMainEplPdfMgt = new()
|
||||
{
|
||||
CloseAtLostFocus = true
|
||||
@@ -96,14 +94,22 @@ namespace FSI.BT.Tools.Commands
|
||||
};
|
||||
frmTxtToClipMain.Show();
|
||||
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:
|
||||
foreach (ICmd cmd in cmds)
|
||||
{
|
||||
if (String.Equals(parameter.ToString().ToLower(), cmd.Cmd.ToLower()))
|
||||
{
|
||||
if (String.Equals(parameter.ToString(), cmd.Cmd))
|
||||
selectedCmd = cmd;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -121,14 +127,14 @@ namespace FSI.BT.Tools.Commands
|
||||
ICmd selectedCmd = null;
|
||||
|
||||
switch ((string)parameter)
|
||||
{
|
||||
case "EplPrj":
|
||||
{
|
||||
case "Epl.Prj":
|
||||
return true;
|
||||
|
||||
case "EplPdf":
|
||||
case "Epl.Pdf":
|
||||
return true;
|
||||
|
||||
case "EplPdfMgt":
|
||||
case "Epl.PdfMgt":
|
||||
return Global.AdminRights;
|
||||
|
||||
case "DeEncrypt":
|
||||
@@ -143,16 +149,16 @@ namespace FSI.BT.Tools.Commands
|
||||
case "TxtToClip":
|
||||
return Global.AppSettings.TxtToClip != null;
|
||||
|
||||
case "Rdp.Mgt":
|
||||
return Global.AppSettings.Rdps != null;
|
||||
|
||||
default:
|
||||
foreach (ICmd cmd in cmds)
|
||||
{
|
||||
if (String.Equals(parameter.ToString().ToLower(), cmd.Cmd.ToLower()))
|
||||
{
|
||||
if (String.Equals(parameter.ToString(), cmd.Cmd))
|
||||
selectedCmd = cmd;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedCmd == null)
|
||||
@@ -160,26 +166,24 @@ namespace FSI.BT.Tools.Commands
|
||||
|
||||
foreach (var file in selectedCmd.Exe.ToList())
|
||||
{
|
||||
|
||||
if (File.Exists(Environment.ExpandEnvironmentVariables(file.ExePath.Trim())))
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (url != String.Empty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static void OpenExe(ICmd selectedCmd)
|
||||
{
|
||||
private static void OpenExe(ICmd selectedCmd)
|
||||
{
|
||||
IExe selectedFile = GetApp(selectedCmd.Exe);
|
||||
|
||||
if (selectedFile == null)
|
||||
@@ -196,8 +200,8 @@ namespace FSI.BT.Tools.Commands
|
||||
else
|
||||
{
|
||||
Process process = new();
|
||||
process.StartInfo.FileName = selectedFile.ExePath;
|
||||
process.StartInfo.WorkingDirectory = selectedFile.Path == null ? selectedFile.Path : Path.GetDirectoryName(selectedFile.ExePath);
|
||||
process.StartInfo.FileName = Environment.ExpandEnvironmentVariables(selectedFile.ExePath);
|
||||
process.StartInfo.WorkingDirectory = selectedFile.Path ?? Path.GetDirectoryName(Environment.ExpandEnvironmentVariables(selectedFile.ExePath));
|
||||
process.StartInfo.Arguments = selectedFile.Arguments;
|
||||
|
||||
try
|
||||
@@ -237,7 +241,20 @@ namespace FSI.BT.Tools.Commands
|
||||
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)
|
||||
{
|
||||
string FilePath = Path.GetDirectoryName(FullPath);
|
||||
@@ -260,7 +277,7 @@ namespace FSI.BT.Tools.Commands
|
||||
|
||||
private static IExe GetApp(IEnumerable<IExe> files)
|
||||
{
|
||||
if(files.ToList().Count == 0)
|
||||
if (files.ToList().Count == 0)
|
||||
return null;
|
||||
|
||||
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">
|
||||
|
||||
<PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
<ApplicationIcon>Icons\FondiumU.ico</ApplicationIcon>
|
||||
<AssemblyVersion>2.0</AssemblyVersion>
|
||||
<Version>2.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Icons\Folders.png" />
|
||||
<None Remove="Icons\Iba.jpg" />
|
||||
<None Remove="Icons\txt.png" />
|
||||
<Compile Remove="ExtTools\kalk\**" />
|
||||
<EmbeddedResource Remove="ExtTools\kalk\**" />
|
||||
<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>
|
||||
@@ -59,10 +72,24 @@
|
||||
</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>
|
||||
<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="..\FSI.Lib\FSI.Lib\FSI.Lib.csproj" />
|
||||
<ProjectReference Include="..\NHotkey\NHotkey.Wpf\NHotkey.Wpf.csproj" />
|
||||
@@ -73,7 +100,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Icons\1087815.png">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Icons\Admin.jpg" />
|
||||
<Resource Include="Icons\Apps.png" />
|
||||
@@ -109,6 +136,7 @@
|
||||
<Resource Include="Icons\TotalCmd.jfif" />
|
||||
<Resource Include="Icons\Vnc.png" />
|
||||
<Resource Include="Icons\VncAdrBook.png" />
|
||||
<Resource Include="Icons\VolUp.png" />
|
||||
<Resource Include="Icons\Vs.png" />
|
||||
<Resource Include="Icons\VsCode.png" />
|
||||
<Resource Include="Icons\TeXstudio.png" />
|
||||
@@ -116,14 +144,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="config.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="nlog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="config.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="nlog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties config_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
|
||||
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties config_1json__JsonSchema="" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<Window x:Class="FSI.BT.Tools.FrmRadialMenu"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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:RadialMenuProvider="clr-namespace:FSI.BT.Tools.RadialMenu"
|
||||
xmlns:commands="clr-namespace:FSI.BT.Tools.Commands"
|
||||
xmlns:wpfCtrl="http://wpfcontrols.com/"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStyle="None"
|
||||
ShowInTaskbar="False"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Deactivated="Window_Deactivated"
|
||||
Activated="Window_Activated"
|
||||
Loaded="Window_Loaded">
|
||||
|
||||
<Window.Resources>
|
||||
@@ -39,6 +43,7 @@
|
||||
FontSize="10" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<RadialMenu:RadialMenu IsOpen="{Binding IsOpenHome}">
|
||||
|
||||
<RadialMenu:RadialMenu.CentralItem>
|
||||
@@ -207,7 +212,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="EplPrj">
|
||||
CommandParameter="Epl.Prj">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="35"
|
||||
Height="35">
|
||||
@@ -223,7 +228,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="EplPdf">
|
||||
CommandParameter="Epl.Pdf">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="35"
|
||||
Height="35">
|
||||
@@ -239,7 +244,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="EplPdfMgt">
|
||||
CommandParameter="Epl.PdfMgt">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="35"
|
||||
Height="35">
|
||||
@@ -728,7 +733,7 @@
|
||||
</RadialMenu:RadialMenu.CentralItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL1Pls">
|
||||
CommandParameter="PL1.Pls">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -744,7 +749,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL1Lst">
|
||||
CommandParameter="PL1.Lst">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -775,7 +780,7 @@
|
||||
</RadialMenu:RadialMenu.CentralItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL2Alg">
|
||||
CommandParameter="PL2.Alg">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -792,7 +797,7 @@
|
||||
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL2Pls">
|
||||
CommandParameter="PL2.Pls">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -808,7 +813,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL2Als">
|
||||
CommandParameter="PL2.Als">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -824,7 +829,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL2Lst">
|
||||
CommandParameter="PL2.Lst">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -840,7 +845,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL2Nc">
|
||||
CommandParameter="PL2.Nc">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -856,7 +861,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL2Key">
|
||||
CommandParameter="PL2.Key">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -887,7 +892,7 @@
|
||||
</RadialMenu:RadialMenu.CentralItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL3Pls">
|
||||
CommandParameter="PL3.Pls">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -903,7 +908,7 @@
|
||||
</RadialMenu:RadialMenuItem>
|
||||
|
||||
<RadialMenu:RadialMenuItem Command="{commands:CmdCommand}"
|
||||
CommandParameter="PL3Lst">
|
||||
CommandParameter="PL3.Lst">
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<Rectangle Width="30"
|
||||
Height="30">
|
||||
@@ -1109,19 +1114,75 @@
|
||||
|
||||
</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 Width="300"
|
||||
<Grid Width="200"
|
||||
Height="50"
|
||||
Canvas.Bottom="0">
|
||||
Canvas.Bottom="0"
|
||||
Canvas.Left="50">
|
||||
<StackPanel Width="Auto"
|
||||
HorizontalAlignment="Stretch"
|
||||
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"
|
||||
KeyDown="tbCmd_KeyDown"
|
||||
TextChanged="tbCmd_TextChanged" />
|
||||
TextChanged="tbCmd_TextChanged" />-->
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -1,429 +1,128 @@
|
||||
using FSI.BT.Tools.Commands;
|
||||
using AutoCompleteTextBox.Editors;
|
||||
using FSI.BT.Tools.Commands;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace FSI.BT.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class FrmRadialMenu : Window, INotifyPropertyChanged
|
||||
public partial class FrmRadialMenu : Window//, INotifyPropertyChanged
|
||||
{
|
||||
private CmdCommand _cmd;
|
||||
|
||||
|
||||
public FrmRadialMenu()
|
||||
{
|
||||
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";
|
||||
_cmd = new ();
|
||||
_cmd = new();
|
||||
}
|
||||
|
||||
#region Home
|
||||
private bool _isOpenHome = true;
|
||||
public bool IsOpenHome
|
||||
private void Window_Activated(object sender, EventArgs e)
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isOpenHome;
|
||||
}
|
||||
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));
|
||||
tbCmd.Focus();
|
||||
|
||||
ChangeBtnIcon();
|
||||
}
|
||||
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
tbCmd.Text = String.Empty;
|
||||
tbCmd.Focus();
|
||||
Visibility = Visibility.Hidden;
|
||||
|
||||
IsOpenHome = true;
|
||||
IsOpenEpl =
|
||||
IsOpenTools =
|
||||
IsOpenSie =
|
||||
IsOpenLinks =
|
||||
IsOpenApps =
|
||||
IsOpenPlantLinksPl1 =
|
||||
IsOpenPlantLinksPl2 =
|
||||
IsOpenPlantLinksPl3 =
|
||||
IsOpenAppsVncRdp =
|
||||
IsOpenPlantLinks = false;
|
||||
tbCmd.Focus();
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
tbCmd.Focus();
|
||||
ChangeBtnIcon();
|
||||
|
||||
tbCmd.Focus();
|
||||
}
|
||||
|
||||
private void tbCmd_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && _cmd.CanExecute(((TextBox)sender).Text))
|
||||
{
|
||||
_cmd.Execute(((TextBox)sender).Text);
|
||||
}
|
||||
{
|
||||
if (e.Key == Key.Enter && _cmd.CanExecute(tbCmd.Text))
|
||||
{
|
||||
_cmd.Execute(tbCmd.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void tbCmd_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_cmd.CanExecute(((TextBox)sender).Text))
|
||||
((TextBox)sender).Background = new SolidColorBrush(Colors.Green);
|
||||
((TextBox)sender).Background = new SolidColorBrush(Colors.Green);
|
||||
else
|
||||
((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<TxtToClip.ITxtToClip> TxtToClip { get; }
|
||||
IEnumerable<RdpMgt.IRdp> Rdps { get; }
|
||||
|
||||
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": [
|
||||
"http://10.10.200.2/SKPL1Web/index.aspx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl1Lst",
|
||||
"Cmd": "Pl1.Lst",
|
||||
"Urls": [
|
||||
"http://desiaugetwf.fondium.org/web/Seiten/Leistungsdaten_FuG.aspx?Fkt=PL1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl2Als",
|
||||
"Cmd": "Pl2.Als",
|
||||
"Urls": [
|
||||
"http://10.10.213.234:84/emb_1/index.html"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl2Pls",
|
||||
"Cmd": "Pl2.Pls",
|
||||
"Urls": [
|
||||
"http://10.10.213.4/SKPL2Web/index.aspx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl2Lst",
|
||||
"Cmd": "Pl2.Lst",
|
||||
"Urls": [
|
||||
"http://desiaugetwf/web/Seiten/Leistungsdaten_PL2.aspx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl2Nc",
|
||||
"Cmd": "Pl2.Nc",
|
||||
"Urls": [
|
||||
"http://10.10.213.4/SKPL2Web/Seiten/Taktzeiten_PopUp.aspx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl2Key",
|
||||
"Cmd": "Pl2.Key",
|
||||
"Urls": [
|
||||
"http://10.10.213.4/skkeymanager-pl2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Cmd": "Pl2Alg",
|
||||
"Cmd": "Pl2.Alg",
|
||||
"Urls": [
|
||||
"http://10.10.213.4/SKPL2Web/index.aspx",
|
||||
"http://10.10.213.234:84/emb_1/index.html",
|
||||
@@ -355,6 +355,34 @@
|
||||
"Urls": [
|
||||
"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": [
|
||||
@@ -424,6 +452,20 @@
|
||||
"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": {
|
||||
"AutoStart": false,
|
||||
"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>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
<AssemblyVersion>3.0</AssemblyVersion>
|
||||
</PropertyGroup>
|
||||
@@ -38,6 +39,7 @@
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageReference Include="TextCopy" Version="6.2.1" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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);
|
||||
|
||||
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 Task task = null;
|
||||
|
||||
private BackgroundWorker backgroundWorker;
|
||||
private readonly BackgroundWorker backgroundWorker;
|
||||
private ICommand _cmdStart;
|
||||
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