Skip to content
This repository has been archived by the owner on Aug 6, 2024. It is now read-only.

Add some important hotkeys #23

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion TAS.Avalonia/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public override void Initialize() {

public override void OnFrameworkInitializationCompleted() {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() };
desktop.MainWindow = new MainWindow();
desktop.MainWindow.DataContext = new MainWindowViewModel((MainWindow)desktop.MainWindow);
}

base.OnFrameworkInitializationCompleted();
Expand Down
5 changes: 4 additions & 1 deletion TAS.Avalonia/Communication/StudioCommunicationServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ namespace TAS.Avalonia.Communication;
public class StudioCommunicationServer : StudioCommunicationBase {
public event Action<StudioInfo> StateUpdated;
public event Action<Dictionary<HotkeyID, List<Keys>>> BindingsUpdated;
public event Action<Dictionary<int, string>> LinesUpdated;

protected virtual void OnStateUpdated(StudioInfo obj) => StateUpdated?.Invoke(obj);
protected virtual void OnBindingsUpdated(Dictionary<HotkeyID, List<Keys>> obj) => BindingsUpdated?.Invoke(obj);
protected virtual void OnLinesUpdated(Dictionary<int, string> lines) => LinesUpdated?.Invoke(lines);

private string _returnData;

Expand Down Expand Up @@ -99,7 +101,8 @@ private void ProcessVersionInfo(byte[] data) {
}

private void ProcessUpdateLines(byte[] data) {
// Dictionary<int, string> updateLines = BinaryFormatterHelper.FromByteArray<Dictionary<int, string>>(data);
Dictionary<int, string> updateLines = BinaryFormatterHelper.FromByteArray<Dictionary<int, string>>(data);
OnLinesUpdated(updateLines);
// CommunicationWrapper.UpdateLines(updateLines);
}

Expand Down
7 changes: 4 additions & 3 deletions TAS.Avalonia/Controls/EditorControl.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ public EditorControl() {
};

int prevLine = 0;
(Application.Current as App).CelesteService.Server.StateUpdated += state => {
if (state.CurrentLine == prevLine) return;
(Application.Current as App)!.CelesteService.Server.StateUpdated += state => {
if (state.CurrentLine == -1 || state.CurrentLine == prevLine) return;
prevLine = state.CurrentLine;

Dispatcher.UIThread.InvokeAsync(() =>
{
const int LinesBelow = 3;
Expand Down Expand Up @@ -99,7 +101,6 @@ public EditorControl() {
view.MakeVisible(new Rect(0, lineTop, 0, lineBottom - lineTop));
}
});
prevLine = state.CurrentLine;
};
}

Expand Down
26 changes: 26 additions & 0 deletions TAS.Avalonia/Editing/TASCaretNavigationCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ internal static class TASCaretNavigationCommandHandler {
private static readonly List<RoutedCommandBinding> CommandBindings = new List<RoutedCommandBinding>();
private static readonly List<KeyBinding> KeyBindings = new List<KeyBinding>();

internal static RoutedCommand SelectBlock { get; } = new(nameof(SelectBlock), new KeyGesture(Key.W, TASInputHandler.PlatformCommandKey));

public static TextAreaInputHandler Create(TextArea textArea) {
var areaInputHandler = new TextAreaInputHandler(textArea);
areaInputHandler.CommandBindings.AddRange(CommandBindings);
Expand Down Expand Up @@ -73,6 +75,7 @@ static TASCaretNavigationCommandHandler() {
AddBinding(RectangleSelection.BoxSelectToLineEnd, KeyModifiers.Alt | keymap.SelectionModifiers, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd));

AddBinding(ApplicationCommands.SelectAll, OnSelectAll);
AddBinding(SelectBlock, OnSelectBlock);

foreach (KeyGesture gesture in keymap.MoveCursorToTheStartOfLine) {
AddBinding(EditingCommands.MoveToLineStart, gesture, OnMoveCaret(CaretMovementType.LineStart));
Expand Down Expand Up @@ -115,6 +118,29 @@ private static void OnSelectAll(object target, ExecutedRoutedEventArgs args) {
textArea.Selection = Selection.Create(textArea, 0, textArea.Document.TextLength);
}

private static void OnSelectBlock(object target, ExecutedRoutedEventArgs args) {
TextArea textArea = GetTextArea(target);
if (textArea?.Document == null) return;
args.Handled = true;

// Search first empty line above/below caret
int above = textArea.Caret.Line;
DocumentLine aboveLine = null;
while (above >= 1 && !string.IsNullOrWhiteSpace(textArea.Document.GetText(aboveLine = textArea.Document.GetLineByNumber(above)))) {
above--;
}
int below = textArea.Caret.Line;
DocumentLine belowLine = null;
while (below <= textArea.Document.LineCount && !string.IsNullOrWhiteSpace(textArea.Document.GetText(belowLine = textArea.Document.GetLineByNumber(below)))) {
below++;
}

if (aboveLine == null || belowLine == null) return;

textArea.Caret.Offset = aboveLine.Offset + 1;
textArea.Selection = Selection.Create(textArea, aboveLine.Offset + 1, belowLine.EndOffset - 1);
}

private static TextArea GetTextArea(object target) => target as TextArea;

private static EventHandler<ExecutedRoutedEventArgs> OnMoveCaret(CaretMovementType direction) {
Expand Down
113 changes: 112 additions & 1 deletion TAS.Avalonia/Editing/TASEditingCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ internal class TASEditingCommandHandler {
private static readonly List<RoutedCommandBinding> CommandBindings = new List<RoutedCommandBinding>();
private static readonly List<KeyBinding> KeyBindings = new List<KeyBinding>();

internal static RoutedCommand DeleteLine { get; } = new(nameof(DeleteLine), new KeyGesture(Key.Y, TASInputHandler.PlatformCommandKey));

internal static RoutedCommand ToggleCommentInputs { get; } = new(nameof(ToggleCommentInputs), new KeyGesture(Key.K, TASInputHandler.PlatformCommandKey));
internal static RoutedCommand ToggleCommentText { get; } = new(nameof(ToggleCommentText), new KeyGesture(Key.K, TASInputHandler.PlatformCommandKey | KeyModifiers.Shift));

internal static RoutedCommand InsertRoomName { get; } = new(nameof(InsertRoomName), new KeyGesture(Key.R, TASInputHandler.PlatformCommandKey));
internal static RoutedCommand InsertTime { get; } = new(nameof(InsertRoomName), new KeyGesture(Key.T, TASInputHandler.PlatformCommandKey));
internal static RoutedCommand InsertCommand { get; } = new(nameof(InsertCommand));

public static TextAreaInputHandler Create(TextArea textArea) {
var areaInputHandler = new TextAreaInputHandler(textArea);
areaInputHandler.CommandBindings.AddRange(CommandBindings);
Expand All @@ -33,6 +42,7 @@ private static void AddBinding(RoutedCommand command, EventHandler<ExecutedRoute
}

static TASEditingCommandHandler() {
// Editing commands
AddBinding(EditingCommands.Delete, KeyModifiers.None, Key.Delete, OnDelete(CaretMovementType.CharRight));
AddBinding(EditingCommands.DeleteNextWord, KeyModifiers.Control, Key.Delete, OnDelete(CaretMovementType.WordRight));
AddBinding(EditingCommands.Backspace, KeyModifiers.None, Key.Back, OnDelete(CaretMovementType.Backspace));
Expand All @@ -46,7 +56,7 @@ static TASEditingCommandHandler() {
AddBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy);
AddBinding(ApplicationCommands.Paste, OnPaste, CanPaste);
AddBinding(AvaloniaEditCommands.ToggleOverstrike, OnToggleOverstrike);
AddBinding(AvaloniaEditCommands.DeleteLine, OnDeleteLine);
AddBinding(DeleteLine, OnDeleteLine);
AddBinding(AvaloniaEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace);
AddBinding(AvaloniaEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace);
AddBinding(AvaloniaEditCommands.ConvertToUppercase, OnConvertToUpperCase);
Expand All @@ -58,8 +68,109 @@ static TASEditingCommandHandler() {
AddBinding(AvaloniaEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces);
AddBinding(AvaloniaEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs);
AddBinding(AvaloniaEditCommands.IndentSelection, OnIndentSelection);

// TAS specific commands
AddBinding(ToggleCommentInputs, OnToggleCommentInputs);
AddBinding(ToggleCommentText, OnToggleCommentText);
AddBinding(InsertRoomName, OnInsertTextAbove(static _ => $"#lvl_{(Application.Current as App)!.CelesteService.LevelName}"));
AddBinding(InsertTime, OnInsertTextAbove(static _ => $"#{(Application.Current as App)!.CelesteService.ChapterTime}"));
AddBinding(InsertCommand, OnInsertTextAbove(static command => (string)command));
}

// TAS specific commands

private static void OnToggleCommentInputs(object target, ExecutedRoutedEventArgs args) {
TextArea textArea = GetTextArea(target);
if (textArea?.Document == null) return;

using (textArea.Document.RunUpdate()) {
int startLine = textArea.Selection.StartPosition.Line;
int endLine = textArea.Selection.EndPosition.Line;
if (textArea.Selection.IsEmpty) {
startLine = endLine = textArea.Caret.Line;
}

// Make sure that start <= end
int tmp = startLine;
startLine = Math.Min(startLine, endLine);
endLine = Math.Max(tmp, endLine);

for (int i = startLine; i <= endLine; i++) {
var line = textArea.Document.GetLineByNumber(i);
string lineText = textArea.Document.GetText(line);

if (lineText.TrimStart().StartsWith('#')) {
int hashIdx = lineText.IndexOf('#');
textArea.Document.Replace(line, lineText.Remove(hashIdx, 1));
} else {
textArea.Document.Replace(line, $"#{lineText}");
}
}
}

textArea.Caret.BringCaretToView();
args.Handled = true;
}

private static void OnToggleCommentText(object target, ExecutedRoutedEventArgs args) {
TextArea textArea = GetTextArea(target);
if (textArea?.Document == null) return;

using (textArea.Document.RunUpdate()) {
int startLine = textArea.Selection.StartPosition.Line;
int endLine = textArea.Selection.EndPosition.Line;
if (textArea.Selection.IsEmpty) {
startLine = endLine = textArea.Caret.Line;
}

// Make sure that start <= end
int tmp = startLine;
startLine = Math.Min(startLine, endLine);
endLine = Math.Max(tmp, endLine);

// Only remove # when all lines start with it. Otherwise add another
bool allCommented = true;
for (int i = startLine; i <= endLine; i++) {
var line = textArea.Document.GetLineByNumber(i);
string lineText = textArea.Document.GetText(line);

if (!lineText.TrimStart().StartsWith('#')) {
allCommented = false;
break;
}
}
for (int i = startLine; i <= endLine; i++) {
var line = textArea.Document.GetLineByNumber(i);
string lineText = textArea.Document.GetText(line);

if (allCommented) {
int hashIdx = lineText.IndexOf('#');
textArea.Document.Replace(line, lineText.Remove(hashIdx, 1));
} else {
textArea.Document.Replace(line, $"#{lineText}");
}
}
}

textArea.Caret.BringCaretToView();
args.Handled = true;
}

private static EventHandler<ExecutedRoutedEventArgs> OnInsertTextAbove(Func<object,string> getText) => (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea?.Document == null) return;

using (textArea.Document.RunUpdate()) {
var line = textArea.Document.GetLineByNumber(textArea.Caret.Line);
textArea.Document.Insert(line.Offset, $"{getText(args.Parameter)}{Environment.NewLine}");
}

textArea.Caret.BringCaretToView();
args.Handled = true;
};

// Editing commands

private static TextArea GetTextArea(object target) => target as TextArea;

internal static void AutoFormatActionLines(TextArea textArea, int lineStart, int lineEnd) {
Expand Down
9 changes: 6 additions & 3 deletions TAS.Avalonia/Editing/TASInputHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
using AvaloniaEdit;
using AvaloniaEdit.Document;
using AvaloniaEdit.Editing;
using System.Runtime.InteropServices;

namespace TAS.Avalonia.Editing;

#nullable disable

public class TASInputHandler : TextAreaInputHandler {
public TextAreaInputHandler CaretNavigation { get; }
internal static readonly KeyModifiers PlatformCommandKey = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? KeyModifiers.Meta : KeyModifiers.Control;

public TextAreaInputHandler Editing { get; }
internal static RoutedCommand Redo { get; } = new(nameof(Redo), new KeyGesture(Key.Z, PlatformCommandKey | KeyModifiers.Shift));

public TextAreaInputHandler CaretNavigation { get; }
public TextAreaInputHandler Editing { get; }
public ITextAreaInputHandler MouseSelection { get; }

private TASInputHandler InputHandler => TextArea.ActiveInputHandler as TASInputHandler;
Expand All @@ -27,7 +30,7 @@ public TASInputHandler(TextArea textArea) : base(textArea) {
NestedInputHandlers.Add(Editing = TASEditingCommandHandler.Create(textArea));
NestedInputHandlers.Add(MouseSelection = new TASSelectionMouseHandler(textArea));
AddBinding(ApplicationCommands.Undo, ExecuteUndo, CanExecuteUndo);
AddBinding(ApplicationCommands.Redo, ExecuteRedo, CanExecuteRedo);
AddBinding(Redo, ExecuteRedo, CanExecuteRedo);
}

private void AddBinding(RoutedCommand command, EventHandler<ExecutedRoutedEventArgs> handler, EventHandler<CanExecuteRoutedEventArgs> canExecuteHandler = null) {
Expand Down
13 changes: 13 additions & 0 deletions TAS.Avalonia/Models/MenuModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Input;
using AvaloniaEdit;
using AvaloniaEdit.Editing;
using DynamicData;
using ReactiveUI;

#pragma warning disable CS8601

Expand All @@ -20,6 +23,16 @@ public class MenuModel : IEnumerable<MenuModel> {
public bool IsChecked { get; init; }
public bool IsVisible { get; init; } = true;

public MenuModel(string header, RoutedCommand routedCommand, TextArea textArea, object commandParameter = null, bool? isEnabled = null, bool isChecked = false, bool isVisible = true) {
Header = header;
Command = ReactiveCommand.Create<object>(param => routedCommand.Execute(param, textArea));
CommandParameter = commandParameter;
Gesture = routedCommand.Gesture;
IsEnabled = isEnabled;
IsChecked = isChecked;
IsVisible = isVisible;
}

public MenuModel(string header, ICommand command = null, object commandParameter = null, KeyGesture gesture = null, bool? isEnabled = null, bool isChecked = false, bool isVisible = true) {
Header = header;
Command = command;
Expand Down
17 changes: 17 additions & 0 deletions TAS.Avalonia/Models/TASDocument.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Avalonia;
using Avalonia.Threading;
using AvaloniaEdit.Document;
using ReactiveUI;

Expand Down Expand Up @@ -30,6 +32,12 @@ private set {
private TASDocument(string contents) {
Document = new TextDocument(contents);
Document.TextChanged += Document_TextChanged;

(Application.Current as App)!.CelesteService.Server.LinesUpdated += OnLinesUpdated;
}

~TASDocument() {
(Application.Current as App)!.CelesteService.Server.LinesUpdated -= OnLinesUpdated;
}

public static TASDocument CreateBlank() => new TASDocument(EmptyDocument);
Expand Down Expand Up @@ -61,4 +69,13 @@ public void Save() {
private void Document_TextChanged(object sender, EventArgs eventArgs) {
Dirty = true;
}

private void OnLinesUpdated(Dictionary<int, string> lines) {
Dispatcher.UIThread.Post(() => {
foreach ((int lineNum, string newText) in lines) {
var line = Document.GetLineByNumber(lineNum + 1); // 0-indexed
Document.Replace(line, newText);
}
});
}
}
Loading
Loading