
For #4965 There are still some minor issues: 1. When change the surround and delete the surround, we should also decide whether there are spaces inside after deleting/replacing according to whether it is open parentheses, and replace them accordingly, but at present, delete and change, haven't done this adaptation for current pr, I'm not sure if I can fit it in the back or if it needs to be fitted together. 2. In the selection mode, pressing s plus brackets should also trigger the Add Surrounds function, but this MR has not adapted the selection mode for the time being, I think we need to support different add behaviors for the three selection modes.(Currently in select mode, s is used for Substitute) 3. For the current change surrounds, if the user does not find the bracket that needs to be matched after entering cs, but it is a valid bracket, and will wait for the second input before failing, the better practice here should be to return to normal mode if the first bracket is not found 4. I reused BracketPair in language, but two of its properties weren't used in this mr, so I'm not sure if I should create a new struct with only start and end, which would have less code I'm not sure which ones need to be changed in the first issue, and which ones can be revised in the future, and it seems that they can be solved --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
278 lines
7.5 KiB
Rust
278 lines
7.5 KiB
Rust
use std::{fmt::Display, ops::Range, sync::Arc};
|
|
|
|
use crate::surrounds::SurroundsType;
|
|
use crate::{motion::Motion, object::Object};
|
|
use collections::HashMap;
|
|
use editor::Anchor;
|
|
use gpui::{Action, KeyContext};
|
|
use language::{CursorShape, Selection, TransactionId};
|
|
use serde::{Deserialize, Serialize};
|
|
use workspace::searchable::Direction;
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
|
pub enum Mode {
|
|
Normal,
|
|
Insert,
|
|
Replace,
|
|
Visual,
|
|
VisualLine,
|
|
VisualBlock,
|
|
}
|
|
|
|
impl Display for Mode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Mode::Normal => write!(f, "NORMAL"),
|
|
Mode::Insert => write!(f, "INSERT"),
|
|
Mode::Replace => write!(f, "REPLACE"),
|
|
Mode::Visual => write!(f, "VISUAL"),
|
|
Mode::VisualLine => write!(f, "VISUAL LINE"),
|
|
Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Mode {
|
|
pub fn is_visual(&self) -> bool {
|
|
match self {
|
|
Mode::Normal | Mode::Insert | Mode::Replace => false,
|
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Mode {
|
|
fn default() -> Self {
|
|
Self::Normal
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
pub enum Operator {
|
|
Change,
|
|
Delete,
|
|
Yank,
|
|
Replace,
|
|
Object { around: bool },
|
|
FindForward { before: bool },
|
|
FindBackward { after: bool },
|
|
AddSurrounds { target: Option<SurroundsType> },
|
|
ChangeSurrounds { target: Option<Object> },
|
|
DeleteSurrounds,
|
|
}
|
|
|
|
#[derive(Default, Clone)]
|
|
pub struct EditorState {
|
|
pub mode: Mode,
|
|
pub last_mode: Mode,
|
|
|
|
/// pre_count is the number before an operator is specified (3 in 3d2d)
|
|
pub pre_count: Option<usize>,
|
|
/// post_count is the number after an operator is specified (2 in 3d2d)
|
|
pub post_count: Option<usize>,
|
|
|
|
pub operator_stack: Vec<Operator>,
|
|
pub replacements: Vec<(Range<editor::Anchor>, String)>,
|
|
|
|
pub current_tx: Option<TransactionId>,
|
|
pub current_anchor: Option<Selection<Anchor>>,
|
|
pub undo_modes: HashMap<TransactionId, Mode>,
|
|
}
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub enum RecordedSelection {
|
|
#[default]
|
|
None,
|
|
Visual {
|
|
rows: u32,
|
|
cols: u32,
|
|
},
|
|
SingleLine {
|
|
cols: u32,
|
|
},
|
|
VisualBlock {
|
|
rows: u32,
|
|
cols: u32,
|
|
},
|
|
VisualLine {
|
|
rows: u32,
|
|
},
|
|
}
|
|
|
|
#[derive(Default, Clone)]
|
|
pub struct WorkspaceState {
|
|
pub search: SearchState,
|
|
pub last_find: Option<Motion>,
|
|
|
|
pub recording: bool,
|
|
pub stop_recording_after_next_action: bool,
|
|
pub replaying: bool,
|
|
pub recorded_count: Option<usize>,
|
|
pub recorded_actions: Vec<ReplayableAction>,
|
|
pub recorded_selection: RecordedSelection,
|
|
|
|
pub registers: HashMap<String, String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ReplayableAction {
|
|
Action(Box<dyn Action>),
|
|
Insertion {
|
|
text: Arc<str>,
|
|
utf16_range_to_replace: Option<Range<isize>>,
|
|
},
|
|
}
|
|
|
|
impl Clone for ReplayableAction {
|
|
fn clone(&self) -> Self {
|
|
match self {
|
|
Self::Action(action) => Self::Action(action.boxed_clone()),
|
|
Self::Insertion {
|
|
text,
|
|
utf16_range_to_replace,
|
|
} => Self::Insertion {
|
|
text: text.clone(),
|
|
utf16_range_to_replace: utf16_range_to_replace.clone(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SearchState {
|
|
pub direction: Direction,
|
|
pub count: usize,
|
|
pub initial_query: String,
|
|
}
|
|
|
|
impl Default for SearchState {
|
|
fn default() -> Self {
|
|
Self {
|
|
direction: Direction::Next,
|
|
count: 1,
|
|
initial_query: "".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EditorState {
|
|
pub fn cursor_shape(&self) -> CursorShape {
|
|
match self.mode {
|
|
Mode::Normal => {
|
|
if self.operator_stack.is_empty() {
|
|
CursorShape::Block
|
|
} else {
|
|
CursorShape::Underscore
|
|
}
|
|
}
|
|
Mode::Replace => CursorShape::Underscore,
|
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
|
|
Mode::Insert => CursorShape::Bar,
|
|
}
|
|
}
|
|
|
|
pub fn vim_controlled(&self) -> bool {
|
|
let is_insert_mode = matches!(self.mode, Mode::Insert);
|
|
if !is_insert_mode {
|
|
return true;
|
|
}
|
|
matches!(
|
|
self.operator_stack.last(),
|
|
Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
|
|
)
|
|
}
|
|
|
|
pub fn should_autoindent(&self) -> bool {
|
|
!(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
|
|
}
|
|
|
|
pub fn clip_at_line_ends(&self) -> bool {
|
|
match self.mode {
|
|
Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
|
|
false
|
|
}
|
|
Mode::Normal => true,
|
|
}
|
|
}
|
|
|
|
pub fn active_operator(&self) -> Option<Operator> {
|
|
self.operator_stack.last().cloned()
|
|
}
|
|
|
|
pub fn keymap_context_layer(&self) -> KeyContext {
|
|
let mut context = KeyContext::default();
|
|
context.set(
|
|
"vim_mode",
|
|
match self.mode {
|
|
Mode::Normal => "normal",
|
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
|
|
Mode::Insert => "insert",
|
|
Mode::Replace => "replace",
|
|
},
|
|
);
|
|
|
|
if self.vim_controlled() {
|
|
context.add("VimControl");
|
|
}
|
|
|
|
if self.active_operator().is_none() && self.pre_count.is_some()
|
|
|| self.active_operator().is_some() && self.post_count.is_some()
|
|
{
|
|
context.add("VimCount");
|
|
}
|
|
|
|
let active_operator = self.active_operator();
|
|
|
|
if let Some(active_operator) = active_operator.clone() {
|
|
for context_flag in active_operator.context_flags().into_iter() {
|
|
context.add(*context_flag);
|
|
}
|
|
}
|
|
|
|
context.set(
|
|
"vim_operator",
|
|
active_operator
|
|
.clone()
|
|
.map(|op| op.id())
|
|
.unwrap_or_else(|| "none"),
|
|
);
|
|
|
|
if self.mode == Mode::Replace {
|
|
context.add("VimWaiting");
|
|
}
|
|
context
|
|
}
|
|
}
|
|
|
|
impl Operator {
|
|
pub fn id(&self) -> &'static str {
|
|
match self {
|
|
Operator::Object { around: false } => "i",
|
|
Operator::Object { around: true } => "a",
|
|
Operator::Change => "c",
|
|
Operator::Delete => "d",
|
|
Operator::Yank => "y",
|
|
Operator::Replace => "r",
|
|
Operator::FindForward { before: false } => "f",
|
|
Operator::FindForward { before: true } => "t",
|
|
Operator::FindBackward { after: false } => "F",
|
|
Operator::FindBackward { after: true } => "T",
|
|
Operator::AddSurrounds { .. } => "ys",
|
|
Operator::ChangeSurrounds { .. } => "cs",
|
|
Operator::DeleteSurrounds => "ds",
|
|
}
|
|
}
|
|
|
|
pub fn context_flags(&self) -> &'static [&'static str] {
|
|
match self {
|
|
Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
|
|
Operator::FindForward { .. }
|
|
| Operator::FindBackward { .. }
|
|
| Operator::Replace
|
|
| Operator::AddSurrounds { target: Some(_) }
|
|
| Operator::ChangeSurrounds { .. }
|
|
| Operator::DeleteSurrounds => &["VimWaiting"],
|
|
_ => &[],
|
|
}
|
|
}
|
|
}
|