diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index 0d7673ba8..a97d28ff9 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -140,6 +140,9 @@ impl Editor { EditCommand::SelectAll => self.select_all(), EditCommand::CutSelection => self.cut_selection_to_cut_buffer(), EditCommand::CopySelection => self.copy_selection_to_cut_buffer(), + EditCommand::LowercaseSelection => self.lowercase_selection(), + EditCommand::UppercaseSelection => self.uppercase_selection(), + EditCommand::SwitchcaseSelection => self.switchcase_selection(), EditCommand::Paste => self.paste_cut_buffer(), EditCommand::CopyFromStart => self.copy_from_start(), EditCommand::CopyFromStartLinewise => self.copy_from_start_linewise(), @@ -674,6 +677,37 @@ impl Editor { } } + fn lowercase_selection(&mut self) { + if let Some((start, end)) = self.get_selection() { + let lowercase_slice = self.line_buffer.get_buffer()[start..end].to_ascii_lowercase(); + self.line_buffer.replace_range(start..end, &lowercase_slice); + } + } + + fn uppercase_selection(&mut self) { + if let Some((start, end)) = self.get_selection() { + let uppercase_slice = self.line_buffer.get_buffer()[start..end].to_ascii_uppercase(); + self.line_buffer.replace_range(start..end, &uppercase_slice); + } + } + + fn switchcase_selection(&mut self) { + if let Some((start, end)) = self.get_selection() { + let switchcase_slice = self.line_buffer.get_buffer()[start..end] + .chars() + .map(|ch| { + if ch.is_ascii_lowercase() { + ch.to_ascii_uppercase() + } else { + ch.to_ascii_lowercase() + } + }) + .collect::(); + self.line_buffer + .replace_range(start..end, &switchcase_slice); + } + } + /// If a selection is active returns the selected range, otherwise None. /// The range is guaranteed to be ascending. pub fn get_selection(&self) -> Option<(usize, usize)> { diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index 18999e5c7..54f1fc0df 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -78,7 +78,7 @@ where let _ = input.next(); Some(Command::EnterViAppend) } - Some('u') => { + Some('u') if mode == ViMode::Normal => { let _ = input.next(); Some(Command::Undo) } @@ -168,6 +168,14 @@ where // This arm should be unreachable ViMode::Insert => None, }, + Some(&&u @ ('u' | 'U')) if mode == ViMode::Visual => { + let _ = input.next(); + if u.is_ascii_lowercase() { + Some(Command::Lowercase) + } else { + Some(Command::Uppercase) + } + } _ => None, } } @@ -193,6 +201,8 @@ pub enum Command { RewriteCurrentLine, Change, HistorySearch, + Lowercase, + Uppercase, Switchcase, RepeatLastAction, Yank, @@ -260,7 +270,19 @@ impl Command { } } Self::HistorySearch => vec![ReedlineOption::Event(ReedlineEvent::SearchHistory)], - Self::Switchcase => vec![ReedlineOption::Edit(EditCommand::SwitchcaseChar)], + Self::Lowercase => { + vec![ReedlineOption::Edit(EditCommand::LowercaseSelection)] + } + Self::Uppercase => { + vec![ReedlineOption::Edit(EditCommand::UppercaseSelection)] + } + Self::Switchcase => { + if vi_state.mode == ViMode::Visual { + vec![ReedlineOption::Edit(EditCommand::SwitchcaseSelection)] + } else { + vec![ReedlineOption::Edit(EditCommand::SwitchcaseChar)] + } + } // Whenever a motion is required to finish the command we must be in visual mode Self::Delete | Self::Change => vec![ReedlineOption::Edit(EditCommand::CutSelection)], Self::Yank => vec![ReedlineOption::Edit(EditCommand::CopySelection)], diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index da0839cd0..b3cd05c6f 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -112,7 +112,12 @@ impl ParsedViSequence { (Some(Command::Change), ParseResult::Incomplete) if mode == ViMode::Visual => { Some(ViMode::Insert) } - (Some(Command::Delete), ParseResult::Incomplete) if mode == ViMode::Visual => { + (Some(Command::Delete), ParseResult::Incomplete) + | (Some(Command::Lowercase), ParseResult::Incomplete) + | (Some(Command::Uppercase), ParseResult::Incomplete) + | (Some(Command::Switchcase), ParseResult::Incomplete) + if mode == ViMode::Visual => + { Some(ViMode::Normal) } (Some(Command::ChangeInsidePair { .. }), _) => Some(ViMode::Insert), diff --git a/src/enums.rs b/src/enums.rs index 160c9e256..d1cdbb33a 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -390,6 +390,15 @@ pub enum EditCommand { /// Copy selection to local buffer CopySelection, + /// LowercaseSelection + LowercaseSelection, + + /// Uppercase selection + UppercaseSelection, + + /// Switchcase selection + SwitchcaseSelection, + /// Paste content from local buffer at the current cursor position Paste, @@ -583,6 +592,9 @@ impl EditCommand { | EditCommand::CutLeftUntil(_) | EditCommand::CutLeftBefore(_) | EditCommand::CutSelection + | EditCommand::LowercaseSelection + | EditCommand::UppercaseSelection + | EditCommand::SwitchcaseSelection | EditCommand::Paste | EditCommand::CutInsidePair { .. } | EditCommand::CutAroundPair { .. }