Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Visual Mode Selection and Command Consistency #867

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

deephbz
Copy link

@deephbz deephbz commented Dec 31, 2024

Fix Vi Mode Visual Selection Behavior

This PR addresses several inconsistencies in Reedline's Vi mode implementation, particularly around visual mode and cut operations, making the behavior more aligned with standard Vim.

Changes

1. Visual Mode Selection Clearing

  • Added proper selection clearing when pressing Esc
  • Fixed by adding explicit cursor movements to reset selection state
  • Ensures visual feedback matches mode state

2. Cut Operations Consistency

  • Fixed x, s, d, and c commands to behave consistently with Vim standards
  • Made x properly cut selection in visual mode instead of just one character
  • Fixed c to enter insert mode after cutting selection
  • Corrected selection range calculation to be inclusive (include the last character)

3. Mode Transitions

  • Properly restore normal mode after cut operations in visual mode
  • Added mode transitions for DeleteChar and DeleteToEnd commands
  • Fixed mode switching timing to happen before command execution

Technical Details

  • Modified get_selection() to include the last character in selection range
  • Updated command parsing to handle mode transitions correctly
  • Added proper mode restoration for all cut-related commands

Testing

The changes have been tested with the following scenarios:

  • Visual mode selection and Esc behavior
  • Cut operations in both normal and visual modes
  • Mode transitions after various commands
  • Selection range calculations

Fixes #865 .
I've done cargo fmt --all, cargo clippy, and cargo test for each of these three commits.

@deephbz deephbz mentioned this pull request Dec 31, 2024
@deephbz deephbz changed the title Bugfix/visual mode Improve Visual Mode Selection and Command Consistency Dec 31, 2024
Copy link
Member

@sholderbach sholderbach left a comment

Choose a reason for hiding this comment

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

Thanks for tackling the VI mode, very much lacking and deserving attention!

Comment on lines 146 to 151
// Move left then right to clear the selection.
// Order matters here: this makes sure cursor does not move at end of line
ReedlineEvent::Edit(vec![
EditCommand::MoveLeft { select: false },
EditCommand::MoveRight { select: false },
]),
Copy link
Member

Choose a reason for hiding this comment

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

As we are controlling things anyways, wouldn't a new ReedlineEvent::ResetSelection be easier and much clearer?

Copy link
Author

@deephbz deephbz Jan 1, 2025

Choose a reason for hiding this comment

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

Sounds great I agree an atomic ReedlineEvent::ResetSelection is much better. Implemented in the 4th commit just pushed (original PR has 3 commits).

I separate commit for easier review but feel free to squash when merging. I also made commit message more clear.

Copy link
Member

Choose a reason for hiding this comment

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

Ah thanks that looks good now!

Yeah our default in both reedline and nushell/nushell and reedline is that we squash the whole PR into one commit to not bother folks about their small fixme commit messages.

(selection_anchor, self.insertion_point())
(
selection_anchor,
(self.insertion_point() + 1).min(buffer_len),
Copy link
Member

Choose a reason for hiding this comment

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

byte index + 1 will run into the issue that this may advance into part of a multibyte UTF-8 character, producing nonsense and a panic downstream.

Copy link
Author

Choose a reason for hiding this comment

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

Do you think the min(buffer_len) can help here?
If not would you kindly suggest alternative way to approach the wrong current selection range issue?

Choose a reason for hiding this comment

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

Instead of (selection_anchor, self.insertion_point() you could use (selection_anchor, self.line_buffer.grapheme_right_index()).
For the else branch i am not sure. You could change the grapheme_right_index method to allow passing a 'cursor position' and then call it with selection_anchor. Along the lines of:

    pub fn grapheme_right_index_from_pos(&self, pos: usize) -> usize {
        self.lines[pos..]
            .grapheme_indices(true)
            .nth(1)
            .map(|(i, _)| pos + i)
            .unwrap_or_else(|| self.lines.len())
    }

Copy link
Member

Choose a reason for hiding this comment

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

Yup, something along those lines sounds right. We advance the cursor always a full grapheme (e.g. so you don't get stuck between an accent and a vowel or the components of an emoji if they are composite and rendered together)

Here it is also worth checking if changing the semantics of the selection for Vi visual mode doesn't introduce a regression for our selection stuff in the emacs mode.

@@ -108,13 +108,19 @@ impl ParsedViSequence {
| (Some(Command::RewriteCurrentLine), ParseResult::Incomplete)
| (Some(Command::SubstituteCharWithInsert), ParseResult::Incomplete)
| (Some(Command::HistorySearch), ParseResult::Incomplete)
| (Some(Command::Change), ParseResult::Valid(_)) => Some(ViMode::Insert),
| (Some(Command::Change), ParseResult::Valid(_))
| (Some(Command::Change), ParseResult::Incomplete) => Some(ViMode::Insert),
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if the mode change should occur for the Change/Incomplete combination already? At least in normal mode we should remain there. Different story in Visual mode, so maybe that is what you are trying to achieve.

We just may want to defend against it becoming an issue in normal mode.

Same for the delete part.

Copy link
Author

Choose a reason for hiding this comment

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

Agree the mode change depends on current mode. Fixed this in the 5th commit of this PR (if you'd like to squash it works together with the 1st commit).

Copy link
Member

Choose a reason for hiding this comment

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

Looks good, at first I was wondering about the special delete versions you had in the previous versoins but they are unreachable in visual mode due to all of them starting with d... Oh do we have to handle an x here?

@deephbz deephbz force-pushed the bugfix/visual-mode branch 2 times, most recently from 49357f3 to f033574 Compare January 1, 2025 01:57
@deephbz deephbz force-pushed the bugfix/visual-mode branch from f033574 to 57c2128 Compare January 1, 2025 02:22
@deephbz
Copy link
Author

deephbz commented Jan 1, 2025

Minor note: Github incorrectly introduced another "participant" because I messed up my .gitconfig "name" setting. It's fixed now

@deephbz deephbz requested a review from sholderbach January 4, 2025 01:46
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.

Vi mode: 's' key not working in visual mode
4 participants