Skip to content

Commit

Permalink
Add option to insert completion item on PointerPressed/PointerRelease…
Browse files Browse the repository at this point in the history
…d/DoubleTapped
  • Loading branch information
mgarstenauer committed Dec 17, 2024
1 parent 73382a7 commit e0f0ec5
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/AvaloniaEdit.Demo/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public MainWindow()
_textEditor.TextArea.Caret.PositionChanged += Caret_PositionChanged;
_textEditor.TextArea.RightClickMovesCaret = true;
_textEditor.Options.HighlightCurrentLine = true;
_textEditor.Options.CompletionAcceptAction = CompletionAcceptAction.DoubleTapped;

_addControlButton = this.FindControl<Button>("addControlBtn");
_addControlButton.Click += AddControlButton_Click;
Expand Down
25 changes: 25 additions & 0 deletions src/AvaloniaEdit/CodeCompletion/CompletionAcceptAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace AvaloniaEdit.CodeCompletion;

/// <summary>
/// Defines the pointer action used to request the insertion of a completion item.
/// </summary>
public enum CompletionAcceptAction
{
/// <summary>
/// Insert the completion item when the pointer is pressed. (This option makes the completion
/// list behave similar to the completion list in Visual Studio Code.)
/// </summary>
PointerPressed,

/// <summary>
/// Insert the completion item when the pointer is pressed. (This option makes the completion
/// list behave similar to a context menu.)
/// </summary>
PointerReleased,

/// <summary>
/// Insert the code completion item when the item is double-tapped. (This option makes the
/// completion list behave similar to the completion list in Visual Studio.)
/// </summary>
DoubleTapped
}
115 changes: 88 additions & 27 deletions src/AvaloniaEdit/CodeCompletion/CompletionList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Threading;
using Avalonia.VisualTree;
using AvaloniaEdit.Utils;

namespace AvaloniaEdit.CodeCompletion
Expand All @@ -37,18 +38,6 @@ namespace AvaloniaEdit.CodeCompletion
/// </summary>
public class CompletionList : TemplatedControl
{
public CompletionList()
{
AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Bubble, true);

CompletionAcceptKeys = new[]
{
Key.Enter,
Key.Tab,
};
}


/// <summary>
/// If true, the CompletionList is filtered to show only matching items. Also enables search by substring.
/// If false, enables the old behavior: no filtering, search by string.StartsWith.
Expand Down Expand Up @@ -95,6 +84,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
if (_listBox != null)
{
_listBox.ItemsSource = _completionData;
AddPointerHandler(CompletionAcceptAction);
}
}

Expand All @@ -111,10 +101,29 @@ public CompletionListBox ListBox
}
}

/// <summary>
/// Gets or sets the pointer action used to request insertion of a completion item.
/// </summary>
public CompletionAcceptAction CompletionAcceptAction
{
get => _completionAcceptAction;
set
{
if (_completionAcceptAction == value)
return;

RemovePointerHandler(_completionAcceptAction);
_completionAcceptAction = value;
AddPointerHandler(value);
}
}

private CompletionAcceptAction _completionAcceptAction;

/// <summary>
/// Gets or sets the array of keys that are supposed to request insertion of the completion.
/// </summary>
public Key[] CompletionAcceptKeys { get; set; }
public Key[] CompletionAcceptKeys { get; set; } = new[] { Key.Enter, Key.Tab };

/// <summary>
/// Gets the scroll viewer used in this list box.
Expand Down Expand Up @@ -192,26 +201,78 @@ public void HandleKey(KeyEventArgs e)
}
}

private void OnPointerPressed(object sender, PointerPressedEventArgs e)
private void AddPointerHandler(CompletionAcceptAction completionAcceptAction)
{
var visual = e.Source as Visual;
if (e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
if (_listBox == null)
return;

switch (completionAcceptAction)
{
var listBoxItem = visual.VisualAncestorsAndSelf()
.TakeWhile(v => v != this)
.OfType<ListBoxItem>()
.FirstOrDefault();
case CompletionAcceptAction.PointerPressed:
_listBox.AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Bubble, true);
break;
case CompletionAcceptAction.PointerReleased:
_listBox.AddHandler(PointerReleasedEvent, OnPointerReleased);
break;
case CompletionAcceptAction.DoubleTapped:
AddHandler(DoubleTappedEvent, OnDoubleTapped);
_listBox.AddHandler(DoubleTappedEvent, OnDoubleTapped);
break;
default:
Debug.Fail("Invalid CompletionAcceptAction");
break;
}
}

if (listBoxItem != null)
{
// A completion item was clicked.
Debug.Assert(e.Handled, "Click expected to be handled by ListBoxItem.");
Debug.Assert(listBoxItem.IsSelected, "Completion item expected to be selected.");
RequestInsertion(e);
}
private void RemovePointerHandler(CompletionAcceptAction completionAcceptAction)
{
if (_listBox == null)
return;

switch (completionAcceptAction)
{
case CompletionAcceptAction.PointerPressed:
_listBox.RemoveHandler(PointerPressedEvent, OnPointerPressed);
break;
case CompletionAcceptAction.PointerReleased:
_listBox.RemoveHandler(PointerReleasedEvent, OnPointerReleased);
break;
case CompletionAcceptAction.DoubleTapped:
_listBox.RemoveHandler(DoubleTappedEvent, OnDoubleTapped);
break;
default:
Debug.Fail("Invalid CompletionAcceptAction");
break;
}
}

private void OnPointerPressed(object sender, PointerPressedEventArgs e)
{
var visual = e.Source as Visual;
if (!e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
return;

RequestInsertion(e);
}

private void OnPointerReleased(object sender, PointerReleasedEventArgs e)
{
if (e.InitialPressMouseButton != MouseButton.Left)
return;

// Ignore event if pointer is released outside the selected item.
var listBoxItem = _listBox.ContainerFromIndex(_listBox.SelectedIndex);
if (listBoxItem == null || !this.GetVisualsAt(e.GetPosition(this)).Any(v => v == listBoxItem || listBoxItem.IsVisualAncestorOf(v)))
return;

RequestInsertion(e);
}

private void OnDoubleTapped(object sender, TappedEventArgs e)
{
RequestInsertion(e);
}

/// <summary>
/// Gets/Sets the selected item.
/// </summary>
Expand Down
13 changes: 9 additions & 4 deletions src/AvaloniaEdit/CodeCompletion/CompletionWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
using AvaloniaEdit.Document;
using AvaloniaEdit.Editing;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;

namespace AvaloniaEdit.CodeCompletion
{
Expand All @@ -45,7 +43,11 @@ public class CompletionWindow : CompletionWindowBase
/// </summary>
public CompletionWindow(TextArea textArea) : base(textArea)
{
CompletionList = new CompletionList();
CompletionList = new CompletionList
{
CompletionAcceptAction = textArea.Options.CompletionAcceptAction
};

// keep height automatic
CloseAutomatically = true;
MaxHeight = 225;
Expand All @@ -59,7 +61,10 @@ public CompletionWindow(TextArea textArea) : base(textArea)

_toolTip = new PopupWithCustomPosition
{
IsLightDismissEnabled = true,
// The popup should not interfere with the pointer input. Popup visibility is
// controlled explicitly.
IsLightDismissEnabled = false,

PlacementTarget = this,
Child = _toolTipContent,
};
Expand Down
1 change: 0 additions & 1 deletion src/AvaloniaEdit/CodeCompletion/CompletionWindowBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ private void ParentWindow_LocationChanged(object sender, EventArgs e)
UpdatePosition();
}

/// <inheritdoc/>
private void OnDeactivated(object sender, EventArgs e)
{
Dispatcher.UIThread.Post(CloseIfFocusLost, DispatcherPriority.Background);
Expand Down
20 changes: 20 additions & 0 deletions src/AvaloniaEdit/TextEditorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using AvaloniaEdit.CodeCompletion;

namespace AvaloniaEdit
{
Expand Down Expand Up @@ -699,5 +700,24 @@ public bool ExtendSelectionOnMouseUp
}
}
}

private CompletionAcceptAction _completionAcceptAction = CompletionAcceptAction.PointerPressed;

/// <summary>
/// Gets/Sets the pointer action used to request the insertion of a completion item.
/// </summary>
[DefaultValue(CompletionAcceptAction.PointerPressed)]
public CompletionAcceptAction CompletionAcceptAction
{
get { return _completionAcceptAction; }
set
{
if (_completionAcceptAction != value)
{
_completionAcceptAction = value;
OnPropertyChanged(nameof(CompletionAcceptAction));
}
}
}
}
}

0 comments on commit e0f0ec5

Please sign in to comment.