2507 lines · 82.0 KB · Rust
RawDownload
use std::ops::Range; use crate::{    motion::right,    state::{Mode, Operator},    Vim,};use editor::{    display_map::{DisplaySnapshot, ToDisplayPoint},    movement::{self, FindRange},    Bias, DisplayPoint, Editor,};use gpui::{actions, impl_actions, Window};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,    DoubleQuotes,    VerticalBars,    AnyBrackets,    Parentheses,    SquareBrackets,    CurlyBrackets,    AngleBrackets,    Argument,    IndentObj { include_below: bool },    Tag,    Method,    Class,    Comment,    EntireFile,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct Word {    #[serde(default)]    ignore_punctuation: bool,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct Subword {    #[serde(default)]    ignore_punctuation: bool,}#[derive(Clone, Deserialize, JsonSchema, PartialEq)]#[serde(deny_unknown_fields)]struct IndentObj {    #[serde(default)]    include_below: bool,} impl_actions!(vim, [Word, Subword, IndentObj]); actions!(    vim,    [        Sentence,        Paragraph,        Quotes,        BackQuotes,        AnyQuotes,        DoubleQuotes,        VerticalBars,        Parentheses,        AnyBrackets,        SquareBrackets,        CurlyBrackets,        AngleBrackets,        Argument,        Tag,        Method,        Class,        Comment,        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, _: &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, _: &DoubleQuotes, window, cx| {        vim.object(Object::DoubleQuotes, window, cx)    });    Vim::action(editor, cx, |vim, _: &DoubleQuotes, window, cx| {        vim.object(Object::DoubleQuotes, window, cx)    });    Vim::action(editor, cx, |vim, _: &Parentheses, window, cx| {        vim.object(Object::Parentheses, window, cx)    });    Vim::action(editor, cx, |vim, _: &SquareBrackets, window, cx| {        vim.object(Object::SquareBrackets, window, cx)    });    Vim::action(editor, cx, |vim, _: &CurlyBrackets, window, cx| {        vim.object(Object::CurlyBrackets, window, cx)    });    Vim::action(editor, cx, |vim, _: &AngleBrackets, window, cx| {        vim.object(Object::AngleBrackets, 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>) {        match self.mode {            Mode::Normal => self.normal_object(object, window, cx),            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {                self.visual_object(object, window, cx)            }            Mode::Insert | Mode::Replace | Mode::HelixNormal => {                // 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::VerticalBars            | Object::DoubleQuotes => false,            Object::Sentence            | Object::Paragraph            | Object::AnyBrackets            | 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 { .. }            | Object::Sentence            | Object::Paragraph            | Object::Argument            | Object::IndentObj { .. } => false,            Object::Quotes            | Object::BackQuotes            | Object::AnyQuotes            | Object::DoubleQuotes            | Object::VerticalBars            | Object::AnyBrackets            | Object::Parentheses            | Object::SquareBrackets            | Object::Tag            | Object::Method            | Object::Class            | Object::Comment            | Object::EntireFile            | Object::CurlyBrackets            | Object::AngleBrackets => true,        }    }     pub fn target_visual_mode(self, current_mode: Mode, around: bool) -> Mode {        match self {            Object::Word { .. }            | Object::Subword { .. }            | Object::Sentence            | Object::Quotes            | Object::AnyQuotes            | Object::BackQuotes            | Object::DoubleQuotes => {                if current_mode == Mode::VisualBlock {                    Mode::VisualBlock                } else {                    Mode::Visual                }            }            Object::Parentheses            | Object::AnyBrackets            | Object::SquareBrackets            | Object::CurlyBrackets            | Object::AngleBrackets            | Object::VerticalBars            | Object::Tag            | Object::Comment            | Object::Argument            | Object::IndentObj { .. } => Mode::Visual,            Object::Method | Object::Class => {                if around {                    Mode::VisualLine                } else {                    Mode::Visual                }            }            Object::Paragraph | Object::EntireFile => Mode::VisualLine,        }    }     pub fn range(        self,        map: &DisplaySnapshot,        selection: Selection<DisplayPoint>,        around: bool,    ) -> Option<Range<DisplayPoint>> {        let relative_to = selection.head();        match self {            Object::Word { ignore_punctuation } => {                if around {                    around_word(map, relative_to, ignore_punctuation)                } else {                    in_word(map, relative_to, ignore_punctuation)                }            }            Object::Subword { ignore_punctuation } => {                if around {                    around_subword(map, relative_to, ignore_punctuation)                } else {                    in_subword(map, relative_to, ignore_punctuation)                }            }            Object::Sentence => sentence(map, relative_to, around),            Object::Paragraph => paragraph(map, relative_to, around),            Object::Quotes => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'')            }            Object::BackQuotes => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')            }            Object::AnyQuotes => {                let quote_types = ['\'', '"', '`']; // Types of quotes to handle                let relative_offset = relative_to.to_offset(map, Bias::Left) as isize;                 // Find the closest matching quote range                quote_types                    .iter()                    .flat_map(|&quote| {                        // Get ranges for each quote type                        surrounding_markers(                            map,                            relative_to,                            around,                            self.is_multiline(),                            quote,                            quote,                        )                    })                    .min_by_key(|range| calculate_range_distance(range, relative_offset, map))            }            Object::DoubleQuotes => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')            }            Object::VerticalBars => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '|', '|')            }            Object::Parentheses => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')            }            Object::Tag => {                let head = selection.head();                let range = selection.range();                surrounding_html_tag(map, head, range, around)            }            Object::AnyBrackets => {                let bracket_pairs = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];                let relative_offset = relative_to.to_offset(map, Bias::Left) as isize;                 bracket_pairs                    .iter()                    .flat_map(|&(open_bracket, close_bracket)| {                        surrounding_markers(                            map,                            relative_to,                            around,                            self.is_multiline(),                            open_bracket,                            close_bracket,                        )                    })                    .min_by_key(|range| calculate_range_distance(range, relative_offset, map))            }            Object::SquareBrackets => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')            }            Object::CurlyBrackets => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '{', '}')            }            Object::AngleBrackets => {                surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')            }            Object::Method => text_object(                map,                relative_to,                if around {                    TextObject::AroundFunction                } else {                    TextObject::InsideFunction                },            ),            Object::Comment => text_object(                map,                relative_to,                if around {                    TextObject::AroundComment                } else {                    TextObject::InsideComment                },            ),            Object::Class => text_object(                map,                relative_to,                if around {                    TextObject::AroundClass                } else {                    TextObject::InsideClass                },            ),            Object::Argument => argument(map, relative_to, around),            Object::IndentObj { include_below } => indent(map, relative_to, around, include_below),            Object::EntireFile => entire_file(map),        }    }     pub fn expand_selection(        self,        map: &DisplaySnapshot,        selection: &mut Selection<DisplayPoint>,        around: bool,    ) -> bool {        if let Some(range) = self.range(map, selection.clone(), around) {            selection.start = range.start;            selection.end = range.end;            if !around && self.is_multiline() {                preserve_indented_newline(map, selection);            }            true        } else {            false        }    }} /// Returns a range without the final newline char.////// If the selection spans multiple lines and is preceded by an opening brace (`{`),/// this function will trim the selection to exclude the final newline/// in order to preserve a properly indented line.pub fn preserve_indented_newline(map: &DisplaySnapshot, selection: &mut Selection<DisplayPoint>) {    let (start_point, end_point) = (selection.start.to_point(map), selection.end.to_point(map));     if start_point.row == end_point.row {        return;    }     let start_offset = selection.start.to_offset(map, Bias::Left);    let mut pos = start_offset;     while pos > 0 {        pos -= 1;        let current_char = map.buffer_chars_at(pos).next().map(|(ch, _)| ch);         match current_char {            Some(ch) if !ch.is_whitespace() => break,            Some('\n') if pos > 0 => {                let prev_char = map.buffer_chars_at(pos - 1).next().map(|(ch, _)| ch);                if prev_char == Some('{') {                    let end_pos = selection.end.to_offset(map, Bias::Left);                    for (ch, offset) in map.reverse_buffer_chars_at(end_pos) {                        match ch {                            '\n' => {                                selection.end = offset.to_display_point(map);                                selection.reversed = true;                                break;                            }                            ch if !ch.is_whitespace() => break,                            _ => continue,                        }                    }                }                break;            }            _ => continue,        }    }} /// Returns a range that surrounds the word `relative_to` is in.////// If `relative_to` is at the start of a word, return the word./// If `relative_to` is between words, return the space between.fn in_word(    map: &DisplaySnapshot,    relative_to: DisplayPoint,    ignore_punctuation: bool,) -> Option<Range<DisplayPoint>> {    // Use motion::right so that we consider the character under the cursor when looking for the start    let classifier = map        .buffer_snapshot        .char_classifier_at(relative_to.to_point(map))        .ignore_punctuation(ignore_punctuation);    let start = movement::find_preceding_boundary_display_point(        map,        right(map, relative_to, 1),        movement::FindRange::SingleLine,        |left, right| classifier.kind(left) != classifier.kind(right),    );     let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {        classifier.kind(left) != classifier.kind(right)    });     Some(start..end)} fn in_subword(    map: &DisplaySnapshot,    relative_to: DisplayPoint,    ignore_punctuation: bool,) -> Option<Range<DisplayPoint>> {    let offset = relative_to.to_offset(map, Bias::Left);    // Use motion::right so that we consider the character under the cursor when looking for the start    let classifier = map        .buffer_snapshot

Lines 1–500 of 2507.