use std::ops::Range; use crate::{ Vim, motion::{is_subword_end, is_subword_start, right}, state::{Mode, Operator},};use editor::{ Bias, BufferOffset, DisplayPoint, Editor, MultiBufferOffset, ToOffset, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::{self, FindRange},};use gpui::{Action, Window, actions};use itertools::Itertools;use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions};use multi_buffer::MultiBufferRow;use schemars::JsonSchema;use serde::Deserialize;use ui::Context; #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub enum Object { Word { ignore_punctuation: bool }, Subword { ignore_punctuation: bool }, Sentence, Paragraph, Quotes, BackQuotes, AnyQuotes, MiniQuotes, DoubleQuotes, VerticalBars, AnyBrackets, MiniBrackets, Parentheses, SquareBrackets, CurlyBrackets, AngleBrackets, Argument, IndentObj { include_below: bool }, Tag, Method, Class, Comment, EntireFile,} /// Selects a word text object.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct Word { #[serde(default)] ignore_punctuation: bool,} /// Selects a subword text object.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct Subword { #[serde(default)] ignore_punctuation: bool,}/// Selects text at the same indentation level.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct IndentObj { #[serde(default)] include_below: bool,} #[derive(Debug, Clone)]pub struct CandidateRange { pub start: DisplayPoint, pub end: DisplayPoint,} #[derive(Debug, Clone)]pub struct CandidateWithRanges { candidate: CandidateRange, open_range: Range<MultiBufferOffset>, close_range: Range<MultiBufferOffset>,} /// Operates on text within or around parentheses `()`.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct Parentheses { #[serde(default)] opening: bool,} /// Operates on text within or around square brackets `[]`.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct SquareBrackets { #[serde(default)] opening: bool,} /// Operates on text within or around angle brackets `<>`.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct AngleBrackets { #[serde(default)] opening: bool,} /// Operates on text within or around curly brackets `{}`.#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]#[action(namespace = vim)]#[serde(deny_unknown_fields)]struct CurlyBrackets { #[serde(default)] opening: bool,} fn cover_or_next<I: Iterator<Item = (Range<MultiBufferOffset>, Range<MultiBufferOffset>)>>( candidates: Option<I>, caret: DisplayPoint, map: &DisplaySnapshot,) -> Option<CandidateWithRanges> { let caret_offset = caret.to_offset(map, Bias::Left); let mut covering = vec![]; let mut next_ones = vec![]; let snapshot = map.buffer_snapshot(); if let Some(ranges) = candidates { for (open_range, close_range) in ranges { let start_off = open_range.start; let end_off = close_range.end; let candidate = CandidateWithRanges { candidate: CandidateRange { start: start_off.to_display_point(map), end: end_off.to_display_point(map), }, open_range: open_range.clone(), close_range: close_range.clone(), }; if open_range .start .to_offset(snapshot) .to_display_point(map) .row() == caret_offset.to_display_point(map).row() { if start_off <= caret_offset && caret_offset < end_off { covering.push(candidate); } else if start_off >= caret_offset { next_ones.push(candidate); } } } } // 1) covering -> smallest width if !covering.is_empty() { return covering.into_iter().min_by_key(|r| { r.candidate.end.to_offset(map, Bias::Right) - r.candidate.start.to_offset(map, Bias::Left) }); } // 2) next -> closest by start if !next_ones.is_empty() { return next_ones.into_iter().min_by_key(|r| { let start = r.candidate.start.to_offset(map, Bias::Left); (start.0 as isize - caret_offset.0 as isize).abs() }); } None} type DelimiterPredicate = dyn Fn(&BufferSnapshot, usize, usize) -> bool; struct DelimiterRange { open: Range<MultiBufferOffset>, close: Range<MultiBufferOffset>,} impl DelimiterRange { fn to_display_range(&self, map: &DisplaySnapshot, around: bool) -> Range<DisplayPoint> { if around { self.open.start.to_display_point(map)..self.close.end.to_display_point(map) } else { self.open.end.to_display_point(map)..self.close.start.to_display_point(map) } }} fn find_mini_delimiters( map: &DisplaySnapshot, display_point: DisplayPoint, around: bool, is_valid_delimiter: &DelimiterPredicate,) -> Option<Range<DisplayPoint>> { let point = map.clip_at_line_end(display_point).to_point(map); let offset = point.to_offset(&map.buffer_snapshot()); let line_range = get_line_range(map, point); let visible_line_range = get_visible_line_range(&line_range); let snapshot = &map.buffer_snapshot(); let mut excerpt = snapshot.excerpt_containing(offset..offset)?; let buffer = excerpt.buffer(); let buffer_offset = excerpt.map_offset_to_buffer(offset); let bracket_filter = |open: Range<usize>, close: Range<usize>| { is_valid_delimiter(buffer, open.start, close.start) }; // Try to find delimiters in visible range first let ranges = map .buffer_snapshot() .bracket_ranges(visible_line_range) .map(|ranges| { ranges.filter_map(|(open, close)| { // Convert the ranges from multibuffer space to buffer space as // that is what `is_valid_delimiter` expects, otherwise it might // panic as the values might be out of bounds. let buffer_open = excerpt.map_range_to_buffer(open.clone()); let buffer_close = excerpt.map_range_to_buffer(close.clone()); if is_valid_delimiter(buffer, buffer_open.start.0, buffer_close.start.0) { Some((open, close)) } else { None } }) }); if let Some(candidate) = cover_or_next(ranges, display_point, map) { return Some( DelimiterRange { open: candidate.open_range, close: candidate.close_range, } .to_display_range(map, around), ); } // Fall back to innermost enclosing brackets let (open_bracket, close_bracket) = buffer .innermost_enclosing_bracket_ranges(buffer_offset..buffer_offset, Some(&bracket_filter))?; Some( DelimiterRange { open: excerpt.map_range_from_buffer( BufferOffset(open_bracket.start)..BufferOffset(open_bracket.end), ), close: excerpt.map_range_from_buffer( BufferOffset(close_bracket.start)..BufferOffset(close_bracket.end), ), } .to_display_range(map, around), )} fn get_line_range(map: &DisplaySnapshot, point: Point) -> Range<Point> { let (start, mut end) = ( map.prev_line_boundary(point).0, map.next_line_boundary(point).0, ); if end == point { end = map.max_point().to_point(map); } start..end} fn get_visible_line_range(line_range: &Range<Point>) -> Range<Point> { let end_column = line_range.end.column.saturating_sub(1); line_range.start..Point::new(line_range.end.row, end_column)} fn is_quote_delimiter(buffer: &BufferSnapshot, _start: usize, end: usize) -> bool { matches!(buffer.chars_at(end).next(), Some('\'' | '"' | '`'))} fn is_bracket_delimiter(buffer: &BufferSnapshot, start: usize, _end: usize) -> bool { matches!( buffer.chars_at(start).next(), Some('(' | '[' | '{' | '<' | '|') )} fn find_mini_quotes( map: &DisplaySnapshot, display_point: DisplayPoint, around: bool,) -> Option<Range<DisplayPoint>> { find_mini_delimiters(map, display_point, around, &is_quote_delimiter)} fn find_mini_brackets( map: &DisplaySnapshot, display_point: DisplayPoint, around: bool,) -> Option<Range<DisplayPoint>> { find_mini_delimiters(map, display_point, around, &is_bracket_delimiter)} actions!( vim, [ /// Selects a sentence text object. Sentence, /// Selects a paragraph text object. Paragraph, /// Selects text within single quotes. Quotes, /// Selects text within backticks. BackQuotes, /// Selects text within the nearest quotes (single or double). MiniQuotes, /// Selects text within any type of quotes. AnyQuotes, /// Selects text within double quotes. DoubleQuotes, /// Selects text within vertical bars (pipes). VerticalBars, /// Selects text within the nearest brackets. MiniBrackets, /// Selects text within any type of brackets. AnyBrackets, /// Selects a function argument. Argument, /// Selects an HTML/XML tag. Tag, /// Selects a method or function. Method, /// Selects a class definition. Class, /// Selects a comment block. Comment, /// Selects the entire file. EntireFile ]); pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) { Vim::action( editor, cx, |vim, &Word { ignore_punctuation }: &Word, window, cx| { vim.object(Object::Word { ignore_punctuation }, window, cx) }, ); Vim::action( editor, cx, |vim, &Subword { ignore_punctuation }: &Subword, window, cx| { vim.object(Object::Subword { ignore_punctuation }, window, cx) }, ); Vim::action(editor, cx, |vim, _: &Tag, window, cx| { vim.object(Object::Tag, window, cx) }); Vim::action(editor, cx, |vim, _: &Sentence, window, cx| { vim.object(Object::Sentence, window, cx) }); Vim::action(editor, cx, |vim, _: &Paragraph, window, cx| { vim.object(Object::Paragraph, window, cx) }); Vim::action(editor, cx, |vim, _: &Quotes, window, cx| { vim.object(Object::Quotes, window, cx) }); Vim::action(editor, cx, |vim, _: &BackQuotes, window, cx| { vim.object(Object::BackQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &MiniQuotes, window, cx| { vim.object(Object::MiniQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &MiniBrackets, window, cx| { vim.object(Object::MiniBrackets, window, cx) }); Vim::action(editor, cx, |vim, _: &AnyQuotes, window, cx| { vim.object(Object::AnyQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &AnyBrackets, window, cx| { vim.object(Object::AnyBrackets, window, cx) }); Vim::action(editor, cx, |vim, _: &BackQuotes, window, cx| { vim.object(Object::BackQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &DoubleQuotes, window, cx| { vim.object(Object::DoubleQuotes, window, cx) }); Vim::action(editor, cx, |vim, action: &Parentheses, window, cx| { vim.object_impl(Object::Parentheses, action.opening, window, cx) }); Vim::action(editor, cx, |vim, action: &SquareBrackets, window, cx| { vim.object_impl(Object::SquareBrackets, action.opening, window, cx) }); Vim::action(editor, cx, |vim, action: &CurlyBrackets, window, cx| { vim.object_impl(Object::CurlyBrackets, action.opening, window, cx) }); Vim::action(editor, cx, |vim, action: &AngleBrackets, window, cx| { vim.object_impl(Object::AngleBrackets, action.opening, window, cx) }); Vim::action(editor, cx, |vim, _: &VerticalBars, window, cx| { vim.object(Object::VerticalBars, window, cx) }); Vim::action(editor, cx, |vim, _: &Argument, window, cx| { vim.object(Object::Argument, window, cx) }); Vim::action(editor, cx, |vim, _: &Method, window, cx| { vim.object(Object::Method, window, cx) }); Vim::action(editor, cx, |vim, _: &Class, window, cx| { vim.object(Object::Class, window, cx) }); Vim::action(editor, cx, |vim, _: &EntireFile, window, cx| { vim.object(Object::EntireFile, window, cx) }); Vim::action(editor, cx, |vim, _: &Comment, window, cx| { if !matches!(vim.active_operator(), Some(Operator::Object { .. })) { vim.push_operator(Operator::Object { around: true }, window, cx); } vim.object(Object::Comment, window, cx) }); Vim::action( editor, cx, |vim, &IndentObj { include_below }: &IndentObj, window, cx| { vim.object(Object::IndentObj { include_below }, window, cx) }, );} impl Vim { fn object(&mut self, object: Object, window: &mut Window, cx: &mut Context<Self>) { self.object_impl(object, false, window, cx); } fn object_impl( &mut self, object: Object, opening: bool, window: &mut Window, cx: &mut Context<Self>, ) { let count = Self::take_count(cx); match self.mode { Mode::Normal | Mode::HelixNormal => { self.normal_object(object, count, opening, window, cx) } Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixSelect => { self.visual_object(object, count, window, cx) } Mode::Insert | Mode::Replace => { // Shouldn't execute a text object in insert mode. Ignoring } } }} impl Object { pub fn is_multiline(self) -> bool { match self { Object::Word { .. } | Object::Subword { .. } | Object::Quotes | Object::BackQuotes | Object::AnyQuotes | Object::MiniQuotes | Object::VerticalBars | Object::DoubleQuotes => false, Object::Sentence | Object::Paragraph | Object::AnyBrackets | Object::MiniBrackets | Object::Parentheses | Object::Tag | Object::AngleBrackets | Object::CurlyBrackets | Object::SquareBrackets | Object::Argument | Object::Method | Object::Class | Object::EntireFile | Object::Comment | Object::IndentObj { .. } => true, } } pub fn always_expands_both_ways(self) -> bool { match self { Object::Word { .. } | Object::Subword { .. }Lines 1–500 of 3967. Load more
use std::ops::Range; use crate::{ Vim, motion::{is_subword_end, is_subword_start, right}, state::{Mode, Operator}, }; use editor::{ Bias, BufferOffset, DisplayPoint, Editor, MultiBufferOffset, ToOffset, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::{self, FindRange}, }; use gpui::{Action, Window, actions}; use itertools::Itertools; use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions}; use multi_buffer::MultiBufferRow; use schemars::JsonSchema; use serde::Deserialize; use ui::Context; #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Object { Word { ignore_punctuation: bool }, Subword { ignore_punctuation: bool }, Sentence, Paragraph, Quotes, BackQuotes, AnyQuotes, MiniQuotes, DoubleQuotes, VerticalBars, AnyBrackets, MiniBrackets, Parentheses, SquareBrackets, CurlyBrackets, AngleBrackets, Argument, IndentObj { include_below: bool }, Tag, Method, Class, Comment, EntireFile, } /// Selects a word text object. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Word { #[serde(default)] ignore_punctuation: bool, } /// Selects a subword text object. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Subword { #[serde(default)] ignore_punctuation: bool, } /// Selects text at the same indentation level. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct IndentObj { #[serde(default)] include_below: bool, } #[derive(Debug, Clone)] pub struct CandidateRange { pub start: DisplayPoint, pub end: DisplayPoint, } #[derive(Debug, Clone)] pub struct CandidateWithRanges { candidate: CandidateRange, open_range: Range<MultiBufferOffset>, close_range: Range<MultiBufferOffset>, } /// Operates on text within or around parentheses `()`. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Parentheses { #[serde(default)] opening: bool, } /// Operates on text within or around square brackets `[]`. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct SquareBrackets { #[serde(default)] opening: bool, } /// Operates on text within or around angle brackets `<>`. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct AngleBrackets { #[serde(default)] opening: bool, } /// Operates on text within or around curly brackets `{}`. #[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] #[action(namespace = vim)] #[serde(deny_unknown_fields)] struct CurlyBrackets { #[serde(default)] opening: bool, } fn cover_or_next<I: Iterator<Item = (Range<MultiBufferOffset>, Range<MultiBufferOffset>)>>( candidates: Option<I>, caret: DisplayPoint, map: &DisplaySnapshot, ) -> Option<CandidateWithRanges> { let caret_offset = caret.to_offset(map, Bias::Left); let mut covering = vec![]; let mut next_ones = vec![]; let snapshot = map.buffer_snapshot(); if let Some(ranges) = candidates { for (open_range, close_range) in ranges { let start_off = open_range.start; let end_off = close_range.end; let candidate = CandidateWithRanges { candidate: CandidateRange { start: start_off.to_display_point(map), end: end_off.to_display_point(map), }, open_range: open_range.clone(), close_range: close_range.clone(), }; if open_range .start .to_offset(snapshot) .to_display_point(map) .row() == caret_offset.to_display_point(map).row() { if start_off <= caret_offset && caret_offset < end_off { covering.push(candidate); } else if start_off >= caret_offset { next_ones.push(candidate); } } } } // 1) covering -> smallest width if !covering.is_empty() { return covering.into_iter().min_by_key(|r| { r.candidate.end.to_offset(map, Bias::Right) - r.candidate.start.to_offset(map, Bias::Left) }); } // 2) next -> closest by start if !next_ones.is_empty() { return next_ones.into_iter().min_by_key(|r| { let start = r.candidate.start.to_offset(map, Bias::Left); (start.0 as isize - caret_offset.0 as isize).abs() }); } None } type DelimiterPredicate = dyn Fn(&BufferSnapshot, usize, usize) -> bool; struct DelimiterRange { open: Range<MultiBufferOffset>, close: Range<MultiBufferOffset>, } impl DelimiterRange { fn to_display_range(&self, map: &DisplaySnapshot, around: bool) -> Range<DisplayPoint> { if around { self.open.start.to_display_point(map)..self.close.end.to_display_point(map) } else { self.open.end.to_display_point(map)..self.close.start.to_display_point(map) } } } fn find_mini_delimiters( map: &DisplaySnapshot, display_point: DisplayPoint, around: bool, is_valid_delimiter: &DelimiterPredicate, ) -> Option<Range<DisplayPoint>> { let point = map.clip_at_line_end(display_point).to_point(map); let offset = point.to_offset(&map.buffer_snapshot()); let line_range = get_line_range(map, point); let visible_line_range = get_visible_line_range(&line_range); let snapshot = &map.buffer_snapshot(); let mut excerpt = snapshot.excerpt_containing(offset..offset)?; let buffer = excerpt.buffer(); let buffer_offset = excerpt.map_offset_to_buffer(offset); let bracket_filter = |open: Range<usize>, close: Range<usize>| { is_valid_delimiter(buffer, open.start, close.start) }; // Try to find delimiters in visible range first let ranges = map .buffer_snapshot() .bracket_ranges(visible_line_range) .map(|ranges| { ranges.filter_map(|(open, close)| { // Convert the ranges from multibuffer space to buffer space as // that is what `is_valid_delimiter` expects, otherwise it might // panic as the values might be out of bounds. let buffer_open = excerpt.map_range_to_buffer(open.clone()); let buffer_close = excerpt.map_range_to_buffer(close.clone()); if is_valid_delimiter(buffer, buffer_open.start.0, buffer_close.start.0) { Some((open, close)) } else { None } }) }); if let Some(candidate) = cover_or_next(ranges, display_point, map) { return Some( DelimiterRange { open: candidate.open_range, close: candidate.close_range, } .to_display_range(map, around), ); } // Fall back to innermost enclosing brackets let (open_bracket, close_bracket) = buffer .innermost_enclosing_bracket_ranges(buffer_offset..buffer_offset, Some(&bracket_filter))?; Some( DelimiterRange { open: excerpt.map_range_from_buffer( BufferOffset(open_bracket.start)..BufferOffset(open_bracket.end), ), close: excerpt.map_range_from_buffer( BufferOffset(close_bracket.start)..BufferOffset(close_bracket.end), ), } .to_display_range(map, around), ) } fn get_line_range(map: &DisplaySnapshot, point: Point) -> Range<Point> { let (start, mut end) = ( map.prev_line_boundary(point).0, map.next_line_boundary(point).0, ); if end == point { end = map.max_point().to_point(map); } start..end } fn get_visible_line_range(line_range: &Range<Point>) -> Range<Point> { let end_column = line_range.end.column.saturating_sub(1); line_range.start..Point::new(line_range.end.row, end_column) } fn is_quote_delimiter(buffer: &BufferSnapshot, _start: usize, end: usize) -> bool { matches!(buffer.chars_at(end).next(), Some('\'' | '"' | '`')) } fn is_bracket_delimiter(buffer: &BufferSnapshot, start: usize, _end: usize) -> bool { matches!( buffer.chars_at(start).next(), Some('(' | '[' | '{' | '<' | '|') ) } fn find_mini_quotes( map: &DisplaySnapshot, display_point: DisplayPoint, around: bool, ) -> Option<Range<DisplayPoint>> { find_mini_delimiters(map, display_point, around, &is_quote_delimiter) } fn find_mini_brackets( map: &DisplaySnapshot, display_point: DisplayPoint, around: bool, ) -> Option<Range<DisplayPoint>> { find_mini_delimiters(map, display_point, around, &is_bracket_delimiter) } actions!( vim, [ /// Selects a sentence text object. Sentence, /// Selects a paragraph text object. Paragraph, /// Selects text within single quotes. Quotes, /// Selects text within backticks. BackQuotes, /// Selects text within the nearest quotes (single or double). MiniQuotes, /// Selects text within any type of quotes. AnyQuotes, /// Selects text within double quotes. DoubleQuotes, /// Selects text within vertical bars (pipes). VerticalBars, /// Selects text within the nearest brackets. MiniBrackets, /// Selects text within any type of brackets. AnyBrackets, /// Selects a function argument. Argument, /// Selects an HTML/XML tag. Tag, /// Selects a method or function. Method, /// Selects a class definition. Class, /// Selects a comment block. Comment, /// Selects the entire file. EntireFile ] ); pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) { Vim::action( editor, cx, |vim, &Word { ignore_punctuation }: &Word, window, cx| { vim.object(Object::Word { ignore_punctuation }, window, cx) }, ); Vim::action( editor, cx, |vim, &Subword { ignore_punctuation }: &Subword, window, cx| { vim.object(Object::Subword { ignore_punctuation }, window, cx) }, ); Vim::action(editor, cx, |vim, _: &Tag, window, cx| { vim.object(Object::Tag, window, cx) }); Vim::action(editor, cx, |vim, _: &Sentence, window, cx| { vim.object(Object::Sentence, window, cx) }); Vim::action(editor, cx, |vim, _: &Paragraph, window, cx| { vim.object(Object::Paragraph, window, cx) }); Vim::action(editor, cx, |vim, _: &Quotes, window, cx| { vim.object(Object::Quotes, window, cx) }); Vim::action(editor, cx, |vim, _: &BackQuotes, window, cx| { vim.object(Object::BackQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &MiniQuotes, window, cx| { vim.object(Object::MiniQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &MiniBrackets, window, cx| { vim.object(Object::MiniBrackets, window, cx) }); Vim::action(editor, cx, |vim, _: &AnyQuotes, window, cx| { vim.object(Object::AnyQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &AnyBrackets, window, cx| { vim.object(Object::AnyBrackets, window, cx) }); Vim::action(editor, cx, |vim, _: &BackQuotes, window, cx| { vim.object(Object::BackQuotes, window, cx) }); Vim::action(editor, cx, |vim, _: &DoubleQuotes, window, cx| { vim.object(Object::DoubleQuotes, window, cx) }); Vim::action(editor, cx, |vim, action: &Parentheses, window, cx| { vim.object_impl(Object::Parentheses, action.opening, window, cx) }); Vim::action(editor, cx, |vim, action: &SquareBrackets, window, cx| { vim.object_impl(Object::SquareBrackets, action.opening, window, cx) }); Vim::action(editor, cx, |vim, action: &CurlyBrackets, window, cx| { vim.object_impl(Object::CurlyBrackets, action.opening, window, cx) }); Vim::action(editor, cx, |vim, action: &AngleBrackets, window, cx| { vim.object_impl(Object::AngleBrackets, action.opening, window, cx) }); Vim::action(editor, cx, |vim, _: &VerticalBars, window, cx| { vim.object(Object::VerticalBars, window, cx) }); Vim::action(editor, cx, |vim, _: &Argument, window, cx| { vim.object(Object::Argument, window, cx) }); Vim::action(editor, cx, |vim, _: &Method, window, cx| { vim.object(Object::Method, window, cx) }); Vim::action(editor, cx, |vim, _: &Class, window, cx| { vim.object(Object::Class, window, cx) }); Vim::action(editor, cx, |vim, _: &EntireFile, window, cx| { vim.object(Object::EntireFile, window, cx) }); Vim::action(editor, cx, |vim, _: &Comment, window, cx| { if !matches!(vim.active_operator(), Some(Operator::Object { .. })) { vim.push_operator(Operator::Object { around: true }, window, cx); } vim.object(Object::Comment, window, cx) }); Vim::action( editor, cx, |vim, &IndentObj { include_below }: &IndentObj, window, cx| { vim.object(Object::IndentObj { include_below }, window, cx) }, ); } impl Vim { fn object(&mut self, object: Object, window: &mut Window, cx: &mut Context<Self>) { self.object_impl(object, false, window, cx); } fn object_impl( &mut self, object: Object, opening: bool, window: &mut Window, cx: &mut Context<Self>, ) { let count = Self::take_count(cx); match self.mode { Mode::Normal | Mode::HelixNormal => { self.normal_object(object, count, opening, window, cx) } Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixSelect => { self.visual_object(object, count, window, cx) } Mode::Insert | Mode::Replace => { // Shouldn't execute a text object in insert mode. Ignoring } } } } impl Object { pub fn is_multiline(self) -> bool { match self { Object::Word { .. } | Object::Subword { .. } | Object::Quotes | Object::BackQuotes | Object::AnyQuotes | Object::MiniQuotes | Object::VerticalBars | Object::DoubleQuotes => false, Object::Sentence | Object::Paragraph | Object::AnyBrackets | Object::MiniBrackets | Object::Parentheses | Object::Tag | Object::AngleBrackets | Object::CurlyBrackets | Object::SquareBrackets | Object::Argument | Object::Method | Object::Class | Object::EntireFile | Object::Comment | Object::IndentObj { .. } => true, } } pub fn always_expands_both_ways(self) -> bool { match self { Object::Word { .. } | Object::Subword { .. }