3967 lines · 130.0 KB · Rust
RawDownload
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.