mirror of
https://github.com/Stone-Red-Code/ClipCMD.git
synced 2026-09-07 15:56:09 +02:00
Implement most features the old app had
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||||
xmlns:local="using:ClipCmd"
|
xmlns:local="using:ClipCmd"
|
||||||
x:Class="ClipCmd.App"
|
x:Class="ClipCmd.App"
|
||||||
RequestedThemeVariant="Default">
|
RequestedThemeVariant="Default">
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
|
<dialogHostAvalonia:DialogHostStyles />
|
||||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@@ -37,6 +37,7 @@ public partial class App : Application
|
|||||||
desktop.MainWindow.Closing += (sender, e) =>
|
desktop.MainWindow.Closing += (sender, e) =>
|
||||||
{
|
{
|
||||||
Settings.SaveSettings();
|
Settings.SaveSettings();
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
};
|
};
|
||||||
|
|
||||||
clipCmdCommandHandler.Start();
|
clipCmdCommandHandler.Start();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
|
||||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
|
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
|
||||||
|
<PackageReference Include="DialogHost.Avalonia" Version="0.7.7" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Scripting.Common" Version="4.8.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Scripting.Common" Version="4.8.0" />
|
||||||
@@ -30,4 +31,10 @@
|
|||||||
<PackageReference Include="SharpHook" Version="5.3.3" />
|
<PackageReference Include="SharpHook" Version="5.3.3" />
|
||||||
<PackageReference Include="Speckle.Material.Icons.Avalonia" Version="1.2.0" />
|
<PackageReference Include="Speckle.Material.Icons.Avalonia" Version="1.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Views\InputDialogView.axaml.cs">
|
||||||
|
<DependentUpon>InputDialogView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
// This file is used by Code Analysis to maintain SuppressMessage
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>")]
|
||||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||||
// attributes that are applied to this project.
|
// attributes that are applied to this project.
|
||||||
// Project-level suppressions either have no target or are given
|
// Project-level suppressions either have no target or are given
|
||||||
// a specific target and scoped to a namespace, type, member, etc.
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace ClipCmd.Models;
|
namespace ClipCmd.Models;
|
||||||
|
|
||||||
internal class ClipCmdCommand
|
public class ClipCmdCommand(string script)
|
||||||
{
|
{
|
||||||
public string Script { get; set; } = string.Empty;
|
public string Script { get; set; } = script;
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ public class Settings
|
|||||||
|
|
||||||
public bool AutoPaste { get; set; } = true;
|
public bool AutoPaste { get; set; } = true;
|
||||||
|
|
||||||
public int AutoTypeDelay { get; set; }
|
public int AutoTypeDelay { get; set; } = 100;
|
||||||
|
|
||||||
public ClipCmdMode Mode { get; set; }
|
public ClipCmdMode Mode { get; set; }
|
||||||
|
|
||||||
@@ -21,6 +21,8 @@ public class Settings
|
|||||||
|
|
||||||
public string Suffix { get; set; } = "";
|
public string Suffix { get; set; } = "";
|
||||||
|
|
||||||
|
public string CommandArgsSeperator { get; set; } = ":";
|
||||||
|
|
||||||
public static void SaveSettings()
|
public static void SaveSettings()
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(Configuration.ApplicationDataPath))
|
if (!Directory.Exists(Configuration.ApplicationDataPath))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input.Platform;
|
using Avalonia.Input.Platform;
|
||||||
|
|
||||||
using ClipCmd.Models;
|
using ClipCmd.Models;
|
||||||
@@ -7,8 +8,12 @@ using SharpHook;
|
|||||||
using SharpHook.Native;
|
using SharpHook.Native;
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ClipCmd.Utilities;
|
namespace ClipCmd.Utilities;
|
||||||
@@ -17,13 +22,15 @@ public class ClipCmdCommandHandler(Window window)
|
|||||||
{
|
{
|
||||||
private readonly IClipboard clipboard = window.Clipboard!;
|
private readonly IClipboard clipboard = window.Clipboard!;
|
||||||
private readonly EventSimulator simulator = new EventSimulator();
|
private readonly EventSimulator simulator = new EventSimulator();
|
||||||
private readonly ConcurrentDictionary<string, ClipCmdCommand> commands = new();
|
|
||||||
private string lastText = string.Empty;
|
private string lastText = string.Empty;
|
||||||
|
public AvaloniaDictionary<string, ClipCmdCommand> Commands { get; } = [];
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_ = new TaskFactory().StartNew(async () =>
|
_ = new TaskFactory().StartNew(async () =>
|
||||||
{
|
{
|
||||||
|
LoadCommands();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await Run();
|
await Run();
|
||||||
@@ -32,6 +39,33 @@ public class ClipCmdCommandHandler(Window window)
|
|||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearCommands()
|
||||||
|
{
|
||||||
|
Commands.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveCommands()
|
||||||
|
{
|
||||||
|
string json = JsonSerializer.Serialize(Commands);
|
||||||
|
File.WriteAllText(Configuration.CommandsPath, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCommands()
|
||||||
|
{
|
||||||
|
if (!File.Exists(Configuration.CommandsPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = File.ReadAllText(Configuration.CommandsPath);
|
||||||
|
Commands.Clear();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, ClipCmdCommand> command in JsonSerializer.Deserialize<ConcurrentDictionary<string, ClipCmdCommand>>(json) ?? new())
|
||||||
|
{
|
||||||
|
Commands[command.Key] = command.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Run()
|
private async Task Run()
|
||||||
{
|
{
|
||||||
string? text = await clipboard.GetTextAsync();
|
string? text = await clipboard.GetTextAsync();
|
||||||
@@ -48,14 +82,18 @@ public class ClipCmdCommandHandler(Window window)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string commandName = text[Settings.Current.Prefix.Length..^Settings.Current.Suffix.Length];
|
string input = text[Settings.Current.Prefix.Length..^Settings.Current.Suffix.Length].Replace(Settings.Current.CommandArgsSeperator + Settings.Current.CommandArgsSeperator, "\0");
|
||||||
|
string[] parts = input.Split(Settings.Current.CommandArgsSeperator);
|
||||||
|
string commandName = parts[0].Replace("\0", Settings.Current.CommandArgsSeperator);
|
||||||
|
string[] parameters = parts.Skip(1).Select(p => p.Replace("\0", Settings.Current.CommandArgsSeperator)).ToArray();
|
||||||
|
|
||||||
StringBuilder outText = new StringBuilder();
|
StringBuilder outText = new StringBuilder();
|
||||||
|
|
||||||
if (commands.TryGetValue(commandName, out ClipCmdCommand? command))
|
if (Commands.TryGetValue(commandName, out ClipCmdCommand? command))
|
||||||
{
|
{
|
||||||
PowerShell powerShell = PowerShell.Create();
|
PowerShell powerShell = PowerShell.Create();
|
||||||
_ = powerShell.AddScript(command.Script);
|
_ = powerShell.AddScript(command.Script);
|
||||||
|
_ = powerShell.AddParameters(parameters);
|
||||||
|
|
||||||
foreach (PSObject commandResult in await powerShell.InvokeAsync())
|
foreach (PSObject commandResult in await powerShell.InvokeAsync())
|
||||||
{
|
{
|
||||||
@@ -66,6 +104,10 @@ public class ClipCmdCommandHandler(Window window)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (commandName == "list")
|
||||||
|
{
|
||||||
|
_ = outText.AppendLine($"Commands: {string.Join(", ", Commands.Keys)}");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_ = outText.AppendLine("Command not found!");
|
_ = outText.AppendLine("Command not found!");
|
||||||
@@ -90,6 +132,12 @@ public class ClipCmdCommandHandler(Window window)
|
|||||||
{
|
{
|
||||||
foreach (char c in outText.ToString().TrimEnd())
|
foreach (char c in outText.ToString().TrimEnd())
|
||||||
{
|
{
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
_ = simulator.SimulateKeyPress(KeyCode.VcEnter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
_ = simulator.SimulateTextEntry(c.ToString());
|
_ = simulator.SimulateTextEntry(c.ToString());
|
||||||
await Task.Delay(Settings.Current.AutoTypeDelay);
|
await Task.Delay(Settings.Current.AutoTypeDelay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ internal static class Configuration
|
|||||||
|
|
||||||
public static string SettingsFilePath => Path.Combine(ApplicationDataPath, "settings.json");
|
public static string SettingsFilePath => Path.Combine(ApplicationDataPath, "settings.json");
|
||||||
public static string LogFilePath => Path.Combine(ApplicationDataPath, $"ClipCMD.log");
|
public static string LogFilePath => Path.Combine(ApplicationDataPath, $"ClipCMD.log");
|
||||||
|
|
||||||
|
public static string CommandsPath => Path.Combine(ApplicationDataPath, "commands.json");
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
|
||||||
|
using ClipCmd.Models;
|
||||||
|
using ClipCmd.Utilities;
|
||||||
|
using ClipCmd.Views;
|
||||||
|
|
||||||
|
using DialogHostAvalonia;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
public class ClipCmdCommandItemViewModel(string name, ClipCmdCommandHandler clipCmdCommandHandler)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
public ICommand RemoveCommand => ReactiveCommand.Create(Remove);
|
||||||
|
|
||||||
|
public ICommand RenameCommand => ReactiveCommand.Create(Rename);
|
||||||
|
|
||||||
|
public ICommand EditCommand => ReactiveCommand.Create(Edit);
|
||||||
|
|
||||||
|
private void Remove()
|
||||||
|
{
|
||||||
|
_ = clipCmdCommandHandler.Commands.Remove(Name);
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Rename()
|
||||||
|
{
|
||||||
|
string? newName = await DialogHost.Show(new InputDialogViewModel("Edit command name:", Name)) as string;
|
||||||
|
newName = newName?.Trim();
|
||||||
|
|
||||||
|
if (!clipCmdCommandHandler.Commands.Remove(Name, out ClipCmdCommand? clipCmdCommand))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(newName))
|
||||||
|
{
|
||||||
|
clipCmdCommandHandler.Commands.Add(Name, clipCmdCommand);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
string originalName = newName;
|
||||||
|
|
||||||
|
while (clipCmdCommandHandler.Commands.ContainsKey(newName))
|
||||||
|
{
|
||||||
|
newName = $"{originalName} ({++counter})";
|
||||||
|
}
|
||||||
|
|
||||||
|
clipCmdCommandHandler.Commands.Add(newName, clipCmdCommand);
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Edit()
|
||||||
|
{
|
||||||
|
Avalonia.Controls.Window? mainWindow = Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop ? desktop.MainWindow : null;
|
||||||
|
|
||||||
|
EditorWindow editorWindow = new EditorWindow
|
||||||
|
{
|
||||||
|
DataContext = new EditorViewModel(Name, clipCmdCommandHandler)
|
||||||
|
};
|
||||||
|
|
||||||
|
await editorWindow.ShowDialog(mainWindow!);
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
internal class EditorMessageViewModel(string message, IBrush color)
|
||||||
|
{
|
||||||
|
public string Message { get; } = message;
|
||||||
|
|
||||||
|
public IBrush Color { get; } = color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using ClipCmd.Utilities;
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
internal class EditorViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ClipCmdCommandHandler clipCmdCommandHandler;
|
||||||
|
|
||||||
|
public string Title => $"ClipCMD Editor - {Name}";
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string Script
|
||||||
|
{
|
||||||
|
get => clipCmdCommandHandler.Commands[Name].Script;
|
||||||
|
set => clipCmdCommandHandler.Commands[Name].Script = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<EditorMessageViewModel> Messages { get; } = [];
|
||||||
|
|
||||||
|
public EditorViewModel(string name, ClipCmdCommandHandler clipCmdCommandHandler)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
this.clipCmdCommandHandler = clipCmdCommandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for the designer
|
||||||
|
public EditorViewModel()
|
||||||
|
{
|
||||||
|
Name = "Test";
|
||||||
|
clipCmdCommandHandler = new ClipCmdCommandHandler(new());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using DialogHostAvalonia;
|
||||||
|
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
internal class InputDialogViewModel(string title, string defaultValue = "") : ViewModelBase
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = title;
|
||||||
|
|
||||||
|
public string Value { get; set; } = defaultValue;
|
||||||
|
|
||||||
|
public ICommand OkCommand => ReactiveCommand.Create(() => DialogHost.GetDialogSession(null)!.Close(Value));
|
||||||
|
|
||||||
|
public ICommand CancelCommand => ReactiveCommand.Create(() => DialogHost.GetDialogSession(null)!.Close(null));
|
||||||
|
}
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
using ClipCmd.Models;
|
using ClipCmd.Models;
|
||||||
using ClipCmd.Utilities;
|
using ClipCmd.Utilities;
|
||||||
|
|
||||||
|
using DialogHostAvalonia;
|
||||||
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace ClipCmd.ViewModels;
|
namespace ClipCmd.ViewModels;
|
||||||
|
|
||||||
public class MainViewModel : ViewModelBase
|
public class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly ClipCmdCommandHandler clipCmdCommandHandler;
|
||||||
|
|
||||||
|
public List<ClipCmdCommandItemViewModel> Commands => [.. clipCmdCommandHandler.Commands.Keys.Select(x => new ClipCmdCommandItemViewModel(x, clipCmdCommandHandler))];
|
||||||
|
|
||||||
public bool AutoTypeModeEnabled
|
public bool AutoTypeModeEnabled
|
||||||
{
|
{
|
||||||
get => Settings.Current.Mode == ClipCmdMode.AutoType;
|
get => Settings.Current.Mode == ClipCmdMode.AutoType;
|
||||||
@@ -31,12 +41,12 @@ public class MainViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int AutoTypeDelay
|
public int? AutoTypeDelay
|
||||||
{
|
{
|
||||||
get => Settings.Current.AutoTypeDelay;
|
get => Settings.Current.AutoTypeDelay;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Settings.Current.AutoTypeDelay = value;
|
Settings.Current.AutoTypeDelay = value ?? 1;
|
||||||
this.RaisePropertyChanged();
|
this.RaisePropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,11 +81,65 @@ public class MainViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainViewModel(ClipCmdCommandHandler clipCmdCommandHandler)
|
public string CommandArgsSeperator
|
||||||
{
|
{
|
||||||
|
get => Settings.Current.CommandArgsSeperator;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value = value.Replace(Settings.Current.CommandArgsSeperator, string.Empty);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
value = Settings.Current.CommandArgsSeperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > 1)
|
||||||
|
{
|
||||||
|
value = value[..1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.Current.CommandArgsSeperator = value;
|
||||||
|
this.RaisePropertyChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICommand AddCommand => ReactiveCommand.Create(Add);
|
||||||
|
|
||||||
|
public MainViewModel(ClipCmdCommandHandler clipCmdCommandHandler)
|
||||||
|
{
|
||||||
|
this.clipCmdCommandHandler = clipCmdCommandHandler;
|
||||||
|
|
||||||
|
clipCmdCommandHandler.Commands.CollectionChanged += (sender, e) =>
|
||||||
|
{
|
||||||
|
this.RaisePropertyChanged(nameof(Commands));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for the designer
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
|
clipCmdCommandHandler = new ClipCmdCommandHandler(new());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Add()
|
||||||
|
{
|
||||||
|
string? name = await DialogHost.Show(new InputDialogViewModel("Enter command name:")) as string;
|
||||||
|
name = name?.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
string originalName = name;
|
||||||
|
|
||||||
|
while (clipCmdCommandHandler.Commands.ContainsKey(name))
|
||||||
|
{
|
||||||
|
name = $"{originalName} ({++counter})";
|
||||||
|
}
|
||||||
|
|
||||||
|
clipCmdCommandHandler.Commands.Add(name, new ClipCmdCommand(@$"Write-Output ""{name} command"""));
|
||||||
|
clipCmdCommandHandler.SaveCommands();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
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:vm="clr-namespace:ClipCmd.ViewModels"
|
||||||
|
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="ClipCmd.Views.EditorWindow"
|
||||||
|
x:DataType="vm:EditorViewModel"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
Width="800" Height="450"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Loaded="Window_Loaded">
|
||||||
|
<Grid RowDefinitions="3* Auto 1*">
|
||||||
|
<AvaloniaEdit:TextEditor Name="Editor" ShowLineNumbers="True" FontFamily="Cascadia Code,Consolas,Menlo,Monospace" />
|
||||||
|
<Separator Grid.Row="1" Margin="0" />
|
||||||
|
<ScrollViewer Grid.Row="2">
|
||||||
|
<ItemsControl ItemsSource="{Binding Messages}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Label Content="{Binding Message}" Foreground="{Binding Color}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.TextMate;
|
||||||
|
|
||||||
|
using ClipCmd.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Management.Automation.Language;
|
||||||
|
|
||||||
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
|
public partial class EditorWindow : Window
|
||||||
|
{
|
||||||
|
public EditorWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
EditorViewModel editorViewModel = DataContext as EditorViewModel ?? throw new InvalidOperationException("DataContext is not EditorViewModel");
|
||||||
|
|
||||||
|
TextEditor textEditor = this.FindControl<TextEditor>("Editor")!;
|
||||||
|
|
||||||
|
textEditor.Text = editorViewModel.Script;
|
||||||
|
|
||||||
|
textEditor.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
editorViewModel.Script = textEditor.Text;
|
||||||
|
CheckScript(editorViewModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
RegistryOptions registryOptions = new RegistryOptions(ThemeName.DarkPlus);
|
||||||
|
TextMate.Installation _textMateInstallation = textEditor.InstallTextMate(registryOptions);
|
||||||
|
|
||||||
|
_textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".ps1").Id));
|
||||||
|
|
||||||
|
CheckScript(editorViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckScript(EditorViewModel editorViewModel)
|
||||||
|
{
|
||||||
|
_ = Parser.ParseInput(editorViewModel.Script, out _, out ParseError[] parseErrors);
|
||||||
|
|
||||||
|
editorViewModel.Messages.Clear();
|
||||||
|
|
||||||
|
if (parseErrors.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (ParseError parseError in parseErrors)
|
||||||
|
{
|
||||||
|
Debug.WriteLine(parseError.Message + " at line " + parseError.Extent.StartLineNumber + " and column " + parseError.Extent.StartColumnNumber);
|
||||||
|
editorViewModel.Messages.Add(new($"Error: {parseError.Message} at line {parseError.Extent.StartLineNumber} and column {parseError.Extent.StartColumnNumber}", Brushes.Red));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
editorViewModel.Messages.Add(new("No errors", Brushes.Green));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
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:vm="clr-namespace:ClipCmd.ViewModels"
|
||||||
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="ClipCmd.Views.InputDialogView"
|
||||||
|
x:DataType="vm:InputDialogViewModel"
|
||||||
|
MinWidth="300"
|
||||||
|
MinHeight="100"
|
||||||
|
Background="{DynamicResource SemiColorBackground0}">
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<Label Content="{Binding Title}" />
|
||||||
|
<TextBox VerticalContentAlignment="Center" Text="{Binding Value}" />
|
||||||
|
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Content="Ok" Width="80" HorizontalContentAlignment="Center" Command="{Binding OkCommand}" />
|
||||||
|
<Button Content="Cancel" Width="80" HorizontalContentAlignment="Center" Command="{Binding CancelCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
|
public partial class InputDialogView : UserControl
|
||||||
|
{
|
||||||
|
public InputDialogView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,10 @@
|
|||||||
xmlns:vm="clr-namespace:ClipCmd.ViewModels"
|
xmlns:vm="clr-namespace:ClipCmd.ViewModels"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||||
x:Class="ClipCmd.Views.MainView"
|
x:Class="ClipCmd.Views.MainView"
|
||||||
x:DataType="vm:MainViewModel"
|
x:DataType="vm:MainViewModel"
|
||||||
Loaded="UserControl_Loaded" Background="{DynamicResource SemiColorBackground0}">
|
Background="{DynamicResource SemiColorBackground0}">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
@@ -18,47 +19,85 @@
|
|||||||
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
|
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto Auto Auto Auto Auto Auto" ColumnDefinitions="Auto * Auto" Margin="10">
|
<dialogHost:DialogHost CloseOnClickAway="False" DisableOpeningAnimation="True" Background="{DynamicResource SemiColorBackground0}">
|
||||||
|
<dialogHost:DialogHost.DialogContent>
|
||||||
|
<!-- put your dialog content here-->
|
||||||
|
</dialogHost:DialogHost.DialogContent>
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto * Auto" VerticalAlignment="Stretch">
|
<Grid RowDefinitions="Auto Auto Auto *" ColumnDefinitions="Auto * Auto" Margin="10">
|
||||||
<TextBox Grid.Row="0" MaxLength="5" InnerLeftContent="Prefix:" Watermark="none" Text="{Binding Prefix}" />
|
|
||||||
<TextBox Grid.Row="2" MaxLength="5" InnerLeftContent="Suffix:" Watermark="none" Text="{Binding Suffix}" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Label Grid.Column="1" Content="ClipCMD" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="30" />
|
<Grid RowDefinitions="Auto * Auto * Auto" VerticalAlignment="Stretch">
|
||||||
|
<TextBox Grid.Row="0" MaxLength="5" InnerLeftContent="Prefix:" Watermark="none" Text="{Binding Prefix}" />
|
||||||
|
<TextBox Grid.Row="2" MaxLength="5" InnerLeftContent="Suffix:" Watermark="none" Text="{Binding Suffix}" />
|
||||||
|
<TextBox Grid.Row="4" MaxLength="2" InnerLeftContent="Args seperator:" Text="{Binding CommandArgsSeperator}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto Auto Auto Auto Auto" ColumnDefinitions="100 20 100" Grid.Column="2">
|
<Label Grid.Column="1" Content="ClipCMD" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="30" />
|
||||||
<Label Content="Mode" />
|
|
||||||
<RadioButton Grid.Row="1" VerticalAlignment="Center" Content="Clipboard" IsChecked="{Binding ClipboardModeEnabled}" />
|
|
||||||
<CheckBox Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Content="Auto Paste" IsEnabled="{Binding ClipboardModeEnabled}" IsChecked="{Binding AutoPaste}" />
|
|
||||||
<Separator Grid.Row="2" Grid.ColumnSpan="3" VerticalAlignment="Center" />
|
|
||||||
<RadioButton Grid.Row="3" VerticalAlignment="Center" Content="Auto Type" IsChecked="{Binding AutoTypeModeEnabled}" />
|
|
||||||
<NumericUpDown Grid.Row="3" Grid.Column="2" Increment="1" Minimum="0" Maximum="1000" VerticalAlignment="Center" IsEnabled="{Binding AutoTypeModeEnabled}" Value="{Binding AutoTypeDelay}" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Separator Grid.Row="1" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 10" />
|
<Grid RowDefinitions="Auto Auto Auto Auto Auto" ColumnDefinitions="100 20 100" Grid.Column="2">
|
||||||
|
<Label Content="Mode" />
|
||||||
|
<RadioButton Grid.Row="1" VerticalAlignment="Center" Content="Clipboard" IsChecked="{Binding ClipboardModeEnabled}" />
|
||||||
|
<CheckBox Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Content="Auto Paste" IsEnabled="{Binding ClipboardModeEnabled}" IsChecked="{Binding AutoPaste}" />
|
||||||
|
<Separator Grid.Row="2" Grid.ColumnSpan="3" VerticalAlignment="Center" />
|
||||||
|
<RadioButton Grid.Row="3" VerticalAlignment="Center" Content="Auto Type" IsChecked="{Binding AutoTypeModeEnabled}" />
|
||||||
|
<NumericUpDown Grid.Row="3" Grid.Column="2" Increment="1" Minimum="1" Maximum="1000" VerticalAlignment="Center" IsEnabled="{Binding AutoTypeModeEnabled}" Value="{Binding AutoTypeDelay, FallbackValue=1, TargetNullValue=1}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Button Grid.Column="0" Grid.Row="2">
|
<Separator Grid.Row="1" Grid.ColumnSpan="3" VerticalAlignment="Center" Margin="0 10" />
|
||||||
<StackPanel Spacing="4" Orientation="Horizontal">
|
|
||||||
<i:Icon Value="fa-plus" />
|
|
||||||
<TextBlock>Add Command</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Grid ColumnDefinitions="* 20 *" Grid.Column="2" Grid.Row="2">
|
<Button Grid.Column="0" Grid.Row="2" Command="{Binding AddCommand}">
|
||||||
<Button>
|
|
||||||
<StackPanel Spacing="4" Orientation="Horizontal">
|
<StackPanel Spacing="4" Orientation="Horizontal">
|
||||||
<i:Icon Value="fa-file-upload" />
|
<i:Icon Value="fa-plus" />
|
||||||
<TextBlock>Import</TextBlock>
|
<TextBlock>Add Command</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button Grid.Column="2">
|
<Grid ColumnDefinitions="* 20 *" Grid.Column="2" Grid.Row="2">
|
||||||
<StackPanel Orientation="Horizontal">
|
<Button>
|
||||||
<i:Icon Value="fa-file-download" />
|
<StackPanel Spacing="4" Orientation="Horizontal">
|
||||||
<TextBlock>Export</TextBlock>
|
<i:Icon Value="fa-file-upload" />
|
||||||
</StackPanel>
|
<TextBlock>Import</TextBlock>
|
||||||
</Button>
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Grid.Column="2">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<i:Icon Value="fa-file-download" />
|
||||||
|
<TextBlock>Export</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto" Margin="0 20 0 0" Theme="{DynamicResource StaticScrollViewer}">
|
||||||
|
<ItemsControl ItemsSource="{Binding Commands}">
|
||||||
|
<!--Use a StackPanel to display all the modules-->
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Spacing="10" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
|
||||||
|
<!--This defines the layout for each item (i.e. each module)-->
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border CornerRadius="3" Background="{DynamicResource SemiColorBackground1}" Padding="10 10">
|
||||||
|
<Grid ColumnDefinitions="* 45 10 45 10 45">
|
||||||
|
<Label Content="{Binding Name}" VerticalAlignment="Center" />
|
||||||
|
<Button Grid.Column="1" Command="{Binding RenameCommand}" HorizontalAlignment="Stretch">
|
||||||
|
<i:Icon Value="fa-edit" />
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="3" Command="{Binding EditCommand}" HorizontalAlignment="Stretch">
|
||||||
|
<i:Icon Value="fa-code" />
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="5" Command="{Binding RemoveCommand}" HorizontalAlignment="Stretch">
|
||||||
|
<i:Icon Value="fa-trash" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</dialogHost:DialogHost>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|
||||||
using AvaloniaEdit;
|
|
||||||
using AvaloniaEdit.TextMate;
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Management.Automation.Language;
|
|
||||||
|
|
||||||
using TextMateSharp.Grammars;
|
|
||||||
|
|
||||||
namespace ClipCmd.Views;
|
namespace ClipCmd.Views;
|
||||||
|
|
||||||
public partial class MainView : UserControl
|
public partial class MainView : UserControl
|
||||||
@@ -16,32 +8,4 @@ public partial class MainView : UserControl
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserControl_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
//First of all you need to have a reference for your TextEditor for it to be used inside AvaloniaEdit.TextMate project.
|
|
||||||
TextEditor? textEditor = this.FindControl<TextEditor>("TextEditor");
|
|
||||||
|
|
||||||
textEditor.TextChanged += (s, e) =>
|
|
||||||
{
|
|
||||||
_ = Parser.ParseInput(textEditor.Text, out _, out ParseError[] parseErrors);
|
|
||||||
|
|
||||||
if (parseErrors.Length > 0)
|
|
||||||
{
|
|
||||||
foreach (ParseError parseError in parseErrors)
|
|
||||||
{
|
|
||||||
Debug.WriteLine(parseError.Message + " at line " + parseError.Extent.StartLineNumber + " and column " + parseError.Extent.StartColumnNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
RegistryOptions registryOptions = new RegistryOptions(ThemeName.DarkPlus);
|
|
||||||
|
|
||||||
//Initial setup of TextMate.
|
|
||||||
TextMate.Installation _textMateInstallation = textEditor.InstallTextMate(registryOptions);
|
|
||||||
|
|
||||||
//Here we are getting the language by the extension and right after that we are initializing grammar with this language.
|
|
||||||
//And that's all 😀, you are ready to use AvaloniaEdit with syntax highlighting!
|
|
||||||
_textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".ps1").Id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,12 @@
|
|||||||
xmlns:vm="using:ClipCmd.ViewModels"
|
xmlns:vm="using:ClipCmd.ViewModels"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||||
xmlns:views="clr-namespace:ClipCmd.Views"
|
xmlns:views="clr-namespace:ClipCmd.Views"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="ClipCmd.Views.MainWindow"
|
x:Class="ClipCmd.Views.MainWindow"
|
||||||
Icon="/Assets/avalonia-logo.ico"
|
Icon="/Assets/avalonia-logo.ico"
|
||||||
Title="ClipCmd" Width="800" Height="450">
|
Title="ClipCmd" Width="800" Height="450">
|
||||||
|
|
||||||
<views:MainView />
|
<views:MainView />
|
||||||
</Window>
|
</Window>
|
||||||
Reference in New Issue
Block a user