Skip to content

Commit

Permalink
Added KeyMappingTimeoutHandler to fix inoremap
Browse files Browse the repository at this point in the history
Fixes inoremap BufferedInput issues
and makes the code closer to Windows version
  • Loading branch information
nosami committed May 29, 2021
1 parent 10aa339 commit 593014d
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 23 deletions.
139 changes: 139 additions & 0 deletions Src/VimMac/KeyMappingTimeoutHandler.cs
@@ -0,0 +1,139 @@
using System;
using System.ComponentModel.Composition;
using System.Windows.Threading;

namespace Vim.UI.Wpf.Implementation.Misc
{
/// <summary>
/// This class is responsible for handling the timeout of key mappings for a given
/// IVimBuffer. If the timeout occurs before the key mapping is completed then the
/// keys should just be played as normal
/// </summary>
[Export(typeof(IVimBufferCreationListener))]
internal sealed class KeyMappingTimeoutHandler : IVimBufferCreationListener
{
#region TimerData

private sealed class TimerData
{
private readonly IVimBuffer _vimBuffer;
private readonly DispatcherTimer _timer;
private readonly IProtectedOperations _protectedOperations;
private readonly KeyMappingTimeoutHandler _keyMappingTimeoutHandler;

internal TimerData(IVimBuffer vimBuffer, IProtectedOperations protectedOperations, KeyMappingTimeoutHandler keyMappingTimeoutHandler)
{
_protectedOperations = protectedOperations;
_vimBuffer = vimBuffer;
_keyMappingTimeoutHandler = keyMappingTimeoutHandler;
_timer = new DispatcherTimer(DispatcherPriority.Input);
_timer.Tick += OnTimerTick;
_vimBuffer.KeyInputProcessed += OnKeyInputProcessed;
_vimBuffer.KeyInputBuffered += OnKeyInputBuffered;
}

internal void Close()
{
_timer.Tick -= OnTimerTick;
_vimBuffer.KeyInputProcessed -= OnKeyInputProcessed;
_vimBuffer.KeyInputBuffered -= OnKeyInputBuffered;
_timer.Stop();
}

private void OnTimerTick(object sender, EventArgs e)
{
try
{
// If the Timer is still enabled then go ahead and process the buffered
// KeyInput values
if (_timer.IsEnabled)
{
_vimBuffer.ProcessBufferedKeyInputs();
}

_keyMappingTimeoutHandler.RaiseTick();
}
catch (Exception ex)
{
_protectedOperations.Report(ex);
}
}

/// <summary>
/// When a KeyInput value is processed then it should stop the timer if it's
/// currently running. Actually processing a KeyInput means it wasn't buffered
/// </summary>
private void OnKeyInputProcessed(object sender, KeyInputProcessedEventArgs args)
{
_timer.Stop();
}

private void OnKeyInputBuffered(object sender, KeyInputSetEventArgs args)
{
try
{
var globalSettings = _vimBuffer.GlobalSettings;

// If 'timeout' is not enabled then ensure the timer is disabled and return. Ensuring
// it's disabled is necessary because the 'timeout' could be disabled in the middle
// of processing a key mapping
if (!globalSettings.Timeout)
{
_timer.Stop();
return;
}

if (_timer.IsEnabled)
{
_timer.Stop();
}

_timer.Interval = TimeSpan.FromMilliseconds(globalSettings.TimeoutLength);
_timer.Start();
}
catch (Exception ex)
{
// Several DispatcherTimer operations including setting the Interval can throw
// so catch them all here
_protectedOperations.Report(ex);
}
}
}

#endregion

private readonly IProtectedOperations _protectedOperations;

/// <summary>
/// This event is raised whenever any of the timers for the underlying IVimBuffer values
/// expires
/// </summary>
internal event EventHandler Tick;

[ImportingConstructor]
internal KeyMappingTimeoutHandler(IProtectedOperations protectedOperations)
{
_protectedOperations = protectedOperations;
}

internal void OnVimBufferCreated(IVimBuffer vimBuffer)
{
var timerData = new TimerData(vimBuffer, _protectedOperations, this);
vimBuffer.Closed += (sender, e) => timerData.Close();
}

private void RaiseTick()
{
Tick?.Invoke(this, EventArgs.Empty);
}

#region IVimBufferCreationListener

void IVimBufferCreationListener.VimBufferCreated(IVimBuffer vimBuffer)
{
OnVimBufferCreated(vimBuffer);
}

#endregion
}
}
36 changes: 13 additions & 23 deletions Src/VimMac/VimKeyProcessor.cs
Expand Up @@ -41,6 +41,8 @@ internal sealed class VimKeyProcessor : KeyProcessor
_inlineRenameListenerFactory = inlineRenameListenerFactory;
}

public override bool IsInterestedInHandledEvents => true;

/// <summary>
/// This handler is necessary to intercept keyboard input which maps to Vim
/// commands but doesn't map to text input. Any combination which can be
Expand All @@ -66,16 +68,7 @@ public override void KeyDown(KeyEventArgs e)
bool canConvert = _keyUtil.TryConvertSpecialToKeyInput(e.Event, out KeyInput keyInput);
if (canConvert)
{
if (ShouldBeProcessedByVim(e, keyInput))
{

handled = TryProcess(keyInput);
}
else
{
// Needed to handle things like insert mode macro recording
_vimBuffer.SimulateProcessed(keyInput);
}
handled = TryProcess(e, keyInput);
}

VimTrace.TraceInfo("VimKeyProcessor::KeyDown Handled = {0}", handled);
Expand All @@ -91,11 +84,6 @@ public override void KeyDown(KeyEventArgs e)
e.Handled = handled;
}

private bool TryProcess(KeyInput keyInput)
{
return _vimBuffer.CanProcessAsCommand(keyInput) && _vimBuffer.Process(keyInput).IsAnyHandled;
}

private bool KeyEventIsDeadChar(KeyEventArgs e)
{
return string.IsNullOrEmpty(e.Characters);
Expand All @@ -106,7 +94,7 @@ private bool IsEscapeKey(KeyEventArgs e)
return (NSKey)e.Event.KeyCode == NSKey.Escape;
}

private bool ShouldBeProcessedByVim(KeyEventArgs e, KeyInput keyInput)
private bool TryProcess(KeyEventArgs e, KeyInput keyInput)
{
if (KeyEventIsDeadChar(e))
// When a dead key combination is pressed we will get the key down events in
Expand All @@ -120,26 +108,28 @@ private bool ShouldBeProcessedByVim(KeyEventArgs e, KeyInput keyInput)

if ((_vimBuffer.ModeKind.IsAnyInsert() || _vimBuffer.ModeKind.IsAnySelect()) &&
!_vimBuffer.CanProcessAsCommand(keyInput) &&
keyInput.Char > 0x1f)
keyInput.Char > 0x1f &&
_vimBuffer.BufferedKeyInputs.IsEmpty &&
!_vimBuffer.Vim.MacroRecorder.IsRecording)
return false;

if (IsEscapeKey(e))
return true;

if (_completionBroker.IsCompletionActive(_textView))
if (_completionBroker.IsCompletionActive(_textView) && !IsEscapeKey(e))
return false;

if (_signatureHelpBroker.IsSignatureHelpActive(_textView))
if (_signatureHelpBroker.IsSignatureHelpActive(_textView) && !IsEscapeKey(e))
return false;

if (_inlineRenameListenerFactory.InRename)
return false;

if (_vimBuffer.ModeKind.IsAnyInsert() && e.Characters == "\t")
// Allow tab key to work for snippet completion
//
// TODO: We should only really do this when the characters
// to the left of the caret form a valid snippet
return false;

return true;
return _vimBuffer.CanProcess(keyInput) && _vimBuffer.Process(keyInput).IsAnyHandled;
}
}
}

0 comments on commit 593014d

Please sign in to comment.