8000 [vi-mode] Correctly closing unbalanced start edit undo groups by springcomp · Pull Request #2012 · PowerShell/PSReadLine · GitHub
[go: up one dir, main page]

Skip to content

Conversation

@springcomp
Copy link
Contributor
@springcomp springcomp commented Dec 3, 2020

PR Summary

Fixes #1281.

Adds an else block in each occurrence where a _groupUndoHelper.StartGroup() is made but ultimately unbalanced (not ended) because no matching characters were found. As a result, the next time this operation is attempted PSRL no longer crashes due to not supporting nested undo groups.

Changes implementations of the following commands:

  • c, f, …
  • c, F, …
  • c, t, …
  • c, T, …

When authoring this PR I introduced a regression that boils down to the function ViInsertWithAppend.
This method does not start an undo group, and therefore, when undoing, only the last character appended is removed from the buffer.

Adding a call to _groupUndoHelper.StartGroup(), however, triggers a nested call, from one of the modified commands above as well as many other occasions, such as ViAppendLine for instance. One attempt would be to just ignore the nested call and simply pretend we are in the same, current group. But I propose another attempt which is to replace all the calls to ViInsertWithAppend which are done internally by a dedicated method that does the same, but without the call to _groupUndoHelper.StartGroup().

Then I noticed that all the occurrences of calls to ViInsertWithAppend are in a block that looks like:

if (some condition)
{
  ViInsertWithAppend(key, arg);
}
else
{
  ViInsertMode(key, arg);
}

That’s why I propose a new internal method that does this pattern in place of each occurrence of this block in the code.

PR Checklist

  • PR has a meaningful title
    • Use the present tense and imperative mood when describing your changes
  • Summarized changes
  • Make sure you've added one or more new tests
  • Make sure you've tested these changes in terminals that PowerShell is commonly used in (i.e. conhost.exe, Windows Terminal, Visual Studio Code Integrated Terminal, etc.)
  • User-facing changes
    • Not expecting documentation change.
Microsoft Reviewers: Open in CodeFlow

Comment on lines 64 to 68
if (_editGroupStart != -1)
{
// Nesting not supported.
throw new InvalidOperationException();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this check, for the edit groups started by calling StartEditGroup (without going through GroupUndoHelper.StartGroup), the current edit is not saved anywhere and will be lost. That doesn't seem right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, _groupUndoHelper and _groupUndoStates are used only when in VI mode, and the starting nested edit group issue is only relevant in VI modes.
If so, then maybe we should only skip the _editGroupStart != -1 check in GroupUndoHelper.StartGroup, but not skipping generally in StartEditGroup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have re-introduced a protection in the general StartEditGroup() method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have re-introduced a protection in the general StartEditGroup() method.

@springcomp the latest updates in the PR doesn't show a change in the StartEditGroup() method. Did you push all changes in this branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion.

Taking a step back and re-assessing the original feedback, I’m not actually in favor of the requested change.
My understanding is that this would allow nested (pending) edit groups in VI mode only, but still protected the general case from unwantingly starting nested groups.

I have pushed a last commit to address this feedback but this leads to code that is – in my opinion – not very elegant.

Please, let me know what you think.

Copy link
Member
@daxian-dbw daxian-dbw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@springcomp Thank you so much for your patience.

I spent nearly 4 hours today reviewing the changes and thinking about what the right thing should be. Overall, I think allowing nested edit groups adds too much complexity and may make it more error-prone when writing code, because it will be harder to notice when a nested edit group was made by mistake. It also makes it harder to reason the code, because part of the code base assumes the edit group is always balanced while the other part of the code base allows unbalanced edit groups.

I wonder whether it's possible to make the VI mode works as expected while requiring balanced edit groups.

Take ViReplaceToChar as an example (triggered by cf in command mode), it calls ViInsertWithAppend (trigged by a in command mode) internally which also starts an edit group with your change in this PR (expected behavior when it's explicitly triggered by user). In this case, ViInsertWithAppend should be changed to the following, and ViReplaceToChar should call ViInsertWithAppendImpl instead.

  public static void ViInsertWithAppend(ConsoleKeyInfo? key = null, object arg = null)
  {
      _singleton._groupUndoHelper.StartGroup(ViInsertWithAppend, arg);
      ViInsertWithAppendImpl(key, arg);
  }

  private static void ViInsertWithAppendImpl(ConsoleKeyInfo? key = null, object arg = null)
  {
      ViInsertMode(key, arg);
      ForwardChar(key, arg);
  }

Comment on lines 36 to 37
_instigator = instigator;
_instigatorArg = instigatorArg;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem to be right to override the existing _instigator and _instigatorArg. Consider ViReplaceToChar as an example, it calls ViInsertWithAppend internally, which also starts a group with your change, however, when redoing, it should be calling the instigator set by ViReplaceToChar instead of the one set by ViInsertWithAppend, because the use didn't explicitly trigger ViInsertWithAppend.

@daxian-dbw
Copy link
Member

@springcomp Given the concerns described in #2012 (review) and that the PR #3845 was merged, I will close this one. But as always, I really appreciate your consistent contribution to PSRL!

@daxian-dbw daxian-dbw closed this Oct 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InvalidOperationException thrown when typing cffcc in command mode

2 participants

0