Skip to content

Commit

Permalink
Work around IntelliCode handling Escape
Browse files Browse the repository at this point in the history
IntelliCode appears to to be handling Escape in more places now (also
possible I just didn't fully catch all of them initially). The most
problematic case is that it's handling Escape even when there is no full
line completion displayed. That is breaking our Insert mode experience
as Escape is dismissing IntelliSense but not returning to Normal mode.

After some digging around I found a better condition to determine when
IntelliCode is handling IntelliSense keystrokes. This seems to catch all
of the big cases I could find.
  • Loading branch information
jaredpar committed Nov 5, 2021
1 parent 6b026b4 commit e2a86d0
Showing 1 changed file with 72 additions and 38 deletions.
110 changes: 72 additions & 38 deletions Src/VsVimShared/Implementation/IntelliCode/IntelliCodeCommandTarget.cs
Expand Up @@ -13,44 +13,93 @@
using Microsoft.VisualStudio.Text.Editor;
using System.Reflection;
using System.Diagnostics;
using Microsoft.VisualStudio.Text;

namespace Vim.VisualStudio.Implementation.IntelliCode
{
internal sealed class IntelliCodeCommandTarget : ICommandTarget
{
private object? _cascadingCompletionSource;
private bool _lookedForCascadingCompletionSource;
private (object CascadingCompletionSource, FieldInfo CurrentPrediction, FieldInfo CompletionState)? _data;
private bool _lookedForData;

internal IVimBufferCoordinator VimBufferCoordinator { get; }
internal IAsyncCompletionBroker AsyncCompletionBroker { get; }
internal ITextDocumentFactoryService TextDocumentFactoryService { get; }

internal IVimBuffer VimBuffer => VimBufferCoordinator.VimBuffer;
internal ITextView TextView => VimBuffer.TextView;
internal object? CascadingCompletionSource
{
get
{
if (!_lookedForCascadingCompletionSource)
{
_lookedForCascadingCompletionSource = true;
_cascadingCompletionSource = TextView
.Properties
.PropertyList
.Where(pair => pair.Key is Type { Name: "CascadingCompletionSource" })
.Select(x => x.Value)
.FirstOrDefault();
}

return _cascadingCompletionSource;
EnsureLookedForData();
return _data?.CascadingCompletionSource;
}
}

internal IntelliCodeCommandTarget(
IVimBufferCoordinator vimBufferCoordinator,
IAsyncCompletionBroker asyncCompletionBroker)
IAsyncCompletionBroker asyncCompletionBroker,
ITextDocumentFactoryService textDocumentFactoryService)
{
VimBufferCoordinator = vimBufferCoordinator;
AsyncCompletionBroker = asyncCompletionBroker;
TextDocumentFactoryService = textDocumentFactoryService;
}

private void EnsureLookedForData()
{
if (_lookedForData)
{
return;
}

_lookedForData = true;
try
{
var cascadingCompletionSource = TextView
.Properties
.PropertyList
.Where(pair => pair.Key is Type { Name: "CascadingCompletionSource" })
.Select(x => x.Value)
.FirstOrDefault();
if (cascadingCompletionSource is object)
{
var type = cascadingCompletionSource.GetType();
var currentPrediction = type.GetField("_currentPrediction", BindingFlags.NonPublic | BindingFlags.Instance);
var completionState = type.GetField("_completionState", BindingFlags.NonPublic | BindingFlags.Instance);
if (currentPrediction is object && completionState is object)
{
_data = (cascadingCompletionSource, currentPrediction, completionState);
}
}
}
catch (Exception ex)
{
VimTrace.TraceInfo($"IntelliCodeCommandTarget::EnsureLookedForData error {ex.Message}");
Debug.Assert(false);
}
}

private bool IsIntelliCodeHandlingEscape()
{
EnsureLookedForData();
if (_data is { } data)
{
try
{
var completionState = data.CompletionState.GetValue(data.CascadingCompletionSource);
var currentPrediction = data.CurrentPrediction.GetValue(data.CascadingCompletionSource);
return completionState is object || currentPrediction is object;
}
catch (Exception ex)
{
VimTrace.TraceInfo($"IntelliCodeCommandTarget::IsIntelliCodeHandlingEscape error {ex.Message}");
Debug.Assert(false);
}
}

return false;
}

private bool Exec(EditCommand editCommand, out Action? preAction, out Action? postAction)
Expand All @@ -65,34 +114,15 @@ private CommandStatus QueryStatus(EditCommand editCommand)
if (editCommand.HasKeyInput &&
editCommand.KeyInput == KeyInputUtil.EscapeKey &&
VimBuffer.CanProcess(editCommand.KeyInput) &&
IsIntelliCodeLineCompletionEnabled())
IsIntelliCodeHandlingEscape())
{
VimTrace.TraceInfo($"IntelliCodeCommandTarget::QueryStatus handling escape");
VimBuffer.Process(editCommand.KeyInput);
}

return CommandStatus.PassOn;
}

private bool IsIntelliCodeLineCompletionEnabled()
{
if (CascadingCompletionSource is not { } source)
{
return false;
}

try
{
var property = source.GetType().GetProperty("ShowGrayText", BindingFlags.NonPublic | BindingFlags.Instance);
var value = property.GetValue(source, null);
return value is bool b && b;
}
catch (Exception)
{
Debug.Assert(false);
return false;
}
}

#region ICommandTarget

bool ICommandTarget.Exec(EditCommand editCommand, out Action? preAction, out Action? postAction) =>
Expand All @@ -110,15 +140,19 @@ private bool IsIntelliCodeLineCompletionEnabled()
internal sealed class IntelliCodeCommandTargetFactory : ICommandTargetFactory
{
internal IAsyncCompletionBroker AsyncCompletionBroker { get; }
internal ITextDocumentFactoryService TextDocumentFactoryService { get; }

[ImportingConstructor]
public IntelliCodeCommandTargetFactory(IAsyncCompletionBroker asyncCompletionBroker)
public IntelliCodeCommandTargetFactory(
IAsyncCompletionBroker asyncCompletionBroker,
ITextDocumentFactoryService textDocumentFactoryService)
{
AsyncCompletionBroker = asyncCompletionBroker;
TextDocumentFactoryService = textDocumentFactoryService;
}

ICommandTarget ICommandTargetFactory.CreateCommandTarget(IOleCommandTarget nextCommandTarget, IVimBufferCoordinator vimBufferCoordinator) =>
new IntelliCodeCommandTarget(vimBufferCoordinator, AsyncCompletionBroker);
new IntelliCodeCommandTarget(vimBufferCoordinator, AsyncCompletionBroker, TextDocumentFactoryService);
}
}

Expand Down

0 comments on commit e2a86d0

Please sign in to comment.