Sicherung

This commit is contained in:
Maier Stephan SI
2023-01-20 16:09:00 +01:00
parent e5257d8413
commit b684704bf8
139 changed files with 95678 additions and 499 deletions

View File

@@ -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)

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections;
namespace AutoCompleteTextBox.Editors
{
public interface ISuggestionProvider
{
#region Public Methods
IEnumerable GetSuggestions(string filter);
#endregion Public Methods
}
}

View 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
}
}

View 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
}
}

View 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>

View File

@@ -0,0 +1,8 @@
namespace AutoCompleteTextBox
{
public enum IconPlacement
{
Left,
Right
}
}

View 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")]

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

View 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>

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

View 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>

View 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>

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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
}
}
}
}

View File

@@ -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];

Binary file not shown.

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,
};
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

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

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

View File

@@ -0,0 +1,6 @@
namespace FSI.BT.Tools
{
internal class ResourceManager
{
}
}

View File

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

View 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 { }
}
}

View File

@@ -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,

View 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
}

View File

@@ -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" />

View 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>

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

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

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

View File

@@ -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;

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

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

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

View 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,
}
}

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

View 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 0x400x5F: (ASCII @AZ[\]^_)
var c = item.Value;
if (_escapeState >= EscapeState.EscapeCsiParameterBytes)
{
// CSI: ESC [ followed by
// - by any number (including none) of "parameter bytes", char in the 0x300x3F: (ASCII 09:;<=>?)
// - then by any number of "intermediate bytes" in the range 0x200x2F (ASCII space and !"#$%&'()*+,-./),
// - then finally by a single "final byte" in the range 0x400x7E (ASCII @AZ[\]^_`az{|}~)
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();
}
}
}
}
}

View 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++;
}
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<Target Name="_CheckForGenerateAppxPackageOnBuild" />
</Project>

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

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

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

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

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

View 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>

View 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>

View 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

View 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,
}
}

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

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

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

View File

@@ -0,0 +1,11 @@
namespace Kalk.Core
{
public enum KalkDisplayMode
{
Raw,
Standard,
Developer
}
}

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

View 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.");
}
}
}

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

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

View File

@@ -0,0 +1,12 @@
using System;
namespace Kalk.Core
{
public class KalkSettings
{
public static void Initialize()
{
var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
}
}
}

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

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

View File

@@ -0,0 +1,7 @@
namespace Kalk.Core
{
public interface IKalkDisplayable
{
void Display(KalkEngine engine, KalkDisplayMode mode);
}
}

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

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

View 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.");
}
}
}

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

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

View 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
{
}
}

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

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

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

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

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

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

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

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

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

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

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

View 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