editor: Extract `git` and `input` out of `editor.rs` (#56155)

Mikhail Pertsev created

cc @SomeoneToIgnore

## Summary

Follow-up to https://github.com/zed-industries/zed/discussions/55352,
where the conclusion was to split `editor.rs` incrementally by topic
instead of all at once.

This mechanically extracts two editor topics into focused sibling
modules:

- `crates/editor/src/input.rs`
- `crates/editor/src/git.rs`

The git extraction is intentionally partial for now. I left a lot of
related parts because otherwise the diff was super huge (over 9K lines)
in the Github, so we can move those parts later in the follow-up PRs
Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs | 1300 ----------------------
crates/editor/src/git.rs    |  709 ++++++++++++
crates/editor/src/input.rs  | 2221 +++++++++++++++++++++++++++++++++++++++
3 files changed, 2,957 insertions(+), 1,273 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -62,6 +62,7 @@ mod code_actions;
 mod completions;
 mod config;
 mod diagnostics;
+mod input;
 mod rewrap;
 mod selection;
 
@@ -3451,14 +3452,6 @@ impl Editor {
         }
     }
 
-    pub fn set_input_enabled(&mut self, input_enabled: bool) {
-        self.input_enabled = input_enabled;
-    }
-
-    pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
-        self.expects_character_input = expects_character_input;
-    }
-
     pub fn set_edit_predictions_hidden_for_vim_mode(
         &mut self,
         hidden: bool,
@@ -3479,14 +3472,6 @@ impl Editor {
         self.menu_edit_predictions_policy = value;
     }
 
-    pub fn set_autoindent(&mut self, autoindent: bool) {
-        if autoindent {
-            self.autoindent_mode = Some(AutoindentMode::EachLine);
-        } else {
-            self.autoindent_mode = None;
-        }
-    }
-
     pub fn capability(&self, cx: &App) -> Capability {
         if self.read_only {
             Capability::ReadOnly
@@ -3503,22 +3488,10 @@ impl Editor {
         self.read_only = read_only;
     }
 
-    pub fn set_use_autoclose(&mut self, autoclose: bool) {
-        self.use_autoclose = autoclose;
-    }
-
     pub fn set_use_selection_highlight(&mut self, highlight: bool) {
         self.use_selection_highlight = highlight;
     }
 
-    pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
-        self.use_auto_surround = auto_surround;
-    }
-
-    pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
-        self.auto_replace_emoji_shortcode = auto_replace;
-    }
-
     pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
         self.buffer_serialization = should_serialize.then(|| {
             BufferSerialization::new(
@@ -3677,1254 +3650,35 @@ impl Editor {
     pub fn dismiss_menus_and_popups(
         &mut self,
         is_user_requested: bool,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> bool {
-        let mut dismissed = false;
-
-        dismissed |= self.take_rename(false, window, cx).is_some();
-        dismissed |= self.hide_blame_popover(true, cx);
-        dismissed |= hide_hover(self, cx);
-        dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
-        dismissed |= self.hide_context_menu(window, cx).is_some();
-        dismissed |= self.mouse_context_menu.take().is_some();
-        dismissed |= is_user_requested
-            && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
-        dismissed |= self.snippet_stack.pop().is_some();
-        if self.diff_review_drag_state.is_some() {
-            self.cancel_diff_review_drag(cx);
-            dismissed = true;
-        }
-        if !self.diff_review_overlays.is_empty() {
-            self.dismiss_all_diff_review_overlays(cx);
-            dismissed = true;
-        }
-
-        if self.mode.is_full() && self.has_active_diagnostic_group() {
-            self.dismiss_diagnostics(cx);
-            dismissed = true;
-        }
-
-        dismissed
-    }
-
-    fn linked_editing_ranges_for(
-        &self,
-        query_range: Range<text::Anchor>,
-        cx: &App,
-    ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
-        use text::ToOffset as TO;
-
-        if self.linked_edit_ranges.is_empty() {
-            return None;
-        }
-        if query_range.start.buffer_id != query_range.end.buffer_id {
-            return None;
-        };
-        let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
-        let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
-        let buffer_snapshot = buffer.read(cx).snapshot();
-        let (base_range, linked_ranges) = self.linked_edit_ranges.get(
-            buffer_snapshot.remote_id(),
-            query_range.clone(),
-            &buffer_snapshot,
-        )?;
-        // find offset from the start of current range to current cursor position
-        let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
-
-        let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
-        let start_difference = start_offset - start_byte_offset;
-        let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
-        let end_difference = end_offset - start_byte_offset;
-
-        // Current range has associated linked ranges.
-        let mut linked_edits = HashMap::<_, Vec<_>>::default();
-        for range in linked_ranges.iter() {
-            let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
-            let end_offset = start_offset + end_difference;
-            let start_offset = start_offset + start_difference;
-            if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
-                continue;
-            }
-            if self.selections.disjoint_anchor_ranges().any(|s| {
-                let Some((selection_start, _)) =
-                    multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
-                else {
-                    return false;
-                };
-                let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
-                else {
-                    return false;
-                };
-                if selection_start.buffer_id != query_range.start.buffer_id
-                    || selection_end.buffer_id != query_range.end.buffer_id
-                {
-                    return false;
-                }
-                TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
-                    && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
-            }) {
-                continue;
-            }
-            let start = buffer_snapshot.anchor_after(start_offset);
-            let end = buffer_snapshot.anchor_after(end_offset);
-            linked_edits
-                .entry(buffer.clone())
-                .or_default()
-                .push(start..end);
-        }
-        Some(linked_edits)
-    }
-
-    pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
-        let text: Arc<str> = text.into();
-
-        if self.read_only(cx) {
-            return;
-        }
-
-        self.unfold_buffers_with_selections(cx);
-
-        let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
-        let mut bracket_inserted = false;
-        let mut edits = Vec::new();
-        let mut linked_edits = LinkedEdits::new();
-        let mut new_selections = Vec::with_capacity(selections.len());
-        let mut new_autoclose_regions = Vec::new();
-        let snapshot = self.buffer.read(cx).read(cx);
-        let mut clear_linked_edit_ranges = false;
-        let mut all_selections_read_only = true;
-        let mut has_adjacent_edits = false;
-        let mut in_adjacent_group = false;
-
-        let mut regions = self
-            .selections_with_autoclose_regions(selections, &snapshot)
-            .peekable();
-
-        while let Some((selection, autoclose_region)) = regions.next() {
-            if snapshot
-                .point_to_buffer_point(selection.head())
-                .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
-            {
-                continue;
-            }
-            if snapshot
-                .point_to_buffer_point(selection.tail())
-                .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
-            {
-                // note, ideally we'd clip the tail to the closest writeable region towards the head
-                continue;
-            }
-            all_selections_read_only = false;
-
-            if let Some(scope) = snapshot.language_scope_at(selection.head()) {
-                // Determine if the inserted text matches the opening or closing
-                // bracket of any of this language's bracket pairs.
-                let mut bracket_pair = None;
-                let mut is_bracket_pair_start = false;
-                let mut is_bracket_pair_end = false;
-                if !text.is_empty() {
-                    let mut bracket_pair_matching_end = None;
-                    // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
-                    //  and they are removing the character that triggered IME popup.
-                    for (pair, enabled) in scope.brackets() {
-                        if !pair.close && !pair.surround {
-                            continue;
-                        }
-
-                        if enabled && pair.start.ends_with(text.as_ref()) {
-                            let prefix_len = pair.start.len() - text.len();
-                            let preceding_text_matches_prefix = prefix_len == 0
-                                || (selection.start.column >= (prefix_len as u32)
-                                    && snapshot.contains_str_at(
-                                        Point::new(
-                                            selection.start.row,
-                                            selection.start.column - (prefix_len as u32),
-                                        ),
-                                        &pair.start[..prefix_len],
-                                    ));
-                            if preceding_text_matches_prefix {
-                                bracket_pair = Some(pair.clone());
-                                is_bracket_pair_start = true;
-                                break;
-                            }
-                        }
-                        if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
-                        {
-                            // take first bracket pair matching end, but don't break in case a later bracket
-                            // pair matches start
-                            bracket_pair_matching_end = Some(pair.clone());
-                        }
-                    }
-                    if let Some(end) = bracket_pair_matching_end
-                        && bracket_pair.is_none()
-                    {
-                        bracket_pair = Some(end);
-                        is_bracket_pair_end = true;
-                    }
-                }
-
-                if let Some(bracket_pair) = bracket_pair {
-                    let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
-                    let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
-                    let auto_surround =
-                        self.use_auto_surround && snapshot_settings.use_auto_surround;
-                    if selection.is_empty() {
-                        if is_bracket_pair_start {
-                            // If the inserted text is a suffix of an opening bracket and the
-                            // selection is preceded by the rest of the opening bracket, then
-                            // insert the closing bracket.
-                            let following_text_allows_autoclose = snapshot
-                                .chars_at(selection.start)
-                                .next()
-                                .is_none_or(|c| scope.should_autoclose_before(c));
-
-                            let preceding_text_allows_autoclose = selection.start.column == 0
-                                || snapshot
-                                    .reversed_chars_at(selection.start)
-                                    .next()
-                                    .is_none_or(|c| {
-                                        bracket_pair.start != bracket_pair.end
-                                            || !snapshot
-                                                .char_classifier_at(selection.start)
-                                                .is_word(c)
-                                    });
-
-                            let is_closing_quote = if bracket_pair.end == bracket_pair.start
-                                && bracket_pair.start.len() == 1
-                            {
-                                let target = bracket_pair.start.chars().next().unwrap();
-                                let mut byte_offset = 0u32;
-                                let current_line_count = snapshot
-                                    .reversed_chars_at(selection.start)
-                                    .take_while(|&c| c != '\n')
-                                    .filter(|c| {
-                                        byte_offset += c.len_utf8() as u32;
-                                        if *c != target {
-                                            return false;
-                                        }
-
-                                        let point = Point::new(
-                                            selection.start.row,
-                                            selection.start.column.saturating_sub(byte_offset),
-                                        );
-
-                                        let is_enabled = snapshot
-                                            .language_scope_at(point)
-                                            .and_then(|scope| {
-                                                scope
-                                                    .brackets()
-                                                    .find(|(pair, _)| {
-                                                        pair.start == bracket_pair.start
-                                                    })
-                                                    .map(|(_, enabled)| enabled)
-                                            })
-                                            .unwrap_or(true);
-
-                                        let is_delimiter = snapshot
-                                            .language_scope_at(Point::new(
-                                                point.row,
-                                                point.column + 1,
-                                            ))
-                                            .and_then(|scope| {
-                                                scope
-                                                    .brackets()
-                                                    .find(|(pair, _)| {
-                                                        pair.start == bracket_pair.start
-                                                    })
-                                                    .map(|(_, enabled)| !enabled)
-                                            })
-                                            .unwrap_or(false);
-
-                                        is_enabled && !is_delimiter
-                                    })
-                                    .count();
-                                current_line_count % 2 == 1
-                            } else {
-                                false
-                            };
-
-                            if autoclose
-                                && bracket_pair.close
-                                && following_text_allows_autoclose
-                                && preceding_text_allows_autoclose
-                                && !is_closing_quote
-                            {
-                                let anchor = snapshot.anchor_before(selection.end);
-                                new_selections.push((selection.map(|_| anchor), text.len()));
-                                new_autoclose_regions.push((
-                                    anchor,
-                                    text.len(),
-                                    selection.id,
-                                    bracket_pair.clone(),
-                                ));
-                                edits.push((
-                                    selection.range(),
-                                    format!("{}{}", text, bracket_pair.end).into(),
-                                ));
-                                bracket_inserted = true;
-                                continue;
-                            }
-                        }
-
-                        if let Some(region) = autoclose_region {
-                            // If the selection is followed by an auto-inserted closing bracket,
-                            // then don't insert that closing bracket again; just move the selection
-                            // past the closing bracket.
-                            let should_skip = selection.end == region.range.end.to_point(&snapshot)
-                                && text.as_ref() == region.pair.end.as_str()
-                                && snapshot.contains_str_at(region.range.end, text.as_ref());
-                            if should_skip {
-                                let anchor = snapshot.anchor_after(selection.end);
-                                new_selections
-                                    .push((selection.map(|_| anchor), region.pair.end.len()));
-                                continue;
-                            }
-                        }
-
-                        let always_treat_brackets_as_autoclosed = snapshot
-                            .language_settings_at(selection.start, cx)
-                            .always_treat_brackets_as_autoclosed;
-                        if always_treat_brackets_as_autoclosed
-                            && is_bracket_pair_end
-                            && snapshot.contains_str_at(selection.end, text.as_ref())
-                        {
-                            // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
-                            // and the inserted text is a closing bracket and the selection is followed
-                            // by the closing bracket then move the selection past the closing bracket.
-                            let anchor = snapshot.anchor_after(selection.end);
-                            new_selections.push((selection.map(|_| anchor), text.len()));
-                            continue;
-                        }
-                    }
-                    // If an opening bracket is 1 character long and is typed while
-                    // text is selected, then surround that text with the bracket pair.
-                    else if auto_surround
-                        && bracket_pair.surround
-                        && is_bracket_pair_start
-                        && bracket_pair.start.chars().count() == 1
-                    {
-                        edits.push((selection.start..selection.start, text.clone()));
-                        edits.push((
-                            selection.end..selection.end,
-                            bracket_pair.end.as_str().into(),
-                        ));
-                        bracket_inserted = true;
-                        new_selections.push((
-                            Selection {
-                                id: selection.id,
-                                start: snapshot.anchor_after(selection.start),
-                                end: snapshot.anchor_before(selection.end),
-                                reversed: selection.reversed,
-                                goal: selection.goal,
-                            },
-                            0,
-                        ));
-                        continue;
-                    }
-                }
-            }
-
-            if self.auto_replace_emoji_shortcode
-                && selection.is_empty()
-                && text.as_ref().ends_with(':')
-                && let Some(possible_emoji_short_code) =
-                    Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
-                && !possible_emoji_short_code.is_empty()
-                && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
-            {
-                let emoji_shortcode_start = Point::new(
-                    selection.start.row,
-                    selection.start.column - possible_emoji_short_code.len() as u32 - 1,
-                );
-
-                // Remove shortcode from buffer
-                edits.push((
-                    emoji_shortcode_start..selection.start,
-                    "".to_string().into(),
-                ));
-                new_selections.push((
-                    Selection {
-                        id: selection.id,
-                        start: snapshot.anchor_after(emoji_shortcode_start),
-                        end: snapshot.anchor_before(selection.start),
-                        reversed: selection.reversed,
-                        goal: selection.goal,
-                    },
-                    0,
-                ));
-
-                // Insert emoji
-                let selection_start_anchor = snapshot.anchor_after(selection.start);
-                new_selections.push((selection.map(|_| selection_start_anchor), 0));
-                edits.push((selection.start..selection.end, emoji.to_string().into()));
-
-                continue;
-            }
-
-            let next_is_adjacent = regions
-                .peek()
-                .is_some_and(|(next, _)| selection.end == next.start);
-
-            // If not handling any auto-close operation, then just replace the selected
-            // text with the given input and move the selection to the end of the
-            // newly inserted text.
-            let anchor = if in_adjacent_group || next_is_adjacent {
-                // After edits the right bias would shift those anchor to the next visible fragment
-                // but we want to resolve to the previous one
-                snapshot.anchor_before(selection.end)
-            } else {
-                snapshot.anchor_after(selection.end)
-            };
-
-            if !self.linked_edit_ranges.is_empty() {
-                let start_anchor = snapshot.anchor_before(selection.start);
-                let classifier = snapshot
-                    .char_classifier_at(start_anchor)
-                    .scope_context(Some(CharScopeContext::LinkedEdit));
-
-                if let Some((_, anchor_range)) =
-                    snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
-                {
-                    let is_word_char = text
-                        .chars()
-                        .next()
-                        .is_none_or(|char| classifier.is_word(char));
-
-                    let is_dot = text.as_ref() == ".";
-                    let should_apply_linked_edit = is_word_char || is_dot;
-
-                    if should_apply_linked_edit {
-                        linked_edits.push(&self, anchor_range, text.clone(), cx);
-                    } else {
-                        clear_linked_edit_ranges = true;
-                    }
-                }
-            }
-
-            new_selections.push((selection.map(|_| anchor), 0));
-            edits.push((selection.start..selection.end, text.clone()));
-
-            has_adjacent_edits |= next_is_adjacent;
-            in_adjacent_group = next_is_adjacent;
-        }
-
-        if all_selections_read_only {
-            return;
-        }
-
-        drop(regions);
-        drop(snapshot);
-
-        self.transact(window, cx, |this, window, cx| {
-            if clear_linked_edit_ranges {
-                this.linked_edit_ranges.clear();
-            }
-            let initial_buffer_versions =
-                jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
-
-            this.buffer.update(cx, |buffer, cx| {
-                if has_adjacent_edits {
-                    buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
-                } else {
-                    buffer.edit(edits, this.autoindent_mode.clone(), cx);
-                }
-            });
-            linked_edits.apply(cx);
-            let new_anchor_selections = new_selections.iter().map(|e| &e.0);
-            let new_selection_deltas = new_selections.iter().map(|e| e.1);
-            let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
-            let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
-                new_anchor_selections,
-                &map,
-            )
-            .zip(new_selection_deltas)
-            .map(|(selection, delta)| Selection {
-                id: selection.id,
-                start: selection.start + delta,
-                end: selection.end + delta,
-                reversed: selection.reversed,
-                goal: SelectionGoal::None,
-            })
-            .collect::<Vec<_>>();
-
-            let mut i = 0;
-            for (position, delta, selection_id, pair) in new_autoclose_regions {
-                let position = position.to_offset(map.buffer_snapshot()) + delta;
-                let start = map.buffer_snapshot().anchor_before(position);
-                let end = map.buffer_snapshot().anchor_after(position);
-                while let Some(existing_state) = this.autoclose_regions.get(i) {
-                    match existing_state
-                        .range
-                        .start
-                        .cmp(&start, map.buffer_snapshot())
-                    {
-                        Ordering::Less => i += 1,
-                        Ordering::Greater => break,
-                        Ordering::Equal => {
-                            match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
-                                Ordering::Less => i += 1,
-                                Ordering::Equal => break,
-                                Ordering::Greater => break,
-                            }
-                        }
-                    }
-                }
-                this.autoclose_regions.insert(
-                    i,
-                    AutocloseRegion {
-                        selection_id,
-                        range: start..end,
-                        pair,
-                    },
-                );
-            }
-
-            let had_active_edit_prediction = this.has_active_edit_prediction();
-            this.change_selections(
-                SelectionEffects::scroll(Autoscroll::fit()).completions(false),
-                window,
-                cx,
-                |s| s.select(new_selections),
-            );
-
-            if !bracket_inserted
-                && let Some(on_type_format_task) =
-                    this.trigger_on_type_formatting(text.to_string(), window, cx)
-            {
-                on_type_format_task.detach_and_log_err(cx);
-            }
-
-            let editor_settings = EditorSettings::get_global(cx);
-            if bracket_inserted
-                && (editor_settings.auto_signature_help
-                    || editor_settings.show_signature_help_after_edits)
-            {
-                this.show_signature_help(&ShowSignatureHelp, window, cx);
-            }
-
-            let trigger_in_words =
-                this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
-            if this.hard_wrap.is_some() {
-                let latest: Range<Point> = this.selections.newest(&map).range();
-                if latest.is_empty()
-                    && this
-                        .buffer()
-                        .read(cx)
-                        .snapshot(cx)
-                        .line_len(MultiBufferRow(latest.start.row))
-                        == latest.start.column
-                {
-                    this.rewrap(
-                        RewrapOptions {
-                            override_language_settings: true,
-                            preserve_existing_whitespace: true,
-                            line_length: None,
-                        },
-                        cx,
-                    )
-                }
-            }
-            this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
-            refresh_linked_ranges(this, window, cx);
-            this.refresh_edit_prediction(true, false, window, cx);
-            jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
-        });
-    }
-
-    fn find_possible_emoji_shortcode_at_position(
-        snapshot: &MultiBufferSnapshot,
-        position: Point,
-    ) -> Option<String> {
-        let mut chars = Vec::new();
-        let mut found_colon = false;
-        for char in snapshot.reversed_chars_at(position).take(100) {
-            // Found a possible emoji shortcode in the middle of the buffer
-            if found_colon {
-                if char.is_whitespace() {
-                    chars.reverse();
-                    return Some(chars.iter().collect());
-                }
-                // If the previous character is not a whitespace, we are in the middle of a word
-                // and we only want to complete the shortcode if the word is made up of other emojis
-                let mut containing_word = String::new();
-                for ch in snapshot
-                    .reversed_chars_at(position)
-                    .skip(chars.len() + 1)
-                    .take(100)
-                {
-                    if ch.is_whitespace() {
-                        break;
-                    }
-                    containing_word.push(ch);
-                }
-                let containing_word = containing_word.chars().rev().collect::<String>();
-                if util::word_consists_of_emojis(containing_word.as_str()) {
-                    chars.reverse();
-                    return Some(chars.iter().collect());
-                }
-            }
-
-            if char.is_whitespace() || !char.is_ascii() {
-                return None;
-            }
-            if char == ':' {
-                found_colon = true;
-            } else {
-                chars.push(char);
-            }
-        }
-        // Found a possible emoji shortcode at the beginning of the buffer
-        chars.reverse();
-        Some(chars.iter().collect())
-    }
-
-    pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
-        if self.read_only(cx) {
-            return;
-        }
-
-        self.transact(window, cx, |this, window, cx| {
-            let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
-                let selections = this
-                    .selections
-                    .all::<MultiBufferOffset>(&this.display_snapshot(cx));
-                let multi_buffer = this.buffer.read(cx);
-                let buffer = multi_buffer.snapshot(cx);
-                selections
-                    .iter()
-                    .map(|selection| {
-                        let start_point = selection.start.to_point(&buffer);
-                        let mut existing_indent =
-                            buffer.indent_size_for_line(MultiBufferRow(start_point.row));
-                        let full_indent_len = existing_indent.len;
-                        existing_indent.len = cmp::min(existing_indent.len, start_point.column);
-                        let mut start = selection.start;
-                        let end = selection.end;
-                        let selection_is_empty = start == end;
-                        let language_scope = buffer.language_scope_at(start);
-                        let (delimiter, newline_config) = if let Some(language) = &language_scope {
-                            let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
-                                &buffer,
-                                start..end,
-                                language,
-                            )
-                                || NewlineConfig::insert_extra_newline_tree_sitter(
-                                    &buffer,
-                                    start..end,
-                                );
-
-                            let mut newline_config = NewlineConfig::Newline {
-                                additional_indent: IndentSize::spaces(0),
-                                extra_line_additional_indent: if needs_extra_newline {
-                                    Some(IndentSize::spaces(0))
-                                } else {
-                                    None
-                                },
-                                prevent_auto_indent: false,
-                            };
-
-                            let comment_delimiter = maybe!({
-                                if !selection_is_empty {
-                                    return None;
-                                }
-
-                                if !multi_buffer.language_settings(cx).extend_comment_on_newline {
-                                    return None;
-                                }
-
-                                return comment_delimiter_for_newline(
-                                    &start_point,
-                                    &buffer,
-                                    language,
-                                );
-                            });
-
-                            let doc_delimiter = maybe!({
-                                if !selection_is_empty {
-                                    return None;
-                                }
-
-                                if !multi_buffer.language_settings(cx).extend_comment_on_newline {
-                                    return None;
-                                }
-
-                                return documentation_delimiter_for_newline(
-                                    &start_point,
-                                    &buffer,
-                                    language,
-                                    &mut newline_config,
-                                );
-                            });
-
-                            let list_delimiter = maybe!({
-                                if !selection_is_empty {
-                                    return None;
-                                }
-
-                                if !multi_buffer.language_settings(cx).extend_list_on_newline {
-                                    return None;
-                                }
-
-                                return list_delimiter_for_newline(
-                                    &start_point,
-                                    &buffer,
-                                    language,
-                                    &mut newline_config,
-                                );
-                            });
-
-                            (
-                                comment_delimiter.or(doc_delimiter).or(list_delimiter),
-                                newline_config,
-                            )
-                        } else {
-                            (
-                                None,
-                                NewlineConfig::Newline {
-                                    additional_indent: IndentSize::spaces(0),
-                                    extra_line_additional_indent: None,
-                                    prevent_auto_indent: false,
-                                },
-                            )
-                        };
-
-                        let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
-                            NewlineConfig::ClearCurrentLine => {
-                                let row_start =
-                                    buffer.point_to_offset(Point::new(start_point.row, 0));
-                                (row_start, String::new(), false)
-                            }
-                            NewlineConfig::UnindentCurrentLine { continuation } => {
-                                let row_start =
-                                    buffer.point_to_offset(Point::new(start_point.row, 0));
-                                let tab_size = buffer.language_settings_at(start, cx).tab_size;
-                                let tab_size_indent = IndentSize::spaces(tab_size.get());
-                                let reduced_indent =
-                                    existing_indent.with_delta(Ordering::Less, tab_size_indent);
-                                let mut new_text = String::new();
-                                new_text.extend(reduced_indent.chars());
-                                new_text.push_str(continuation);
-                                (row_start, new_text, true)
-                            }
-                            NewlineConfig::Newline {
-                                additional_indent,
-                                extra_line_additional_indent,
-                                prevent_auto_indent,
-                            } => {
-                                let auto_indent_mode =
-                                    buffer.language_settings_at(start, cx).auto_indent;
-                                let preserve_indent =
-                                    auto_indent_mode != language::AutoIndentMode::None;
-                                let apply_syntax_indent =
-                                    auto_indent_mode == language::AutoIndentMode::SyntaxAware;
-                                let capacity_for_delimiter =
-                                    delimiter.as_deref().map(str::len).unwrap_or_default();
-                                let existing_indent_len = if preserve_indent {
-                                    existing_indent.len as usize
-                                } else {
-                                    0
-                                };
-                                let extra_line_len = extra_line_additional_indent
-                                    .map(|i| 1 + existing_indent_len + i.len as usize)
-                                    .unwrap_or(0);
-                                let mut new_text = String::with_capacity(
-                                    1 + capacity_for_delimiter
-                                        + existing_indent_len
-                                        + additional_indent.len as usize
-                                        + extra_line_len,
-                                );
-                                new_text.push('\n');
-                                if preserve_indent {
-                                    new_text.extend(existing_indent.chars());
-                                }
-                                new_text.extend(additional_indent.chars());
-                                if let Some(delimiter) = &delimiter {
-                                    new_text.push_str(delimiter);
-                                }
-                                if let Some(extra_indent) = extra_line_additional_indent {
-                                    new_text.push('\n');
-                                    if preserve_indent {
-                                        new_text.extend(existing_indent.chars());
-                                    }
-                                    new_text.extend(extra_indent.chars());
-                                }
-                                // Extend the edit to the beginning of the line
-                                // to clear auto-indent whitespace that would
-                                // otherwise remain as trailing whitespace. This
-                                // applies to blank lines and lines where only
-                                // indentation remains before the cursor.
-                                if selection_is_empty
-                                    && preserve_indent
-                                    && full_indent_len > 0
-                                    && start_point.column == full_indent_len
-                                {
-                                    start = buffer.point_to_offset(Point::new(start_point.row, 0));
-                                }
-
-                                (
-                                    start,
-                                    new_text,
-                                    *prevent_auto_indent || !apply_syntax_indent,
-                                )
-                            }
-                        };
-
-                        let anchor = buffer.anchor_after(end);
-                        let new_selection = selection.map(|_| anchor);
-                        (
-                            ((edit_start..end, new_text), prevent_auto_indent),
-                            (newline_config.has_extra_line(), new_selection),
-                        )
-                    })
-                    .unzip()
-            };
-
-            let mut auto_indent_edits = Vec::new();
-            let mut edits = Vec::new();
-            for (edit, prevent_auto_indent) in edits_with_flags {
-                if prevent_auto_indent {
-                    edits.push(edit);
-                } else {
-                    auto_indent_edits.push(edit);
-                }
-            }
-            if !edits.is_empty() {
-                this.edit(edits, cx);
-            }
-            if !auto_indent_edits.is_empty() {
-                this.edit_with_autoindent(auto_indent_edits, cx);
-            }
-
-            let buffer = this.buffer.read(cx).snapshot(cx);
-            let new_selections = selection_info
-                .into_iter()
-                .map(|(extra_newline_inserted, new_selection)| {
-                    let mut cursor = new_selection.end.to_point(&buffer);
-                    if extra_newline_inserted {
-                        cursor.row -= 1;
-                        cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
-                    }
-                    new_selection.map(|_| cursor)
-                })
-                .collect();
-
-            this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
-            this.refresh_edit_prediction(true, false, window, cx);
-            if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
-                task.detach_and_log_err(cx);
-            }
-        });
-    }
-
-    pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
-        if self.read_only(cx) {
-            return;
-        }
-
-        let buffer = self.buffer.read(cx);
-        let snapshot = buffer.snapshot(cx);
-
-        let mut edits = Vec::new();
-        let mut rows = Vec::new();
-
-        for (rows_inserted, selection) in self
-            .selections
-            .all_adjusted(&self.display_snapshot(cx))
-            .into_iter()
-            .enumerate()
-        {
-            let cursor = selection.head();
-            let row = cursor.row;
-
-            let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
-
-            let newline = "\n".to_string();
-            edits.push((start_of_line..start_of_line, newline));
-
-            rows.push(row + rows_inserted as u32);
-        }
-
-        self.transact(window, cx, |editor, window, cx| {
-            editor.edit(edits, cx);
-
-            editor.change_selections(Default::default(), window, cx, |s| {
-                let mut index = 0;
-                s.move_cursors_with(&mut |map, _, _| {
-                    let row = rows[index];
-                    index += 1;
-
-                    let point = Point::new(row, 0);
-                    let boundary = map.next_line_boundary(point).1;
-                    let clipped = map.clip_point(boundary, Bias::Left);
-
-                    (clipped, SelectionGoal::None)
-                });
-            });
-
-            let mut indent_edits = Vec::new();
-            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
-            for row in rows {
-                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
-                for (row, indent) in indents {
-                    if indent.len == 0 {
-                        continue;
-                    }
-
-                    let text = match indent.kind {
-                        IndentKind::Space => " ".repeat(indent.len as usize),
-                        IndentKind::Tab => "\t".repeat(indent.len as usize),
-                    };
-                    let point = Point::new(row.0, 0);
-                    indent_edits.push((point..point, text));
-                }
-            }
-            editor.edit(indent_edits, cx);
-            if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
-                format.detach_and_log_err(cx);
-            }
-        });
-    }
-
-    pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
-        if self.read_only(cx) {
-            return;
-        }
-
-        let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
-        let mut rows = Vec::new();
-        let mut rows_inserted = 0;
-
-        for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
-            let cursor = selection.head();
-            let row = cursor.row;
-
-            let point = Point::new(row, 0);
-            let Some((buffer_handle, buffer_point)) =
-                self.buffer.read(cx).point_to_buffer_point(point, cx)
-            else {
-                continue;
-            };
-
-            buffer_edits
-                .entry(buffer_handle.entity_id())
-                .or_insert_with(|| (buffer_handle, Vec::new()))
-                .1
-                .push(buffer_point);
-
-            rows_inserted += 1;
-            rows.push(row + rows_inserted);
-        }
-
-        self.transact(window, cx, |editor, window, cx| {
-            for (_, (buffer_handle, points)) in &buffer_edits {
-                buffer_handle.update(cx, |buffer, cx| {
-                    let edits: Vec<_> = points
-                        .iter()
-                        .map(|point| {
-                            let target = Point::new(point.row + 1, 0);
-                            let start_of_line = buffer.point_to_offset(target).min(buffer.len());
-                            (start_of_line..start_of_line, "\n")
-                        })
-                        .collect();
-                    buffer.edit(edits, None, cx);
-                });
-            }
-
-            editor.change_selections(Default::default(), window, cx, |s| {
-                let mut index = 0;
-                s.move_cursors_with(&mut |map, _, _| {
-                    let row = rows[index];
-                    index += 1;
-
-                    let point = Point::new(row, 0);
-                    let boundary = map.next_line_boundary(point).1;
-                    let clipped = map.clip_point(boundary, Bias::Left);
-
-                    (clipped, SelectionGoal::None)
-                });
-            });
-
-            let mut indent_edits = Vec::new();
-            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
-            for row in rows {
-                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
-                for (row, indent) in indents {
-                    if indent.len == 0 {
-                        continue;
-                    }
-
-                    let text = match indent.kind {
-                        IndentKind::Space => " ".repeat(indent.len as usize),
-                        IndentKind::Tab => "\t".repeat(indent.len as usize),
-                    };
-                    let point = Point::new(row.0, 0);
-                    indent_edits.push((point..point, text));
-                }
-            }
-            editor.edit(indent_edits, cx);
-            if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
-                format.detach_and_log_err(cx);
-            }
-        });
-    }
-
-    pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
-        let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
-            original_indent_columns: Vec::new(),
-        });
-        self.replace_selections(text, autoindent, window, cx, false);
-    }
-
-    /// Replaces the editor's selections with the provided `text`, applying the
-    /// given `autoindent_mode` (`None` will skip autoindentation).
-    ///
-    /// Early returns if the editor is in read-only mode, without applying any
-    /// edits.
-    fn replace_selections(
-        &mut self,
-        text: &str,
-        autoindent_mode: Option<AutoindentMode>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-        apply_linked_edits: bool,
-    ) {
-        if self.read_only(cx) {
-            return;
-        }
-
-        let text: Arc<str> = text.into();
-        self.transact(window, cx, |this, window, cx| {
-            let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
-            let linked_edits = if apply_linked_edits {
-                this.linked_edits_for_selections(text.clone(), cx)
-            } else {
-                LinkedEdits::new()
-            };
-
-            let selection_anchors = this.buffer.update(cx, |buffer, cx| {
-                let anchors = {
-                    let snapshot = buffer.read(cx);
-                    old_selections
-                        .iter()
-                        .map(|s| {
-                            let anchor = snapshot.anchor_after(s.head());
-                            s.map(|_| anchor)
-                        })
-                        .collect::<Vec<_>>()
-                };
-                buffer.edit(
-                    old_selections
-                        .iter()
-                        .map(|s| (s.start..s.end, text.clone())),
-                    autoindent_mode,
-                    cx,
-                );
-                anchors
-            });
-
-            linked_edits.apply(cx);
-
-            this.change_selections(Default::default(), window, cx, |s| {
-                s.select_anchors(selection_anchors);
-            });
-
-            if apply_linked_edits {
-                refresh_linked_ranges(this, window, cx);
-            }
-
-            cx.notify();
-        });
-    }
-
-    /// Collects linked edits for the current selections, pairing each linked
-    /// range with `text`.
-    pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
-        let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
-        let mut linked_edits = LinkedEdits::new();
-        if !self.linked_edit_ranges.is_empty() {
-            for selection in self.selections.disjoint_anchors() {
-                let Some((_, range)) =
-                    multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
-                else {
-                    continue;
-                };
-                linked_edits.push(self, range, text.clone(), cx);
-            }
-        }
-        linked_edits
-    }
-
-    /// Deletes the content covered by the current selections and applies
-    /// linked edits.
-    pub fn delete_selections_with_linked_edits(
-        &mut self,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.replace_selections("", None, window, cx, true);
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn set_linked_edit_ranges_for_testing(
-        &mut self,
-        ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
-        cx: &mut Context<Self>,
-    ) -> Option<()> {
-        let Some((buffer, _)) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(self.selections.newest_anchor().start, cx)
-        else {
-            return None;
-        };
-        let buffer = buffer.read(cx);
-        let buffer_id = buffer.remote_id();
-        let mut linked_ranges = Vec::with_capacity(ranges.len());
-        for (base_range, linked_ranges_points) in ranges {
-            let base_anchor =
-                buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
-            let linked_anchors = linked_ranges_points
-                .into_iter()
-                .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
-                .collect();
-            linked_ranges.push((base_anchor, linked_anchors));
-        }
-        let mut map = HashMap::default();
-        map.insert(buffer_id, linked_ranges);
-        self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
-        Some(())
-    }
-
-    /// If any empty selections is touching the start of its innermost containing autoclose
-    /// region, expand it to select the brackets.
-    fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let selections = self
-            .selections
-            .all::<MultiBufferOffset>(&self.display_snapshot(cx));
-        let buffer = self.buffer.read(cx).read(cx);
-        let new_selections = self
-            .selections_with_autoclose_regions(selections, &buffer)
-            .map(|(mut selection, region)| {
-                if !selection.is_empty() {
-                    return selection;
-                }
-
-                if let Some(region) = region {
-                    let mut range = region.range.to_offset(&buffer);
-                    if selection.start == range.start && range.start.0 >= region.pair.start.len() {
-                        range.start -= region.pair.start.len();
-                        if buffer.contains_str_at(range.start, &region.pair.start)
-                            && buffer.contains_str_at(range.end, &region.pair.end)
-                        {
-                            range.end += region.pair.end.len();
-                            selection.start = range.start;
-                            selection.end = range.end;
-
-                            return selection;
-                        }
-                    }
-                }
-
-                let always_treat_brackets_as_autoclosed = buffer
-                    .language_settings_at(selection.start, cx)
-                    .always_treat_brackets_as_autoclosed;
-
-                if !always_treat_brackets_as_autoclosed {
-                    return selection;
-                }
-
-                if let Some(scope) = buffer.language_scope_at(selection.start) {
-                    for (pair, enabled) in scope.brackets() {
-                        if !enabled || !pair.close {
-                            continue;
-                        }
-
-                        if buffer.contains_str_at(selection.start, &pair.end) {
-                            let pair_start_len = pair.start.len();
-                            if buffer.contains_str_at(
-                                selection.start.saturating_sub_usize(pair_start_len),
-                                &pair.start,
-                            ) {
-                                selection.start -= pair_start_len;
-                                selection.end += pair.end.len();
-
-                                return selection;
-                            }
-                        }
-                    }
-                }
-
-                selection
-            })
-            .collect();
-
-        drop(buffer);
-        self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
-            selections.select(new_selections)
-        });
-    }
-
-    /// Iterate the given selections, and for each one, find the smallest surrounding
-    /// autoclose region. This uses the ordering of the selections and the autoclose
-    /// regions to avoid repeated comparisons.
-    fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
-        &'a self,
-        selections: impl IntoIterator<Item = Selection<D>>,
-        buffer: &'a MultiBufferSnapshot,
-    ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
-        let mut i = 0;
-        let mut regions = self.autoclose_regions.as_slice();
-        selections.into_iter().map(move |selection| {
-            let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
-
-            let mut enclosing = None;
-            while let Some(pair_state) = regions.get(i) {
-                if pair_state.range.end.to_offset(buffer) < range.start {
-                    regions = &regions[i + 1..];
-                    i = 0;
-                } else if pair_state.range.start.to_offset(buffer) > range.end {
-                    break;
-                } else {
-                    if pair_state.selection_id == selection.id {
-                        enclosing = Some(pair_state);
-                    }
-                    i += 1;
-                }
-            }
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> bool {
+        let mut dismissed = false;
 
-            (selection, enclosing)
-        })
-    }
+        dismissed |= self.take_rename(false, window, cx).is_some();
+        dismissed |= self.hide_blame_popover(true, cx);
+        dismissed |= hide_hover(self, cx);
+        dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
+        dismissed |= self.hide_context_menu(window, cx).is_some();
+        dismissed |= self.mouse_context_menu.take().is_some();
+        dismissed |= is_user_requested
+            && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
+        dismissed |= self.snippet_stack.pop().is_some();
+        if self.diff_review_drag_state.is_some() {
+            self.cancel_diff_review_drag(cx);
+            dismissed = true;
+        }
+        if !self.diff_review_overlays.is_empty() {
+            self.dismiss_all_diff_review_overlays(cx);
+            dismissed = true;
+        }
 
-    /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
-    fn invalidate_autoclose_regions(
-        &mut self,
-        mut selections: &[Selection<Anchor>],
-        buffer: &MultiBufferSnapshot,
-    ) {
-        self.autoclose_regions.retain(|state| {
-            if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
-                return false;
-            }
+        if self.mode.is_full() && self.has_active_diagnostic_group() {
+            self.dismiss_diagnostics(cx);
+            dismissed = true;
+        }
 
-            let mut i = 0;
-            while let Some(selection) = selections.get(i) {
-                if selection.end.cmp(&state.range.start, buffer).is_lt() {
-                    selections = &selections[1..];
-                    continue;
-                }
-                if selection.start.cmp(&state.range.end, buffer).is_gt() {
-                    break;
-                }
-                if selection.id == state.selection_id {
-                    return true;
-                } else {
-                    i += 1;
-                }
-            }
-            false
-        });
+        dismissed
     }
 
     fn open_transaction_for_hidden_buffers(

crates/editor/src/git.rs 🔗

@@ -1 +1,710 @@
 pub mod blame;
+
+use super::*;
+
+impl Editor {
+    pub fn diff_hunks_in_ranges<'a>(
+        &'a self,
+        ranges: &'a [Range<Anchor>],
+        buffer: &'a MultiBufferSnapshot,
+    ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
+        ranges.iter().flat_map(move |range| {
+            let end_excerpt = buffer.excerpt_containing(range.end..range.end);
+            let range = range.to_point(buffer);
+            let mut peek_end = range.end;
+            if range.end.row < buffer.max_row().0 {
+                peek_end = Point::new(range.end.row + 1, 0);
+            }
+            buffer
+                .diff_hunks_in_range(range.start..peek_end)
+                .filter(move |hunk| {
+                    if let Some((_, excerpt_range)) = &end_excerpt
+                        && let Some(end_anchor) =
+                            buffer.anchor_in_excerpt(excerpt_range.context.end)
+                        && let Some(hunk_end_anchor) =
+                            buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
+                        && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
+                    {
+                        false
+                    } else {
+                        true
+                    }
+                })
+        })
+    }
+
+    pub fn set_render_diff_hunk_controls(
+        &mut self,
+        render_diff_hunk_controls: RenderDiffHunkControlsFn,
+        cx: &mut Context<Self>,
+    ) {
+        self.render_diff_hunk_controls = render_diff_hunk_controls;
+        cx.notify();
+    }
+
+    pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
+        if let Some(buffer) = self.buffer().read(cx).as_singleton() {
+            if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
+                && let Some(dir) = file.abs_path(cx).parent()
+            {
+                return Some(dir.to_owned());
+            }
+        }
+
+        None
+    }
+
+    pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
+        self.active_buffer(cx).and_then(|buffer| {
+            let buffer = buffer.read(cx);
+            if let Some(project_path) = buffer.project_path(cx) {
+                let project = self.project()?.read(cx);
+                project.absolute_path(&project_path, cx)
+            } else {
+                buffer
+                    .file()
+                    .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
+            }
+        })
+    }
+
+    /// Returns the project path for the editor's buffer, if any buffer is
+    /// opened in the editor.
+    pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
+        if let Some(buffer) = self.buffer.read(cx).as_singleton() {
+            buffer.read(cx).project_path(cx)
+        } else {
+            None
+        }
+    }
+
+    pub fn git_blame_inline_enabled(&self) -> bool {
+        self.git_blame_inline_enabled
+    }
+
+    pub fn selection_menu_enabled(&self, cx: &App) -> bool {
+        self.show_selection_menu
+            .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
+    }
+
+    pub fn toggle_selection_menu(
+        &mut self,
+        _: &ToggleSelectionMenu,
+        _: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.show_selection_menu = self
+            .show_selection_menu
+            .map(|show_selections_menu| !show_selections_menu)
+            .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
+
+        cx.notify();
+    }
+
+    pub fn blame(&self) -> Option<&Entity<GitBlame>> {
+        self.blame.as_ref()
+    }
+
+    pub fn show_git_blame_gutter(&self) -> bool {
+        self.show_git_blame_gutter
+    }
+
+    pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
+        let ranges: Vec<_> = self
+            .selections
+            .disjoint_anchors()
+            .iter()
+            .map(|s| s.range())
+            .collect();
+        self.buffer
+            .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
+    }
+
+    pub fn copy_file_name_without_extension(
+        &mut self,
+        _: &CopyFileNameWithoutExtension,
+        _: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
+            let file = buffer.read(cx).file()?;
+            file.path().file_stem()
+        }) {
+            cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
+        }
+    }
+
+    pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
+        if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
+            let file = buffer.read(cx).file()?;
+            Some(file.file_name(cx))
+        }) {
+            cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
+        }
+    }
+
+    pub fn toggle_git_blame(
+        &mut self,
+        _: &::git::Blame,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.show_git_blame_gutter = !self.show_git_blame_gutter;
+
+        if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
+            self.start_git_blame(true, window, cx);
+        }
+
+        cx.notify();
+    }
+
+    pub fn toggle_git_blame_inline(
+        &mut self,
+        _: &ToggleGitBlameInline,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.toggle_git_blame_inline_internal(true, window, cx);
+        cx.notify();
+    }
+
+    pub(super) fn toggle_staged_selected_diff_hunks(
+        &mut self,
+        _: &::git::ToggleStaged,
+        _: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let ranges: Vec<_> = self
+            .selections
+            .disjoint_anchors()
+            .iter()
+            .map(|s| s.range())
+            .collect();
+        let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
+        self.stage_or_unstage_diff_hunks(stage, ranges, cx);
+    }
+
+    pub(super) fn stage_and_next(
+        &mut self,
+        _: &::git::StageAndNext,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.do_stage_or_unstage_and_next(true, window, cx);
+    }
+
+    pub(super) fn unstage_and_next(
+        &mut self,
+        _: &::git::UnstageAndNext,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.do_stage_or_unstage_and_next(false, window, cx);
+    }
+
+    pub(super) fn stage_or_unstage_diff_hunks(
+        &mut self,
+        stage: bool,
+        ranges: Vec<Range<Anchor>>,
+        cx: &mut Context<Self>,
+    ) {
+        if self.delegate_stage_and_restore {
+            let snapshot = self.buffer.read(cx).snapshot(cx);
+            let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
+            if !hunks.is_empty() {
+                cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
+            }
+            return;
+        }
+        let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
+        cx.spawn(async move |this, cx| {
+            task.await?;
+            this.update(cx, |this, cx| {
+                let snapshot = this.buffer.read(cx).snapshot(cx);
+                let chunk_by = this
+                    .diff_hunks_in_ranges(&ranges, &snapshot)
+                    .chunk_by(|hunk| hunk.buffer_id);
+                for (buffer_id, hunks) in &chunk_by {
+                    this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
+                }
+            })
+        })
+        .detach_and_log_err(cx);
+    }
+
+    pub(super) fn do_stage_or_unstage(
+        &self,
+        stage: bool,
+        buffer_id: BufferId,
+        hunks: impl Iterator<Item = MultiBufferDiffHunk>,
+        cx: &mut App,
+    ) -> Option<()> {
+        let project = self.project()?;
+        let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
+        let diff = self.buffer.read(cx).diff_for(buffer_id)?;
+        let buffer_snapshot = buffer.read(cx).snapshot();
+        let file_exists = buffer_snapshot
+            .file()
+            .is_some_and(|file| file.disk_state().exists());
+        diff.update(cx, |diff, cx| {
+            diff.stage_or_unstage_hunks(
+                stage,
+                &hunks
+                    .map(|hunk| buffer_diff::DiffHunk {
+                        buffer_range: hunk.buffer_range,
+                        // We don't need to pass in word diffs here because they're only used for rendering and
+                        // this function changes internal state
+                        base_word_diffs: Vec::default(),
+                        buffer_word_diffs: Vec::default(),
+                        diff_base_byte_range: hunk.diff_base_byte_range.start.0
+                            ..hunk.diff_base_byte_range.end.0,
+                        secondary_status: hunk.status.secondary,
+                        range: Point::zero()..Point::zero(), // unused
+                    })
+                    .collect::<Vec<_>>(),
+                &buffer_snapshot,
+                file_exists,
+                cx,
+            )
+        });
+        None
+    }
+
+    pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
+        self.buffer.update(cx, |buffer, cx| {
+            let ranges = vec![Anchor::Min..Anchor::Max];
+            if !buffer.all_diff_hunks_expanded()
+                && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
+            {
+                buffer.collapse_diff_hunks(ranges, cx);
+                true
+            } else {
+                false
+            }
+        })
+    }
+
+    pub(super) fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
+        if self.buffer.read(cx).all_diff_hunks_expanded() {
+            return true;
+        }
+        let ranges = vec![Anchor::Min..Anchor::Max];
+        self.buffer
+            .read(cx)
+            .has_expanded_diff_hunks_in_ranges(&ranges, cx)
+    }
+
+    pub(super) fn toggle_diff_hunks_in_ranges(
+        &mut self,
+        ranges: Vec<Range<Anchor>>,
+        cx: &mut Context<Editor>,
+    ) {
+        self.buffer.update(cx, |buffer, cx| {
+            let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
+            buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
+        })
+    }
+
+    pub(super) fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
+        self.buffer.update(cx, |buffer, cx| {
+            buffer.toggle_single_diff_hunk(range, cx);
+        })
+    }
+
+    pub(super) fn apply_all_diff_hunks(
+        &mut self,
+        _: &ApplyAllDiffHunks,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if self.read_only(cx) {
+            return;
+        }
+
+        let buffers = self.buffer.read(cx).all_buffers();
+        for branch_buffer in buffers {
+            branch_buffer.update(cx, |branch_buffer, cx| {
+                branch_buffer.merge_into_base(Vec::new(), cx);
+            });
+        }
+
+        if let Some(project) = self.project.clone() {
+            self.save(
+                SaveOptions {
+                    format: true,
+                    force_format: false,
+                    autosave: false,
+                },
+                project,
+                window,
+                cx,
+            )
+            .detach_and_log_err(cx);
+        }
+    }
+
+    pub(super) fn apply_selected_diff_hunks(
+        &mut self,
+        _: &ApplyDiffHunk,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if self.read_only(cx) {
+            return;
+        }
+        let snapshot = self.snapshot(window, cx);
+        let hunks = snapshot.hunks_for_ranges(
+            self.selections
+                .all(&snapshot.display_snapshot)
+                .into_iter()
+                .map(|selection| selection.range()),
+        );
+        let mut ranges_by_buffer = HashMap::default();
+        self.transact(window, cx, |editor, _window, cx| {
+            for hunk in hunks {
+                if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
+                    ranges_by_buffer
+                        .entry(buffer.clone())
+                        .or_insert_with(Vec::new)
+                        .push(hunk.buffer_range.to_offset(buffer.read(cx)));
+                }
+            }
+
+            for (buffer, ranges) in ranges_by_buffer {
+                buffer.update(cx, |buffer, cx| {
+                    buffer.merge_into_base(ranges, cx);
+                });
+            }
+        });
+
+        if let Some(project) = self.project.clone() {
+            self.save(
+                SaveOptions {
+                    format: true,
+                    force_format: false,
+                    autosave: false,
+                },
+                project,
+                window,
+                cx,
+            )
+            .detach_and_log_err(cx);
+        }
+    }
+
+    pub(super) fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
+        self.active_buffer(cx)?
+            .read(cx)
+            .file()
+            .and_then(|f| f.as_local())
+    }
+
+    pub(super) fn reveal_in_finder(
+        &mut self,
+        _: &RevealInFileManager,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(path) = self.target_file_abs_path(cx) {
+            if let Some(project) = self.project() {
+                project.update(cx, |project, cx| project.reveal_path(&path, cx));
+            } else {
+                cx.reveal_path(&path);
+            }
+        }
+    }
+
+    pub(super) fn copy_path(
+        &mut self,
+        _: &zed_actions::workspace::CopyPath,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(path) = self.target_file_abs_path(cx)
+            && let Some(path) = path.to_str()
+        {
+            cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
+        } else {
+            cx.propagate();
+        }
+    }
+
+    pub(super) fn copy_relative_path(
+        &mut self,
+        _: &zed_actions::workspace::CopyRelativePath,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
+            let project = self.project()?.read(cx);
+            let path = buffer.read(cx).file()?.path();
+            let path = path.display(project.path_style(cx));
+            Some(path)
+        }) {
+            cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
+        } else {
+            cx.propagate();
+        }
+    }
+
+    pub(super) fn go_to_active_debug_line(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> bool {
+        maybe!({
+            let breakpoint_store = self.breakpoint_store.as_ref()?;
+
+            let (active_stack_frame, debug_line_pane_id) = {
+                let store = breakpoint_store.read(cx);
+                let active_stack_frame = store.active_position().cloned();
+                let debug_line_pane_id = store.active_debug_line_pane_id();
+                (active_stack_frame, debug_line_pane_id)
+            };
+
+            let Some(active_stack_frame) = active_stack_frame else {
+                self.clear_row_highlights::<ActiveDebugLine>();
+                return None;
+            };
+
+            if let Some(debug_line_pane_id) = debug_line_pane_id {
+                if let Some(workspace) = self
+                    .workspace
+                    .as_ref()
+                    .and_then(|(workspace, _)| workspace.upgrade())
+                {
+                    let editor_pane_id = workspace
+                        .read(cx)
+                        .pane_for_item_id(cx.entity_id())
+                        .map(|pane| pane.entity_id());
+
+                    if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
+                        self.clear_row_highlights::<ActiveDebugLine>();
+                        return None;
+                    }
+                }
+            }
+
+            let position = active_stack_frame.position;
+
+            let snapshot = self.buffer.read(cx).snapshot(cx);
+            let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
+
+            self.clear_row_highlights::<ActiveDebugLine>();
+
+            self.go_to_line::<ActiveDebugLine>(
+                multibuffer_anchor,
+                Some(cx.theme().colors().editor_debugger_active_line_background),
+                window,
+                cx,
+            );
+
+            cx.notify();
+
+            Some(())
+        })
+        .is_some()
+    }
+
+    pub(super) fn open_git_blame_commit(
+        &mut self,
+        _: &OpenGitBlameCommit,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.open_git_blame_commit_internal(window, cx);
+    }
+
+    pub(super) fn start_git_blame(
+        &mut self,
+        user_triggered: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(project) = self.project() {
+            if let Some(buffer) = self.buffer().read(cx).as_singleton()
+                && buffer.read(cx).file().is_none()
+            {
+                return;
+            }
+
+            let focused = self.focus_handle(cx).contains_focused(window, cx);
+
+            let project = project.clone();
+            let blame = cx
+                .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
+            self.blame_subscription =
+                Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
+            self.blame = Some(blame);
+        }
+    }
+
+    pub(super) fn toggle_git_blame_inline_internal(
+        &mut self,
+        user_triggered: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if self.git_blame_inline_enabled {
+            self.git_blame_inline_enabled = false;
+            self.show_git_blame_inline = false;
+            self.show_git_blame_inline_delay_task.take();
+        } else {
+            self.git_blame_inline_enabled = true;
+            self.start_git_blame_inline(user_triggered, window, cx);
+        }
+
+        cx.notify();
+    }
+
+    pub(super) fn start_git_blame_inline(
+        &mut self,
+        user_triggered: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.start_git_blame(user_triggered, window, cx);
+
+        if ProjectSettings::get_global(cx)
+            .git
+            .inline_blame_delay()
+            .is_some()
+        {
+            self.start_inline_blame_timer(window, cx);
+        } else {
+            self.show_git_blame_inline = true
+        }
+    }
+
+    pub(super) fn render_git_blame_gutter(&self, cx: &App) -> bool {
+        !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
+    }
+
+    pub(super) fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
+        self.show_git_blame_inline
+            && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
+            && !self.newest_selection_head_on_empty_line(cx)
+            && self.has_blame_entries(cx)
+    }
+
+    fn has_stageable_diff_hunks_in_ranges(
+        &self,
+        ranges: &[Range<Anchor>],
+        snapshot: &MultiBufferSnapshot,
+    ) -> bool {
+        let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
+        hunks.any(|hunk| hunk.status().has_secondary_hunk())
+    }
+
+    fn save_buffers_for_ranges_if_needed(
+        &mut self,
+        ranges: &[Range<Anchor>],
+        cx: &mut Context<Editor>,
+    ) -> Task<Result<()>> {
+        let multibuffer = self.buffer.read(cx);
+        let snapshot = multibuffer.read(cx);
+        let buffer_ids: HashSet<_> = ranges
+            .iter()
+            .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
+            .collect();
+        drop(snapshot);
+
+        let mut buffers = HashSet::default();
+        for buffer_id in buffer_ids {
+            if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
+                let buffer = buffer_entity.read(cx);
+                if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
+                {
+                    buffers.insert(buffer_entity);
+                }
+            }
+        }
+
+        if let Some(project) = &self.project {
+            project.update(cx, |project, cx| project.save_buffers(buffers, cx))
+        } else {
+            Task::ready(Ok(()))
+        }
+    }
+
+    fn do_stage_or_unstage_and_next(
+        &mut self,
+        stage: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
+
+        if ranges.iter().any(|range| range.start != range.end) {
+            self.stage_or_unstage_diff_hunks(stage, ranges, cx);
+            return;
+        }
+
+        self.stage_or_unstage_diff_hunks(stage, ranges, cx);
+
+        let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
+        let wrap_around = !all_diff_hunks_expanded;
+        let snapshot = self.snapshot(window, cx);
+        let position = self
+            .selections
+            .newest::<Point>(&snapshot.display_snapshot)
+            .head();
+
+        self.go_to_hunk_before_or_after_position(
+            &snapshot,
+            position,
+            Direction::Next,
+            wrap_around,
+            window,
+            cx,
+        );
+    }
+
+    fn open_git_blame_commit_internal(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<()> {
+        let blame = self.blame.as_ref()?;
+        let snapshot = self.snapshot(window, cx);
+        let cursor = self
+            .selections
+            .newest::<Point>(&snapshot.display_snapshot)
+            .head();
+        let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
+        let (_, blame_entry) = blame
+            .update(cx, |blame, cx| {
+                blame
+                    .blame_for_rows(
+                        &[RowInfo {
+                            buffer_id: Some(buffer.remote_id()),
+                            buffer_row: Some(point.row),
+                            ..Default::default()
+                        }],
+                        cx,
+                    )
+                    .next()
+            })
+            .flatten()?;
+        let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
+        let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
+        let workspace = self.workspace()?.downgrade();
+        renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
+        None
+    }
+
+    fn has_blame_entries(&self, cx: &App) -> bool {
+        self.blame()
+            .is_some_and(|blame| blame.read(cx).has_generated_entries())
+    }
+
+    fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
+        let cursor_anchor = self.selections.newest_anchor().head();
+
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
+
+        snapshot.line_len(buffer_row) == 0
+    }
+}

crates/editor/src/input.rs 🔗

@@ -0,0 +1,2221 @@
+use super::*;
+
+const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
+
+impl Editor {
+    pub fn set_input_enabled(&mut self, input_enabled: bool) {
+        self.input_enabled = input_enabled;
+    }
+
+    pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
+        self.expects_character_input = expects_character_input;
+    }
+
+    pub fn set_autoindent(&mut self, autoindent: bool) {
+        if autoindent {
+            self.autoindent_mode = Some(AutoindentMode::EachLine);
+        } else {
+            self.autoindent_mode = None;
+        }
+    }
+
+    pub fn set_use_autoclose(&mut self, autoclose: bool) {
+        self.use_autoclose = autoclose;
+    }
+
+    pub fn replay_insert_event(
+        &mut self,
+        text: &str,
+        relative_utf16_range: Option<Range<isize>>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if !self.input_enabled {
+            cx.emit(EditorEvent::InputIgnored { text: text.into() });
+            return;
+        }
+        if let Some(relative_utf16_range) = relative_utf16_range {
+            let selections = self
+                .selections
+                .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
+            self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                let new_ranges = selections.into_iter().map(|range| {
+                    let start = MultiBufferOffsetUtf16(OffsetUtf16(
+                        range
+                            .head()
+                            .0
+                            .0
+                            .saturating_add_signed(relative_utf16_range.start),
+                    ));
+                    let end = MultiBufferOffsetUtf16(OffsetUtf16(
+                        range
+                            .head()
+                            .0
+                            .0
+                            .saturating_add_signed(relative_utf16_range.end),
+                    ));
+                    start..end
+                });
+                s.select_ranges(new_ranges);
+            });
+        }
+
+        self.handle_input(text, window, cx);
+    }
+
+    pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
+        let text: Arc<str> = text.into();
+
+        if self.read_only(cx) {
+            return;
+        }
+
+        self.unfold_buffers_with_selections(cx);
+
+        let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
+        let mut bracket_inserted = false;
+        let mut edits = Vec::new();
+        let mut linked_edits = LinkedEdits::new();
+        let mut new_selections = Vec::with_capacity(selections.len());
+        let mut new_autoclose_regions = Vec::new();
+        let snapshot = self.buffer.read(cx).read(cx);
+        let mut clear_linked_edit_ranges = false;
+        let mut all_selections_read_only = true;
+        let mut has_adjacent_edits = false;
+        let mut in_adjacent_group = false;
+
+        let mut regions = self
+            .selections_with_autoclose_regions(selections, &snapshot)
+            .peekable();
+
+        while let Some((selection, autoclose_region)) = regions.next() {
+            if snapshot
+                .point_to_buffer_point(selection.head())
+                .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
+            {
+                continue;
+            }
+            if snapshot
+                .point_to_buffer_point(selection.tail())
+                .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
+            {
+                // note, ideally we'd clip the tail to the closest writeable region towards the head
+                continue;
+            }
+            all_selections_read_only = false;
+
+            if let Some(scope) = snapshot.language_scope_at(selection.head()) {
+                // Determine if the inserted text matches the opening or closing
+                // bracket of any of this language's bracket pairs.
+                let mut bracket_pair = None;
+                let mut is_bracket_pair_start = false;
+                let mut is_bracket_pair_end = false;
+                if !text.is_empty() {
+                    let mut bracket_pair_matching_end = None;
+                    // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
+                    //  and they are removing the character that triggered IME popup.
+                    for (pair, enabled) in scope.brackets() {
+                        if !pair.close && !pair.surround {
+                            continue;
+                        }
+
+                        if enabled && pair.start.ends_with(text.as_ref()) {
+                            let prefix_len = pair.start.len() - text.len();
+                            let preceding_text_matches_prefix = prefix_len == 0
+                                || (selection.start.column >= (prefix_len as u32)
+                                    && snapshot.contains_str_at(
+                                        Point::new(
+                                            selection.start.row,
+                                            selection.start.column - (prefix_len as u32),
+                                        ),
+                                        &pair.start[..prefix_len],
+                                    ));
+                            if preceding_text_matches_prefix {
+                                bracket_pair = Some(pair.clone());
+                                is_bracket_pair_start = true;
+                                break;
+                            }
+                        }
+                        if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
+                        {
+                            // take first bracket pair matching end, but don't break in case a later bracket
+                            // pair matches start
+                            bracket_pair_matching_end = Some(pair.clone());
+                        }
+                    }
+                    if let Some(end) = bracket_pair_matching_end
+                        && bracket_pair.is_none()
+                    {
+                        bracket_pair = Some(end);
+                        is_bracket_pair_end = true;
+                    }
+                }
+
+                if let Some(bracket_pair) = bracket_pair {
+                    let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
+                    let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
+                    let auto_surround =
+                        self.use_auto_surround && snapshot_settings.use_auto_surround;
+                    if selection.is_empty() {
+                        if is_bracket_pair_start {
+                            // If the inserted text is a suffix of an opening bracket and the
+                            // selection is preceded by the rest of the opening bracket, then
+                            // insert the closing bracket.
+                            let following_text_allows_autoclose = snapshot
+                                .chars_at(selection.start)
+                                .next()
+                                .is_none_or(|c| scope.should_autoclose_before(c));
+
+                            let preceding_text_allows_autoclose = selection.start.column == 0
+                                || snapshot
+                                    .reversed_chars_at(selection.start)
+                                    .next()
+                                    .is_none_or(|c| {
+                                        bracket_pair.start != bracket_pair.end
+                                            || !snapshot
+                                                .char_classifier_at(selection.start)
+                                                .is_word(c)
+                                    });
+
+                            let is_closing_quote = if bracket_pair.end == bracket_pair.start
+                                && bracket_pair.start.len() == 1
+                            {
+                                if let Some(target) = bracket_pair.start.chars().next() {
+                                    let mut byte_offset = 0u32;
+                                    let current_line_count = snapshot
+                                        .reversed_chars_at(selection.start)
+                                        .take_while(|&c| c != '\n')
+                                        .filter(|c| {
+                                            byte_offset += c.len_utf8() as u32;
+                                            if *c != target {
+                                                return false;
+                                            }
+
+                                            let point = Point::new(
+                                                selection.start.row,
+                                                selection.start.column.saturating_sub(byte_offset),
+                                            );
+
+                                            let is_enabled = snapshot
+                                                .language_scope_at(point)
+                                                .and_then(|scope| {
+                                                    scope
+                                                        .brackets()
+                                                        .find(|(pair, _)| {
+                                                            pair.start == bracket_pair.start
+                                                        })
+                                                        .map(|(_, enabled)| enabled)
+                                                })
+                                                .unwrap_or(true);
+
+                                            let is_delimiter = snapshot
+                                                .language_scope_at(Point::new(
+                                                    point.row,
+                                                    point.column + 1,
+                                                ))
+                                                .and_then(|scope| {
+                                                    scope
+                                                        .brackets()
+                                                        .find(|(pair, _)| {
+                                                            pair.start == bracket_pair.start
+                                                        })
+                                                        .map(|(_, enabled)| !enabled)
+                                                })
+                                                .unwrap_or(false);
+
+                                            is_enabled && !is_delimiter
+                                        })
+                                        .count();
+                                    current_line_count % 2 == 1
+                                } else {
+                                    false
+                                }
+                            } else {
+                                false
+                            };
+
+                            if autoclose
+                                && bracket_pair.close
+                                && following_text_allows_autoclose
+                                && preceding_text_allows_autoclose
+                                && !is_closing_quote
+                            {
+                                let anchor = snapshot.anchor_before(selection.end);
+                                new_selections.push((selection.map(|_| anchor), text.len()));
+                                new_autoclose_regions.push((
+                                    anchor,
+                                    text.len(),
+                                    selection.id,
+                                    bracket_pair.clone(),
+                                ));
+                                edits.push((
+                                    selection.range(),
+                                    format!("{}{}", text, bracket_pair.end).into(),
+                                ));
+                                bracket_inserted = true;
+                                continue;
+                            }
+                        }
+
+                        if let Some(region) = autoclose_region {
+                            // If the selection is followed by an auto-inserted closing bracket,
+                            // then don't insert that closing bracket again; just move the selection
+                            // past the closing bracket.
+                            let should_skip = selection.end == region.range.end.to_point(&snapshot)
+                                && text.as_ref() == region.pair.end.as_str()
+                                && snapshot.contains_str_at(region.range.end, text.as_ref());
+                            if should_skip {
+                                let anchor = snapshot.anchor_after(selection.end);
+                                new_selections
+                                    .push((selection.map(|_| anchor), region.pair.end.len()));
+                                continue;
+                            }
+                        }
+
+                        let always_treat_brackets_as_autoclosed = snapshot
+                            .language_settings_at(selection.start, cx)
+                            .always_treat_brackets_as_autoclosed;
+                        if always_treat_brackets_as_autoclosed
+                            && is_bracket_pair_end
+                            && snapshot.contains_str_at(selection.end, text.as_ref())
+                        {
+                            // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
+                            // and the inserted text is a closing bracket and the selection is followed
+                            // by the closing bracket then move the selection past the closing bracket.
+                            let anchor = snapshot.anchor_after(selection.end);
+                            new_selections.push((selection.map(|_| anchor), text.len()));
+                            continue;
+                        }
+                    }
+                    // If an opening bracket is 1 character long and is typed while
+                    // text is selected, then surround that text with the bracket pair.
+                    else if auto_surround
+                        && bracket_pair.surround
+                        && is_bracket_pair_start
+                        && bracket_pair.start.chars().count() == 1
+                    {
+                        edits.push((selection.start..selection.start, text.clone()));
+                        edits.push((
+                            selection.end..selection.end,
+                            bracket_pair.end.as_str().into(),
+                        ));
+                        bracket_inserted = true;
+                        new_selections.push((
+                            Selection {
+                                id: selection.id,
+                                start: snapshot.anchor_after(selection.start),
+                                end: snapshot.anchor_before(selection.end),
+                                reversed: selection.reversed,
+                                goal: selection.goal,
+                            },
+                            0,
+                        ));
+                        continue;
+                    }
+                }
+            }
+
+            if self.auto_replace_emoji_shortcode
+                && selection.is_empty()
+                && text.as_ref().ends_with(':')
+                && let Some(possible_emoji_short_code) =
+                    Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
+                && !possible_emoji_short_code.is_empty()
+                && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
+            {
+                let emoji_shortcode_start = Point::new(
+                    selection.start.row,
+                    selection.start.column - possible_emoji_short_code.len() as u32 - 1,
+                );
+
+                // Remove shortcode from buffer
+                edits.push((
+                    emoji_shortcode_start..selection.start,
+                    "".to_string().into(),
+                ));
+                new_selections.push((
+                    Selection {
+                        id: selection.id,
+                        start: snapshot.anchor_after(emoji_shortcode_start),
+                        end: snapshot.anchor_before(selection.start),
+                        reversed: selection.reversed,
+                        goal: selection.goal,
+                    },
+                    0,
+                ));
+
+                // Insert emoji
+                let selection_start_anchor = snapshot.anchor_after(selection.start);
+                new_selections.push((selection.map(|_| selection_start_anchor), 0));
+                edits.push((selection.start..selection.end, emoji.to_string().into()));
+
+                continue;
+            }
+
+            let next_is_adjacent = regions
+                .peek()
+                .is_some_and(|(next, _)| selection.end == next.start);
+
+            // If not handling any auto-close operation, then just replace the selected
+            // text with the given input and move the selection to the end of the
+            // newly inserted text.
+            let anchor = if in_adjacent_group || next_is_adjacent {
+                // After edits the right bias would shift those anchor to the next visible fragment
+                // but we want to resolve to the previous one
+                snapshot.anchor_before(selection.end)
+            } else {
+                snapshot.anchor_after(selection.end)
+            };
+
+            if !self.linked_edit_ranges.is_empty() {
+                let start_anchor = snapshot.anchor_before(selection.start);
+                let classifier = snapshot
+                    .char_classifier_at(start_anchor)
+                    .scope_context(Some(CharScopeContext::LinkedEdit));
+
+                if let Some((_, anchor_range)) =
+                    snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
+                {
+                    let is_word_char = text
+                        .chars()
+                        .next()
+                        .is_none_or(|char| classifier.is_word(char));
+
+                    let is_dot = text.as_ref() == ".";
+                    let should_apply_linked_edit = is_word_char || is_dot;
+
+                    if should_apply_linked_edit {
+                        linked_edits.push(&self, anchor_range, text.clone(), cx);
+                    } else {
+                        clear_linked_edit_ranges = true;
+                    }
+                }
+            }
+
+            new_selections.push((selection.map(|_| anchor), 0));
+            edits.push((selection.start..selection.end, text.clone()));
+
+            has_adjacent_edits |= next_is_adjacent;
+            in_adjacent_group = next_is_adjacent;
+        }
+
+        if all_selections_read_only {
+            return;
+        }
+
+        drop(regions);
+        drop(snapshot);
+
+        self.transact(window, cx, |this, window, cx| {
+            if clear_linked_edit_ranges {
+                this.linked_edit_ranges.clear();
+            }
+            let initial_buffer_versions =
+                jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
+
+            this.buffer.update(cx, |buffer, cx| {
+                if has_adjacent_edits {
+                    buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
+                } else {
+                    buffer.edit(edits, this.autoindent_mode.clone(), cx);
+                }
+            });
+            linked_edits.apply(cx);
+            let new_anchor_selections = new_selections.iter().map(|e| &e.0);
+            let new_selection_deltas = new_selections.iter().map(|e| e.1);
+            let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
+            let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
+                new_anchor_selections,
+                &map,
+            )
+            .zip(new_selection_deltas)
+            .map(|(selection, delta)| Selection {
+                id: selection.id,
+                start: selection.start + delta,
+                end: selection.end + delta,
+                reversed: selection.reversed,
+                goal: SelectionGoal::None,
+            })
+            .collect::<Vec<_>>();
+
+            let mut i = 0;
+            for (position, delta, selection_id, pair) in new_autoclose_regions {
+                let position = position.to_offset(map.buffer_snapshot()) + delta;
+                let start = map.buffer_snapshot().anchor_before(position);
+                let end = map.buffer_snapshot().anchor_after(position);
+                while let Some(existing_state) = this.autoclose_regions.get(i) {
+                    match existing_state
+                        .range
+                        .start
+                        .cmp(&start, map.buffer_snapshot())
+                    {
+                        Ordering::Less => i += 1,
+                        Ordering::Greater => break,
+                        Ordering::Equal => {
+                            match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
+                                Ordering::Less => i += 1,
+                                Ordering::Equal => break,
+                                Ordering::Greater => break,
+                            }
+                        }
+                    }
+                }
+                this.autoclose_regions.insert(
+                    i,
+                    AutocloseRegion {
+                        selection_id,
+                        range: start..end,
+                        pair,
+                    },
+                );
+            }
+
+            let had_active_edit_prediction = this.has_active_edit_prediction();
+            this.change_selections(
+                SelectionEffects::scroll(Autoscroll::fit()).completions(false),
+                window,
+                cx,
+                |s| s.select(new_selections),
+            );
+
+            if !bracket_inserted
+                && let Some(on_type_format_task) =
+                    this.trigger_on_type_formatting(text.to_string(), window, cx)
+            {
+                on_type_format_task.detach_and_log_err(cx);
+            }
+
+            let editor_settings = EditorSettings::get_global(cx);
+            if bracket_inserted
+                && (editor_settings.auto_signature_help
+                    || editor_settings.show_signature_help_after_edits)
+            {
+                this.show_signature_help(&ShowSignatureHelp, window, cx);
+            }
+
+            let trigger_in_words =
+                this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
+            if this.hard_wrap.is_some() {
+                let latest: Range<Point> = this.selections.newest(&map).range();
+                if latest.is_empty()
+                    && this
+                        .buffer()
+                        .read(cx)
+                        .snapshot(cx)
+                        .line_len(MultiBufferRow(latest.start.row))
+                        == latest.start.column
+                {
+                    this.rewrap(
+                        RewrapOptions {
+                            override_language_settings: true,
+                            preserve_existing_whitespace: true,
+                            line_length: None,
+                        },
+                        cx,
+                    )
+                }
+            }
+            this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
+            refresh_linked_ranges(this, window, cx);
+            this.refresh_edit_prediction(true, false, window, cx);
+            jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
+        });
+    }
+
+    pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
+        if self.read_only(cx) {
+            return;
+        }
+
+        self.transact(window, cx, |this, window, cx| {
+            let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
+                let selections = this
+                    .selections
+                    .all::<MultiBufferOffset>(&this.display_snapshot(cx));
+                let multi_buffer = this.buffer.read(cx);
+                let buffer = multi_buffer.snapshot(cx);
+                selections
+                    .iter()
+                    .map(|selection| {
+                        let start_point = selection.start.to_point(&buffer);
+                        let mut existing_indent =
+                            buffer.indent_size_for_line(MultiBufferRow(start_point.row));
+                        let full_indent_len = existing_indent.len;
+                        existing_indent.len = cmp::min(existing_indent.len, start_point.column);
+                        let mut start = selection.start;
+                        let end = selection.end;
+                        let selection_is_empty = start == end;
+                        let language_scope = buffer.language_scope_at(start);
+                        let (delimiter, newline_config) = if let Some(language) = &language_scope {
+                            let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
+                                &buffer,
+                                start..end,
+                                language,
+                            )
+                                || NewlineConfig::insert_extra_newline_tree_sitter(
+                                    &buffer,
+                                    start..end,
+                                );
+
+                            let mut newline_config = NewlineConfig::Newline {
+                                additional_indent: IndentSize::spaces(0),
+                                extra_line_additional_indent: if needs_extra_newline {
+                                    Some(IndentSize::spaces(0))
+                                } else {
+                                    None
+                                },
+                                prevent_auto_indent: false,
+                            };
+
+                            let comment_delimiter = maybe!({
+                                if !selection_is_empty {
+                                    return None;
+                                }
+
+                                if !multi_buffer.language_settings(cx).extend_comment_on_newline {
+                                    return None;
+                                }
+
+                                return comment_delimiter_for_newline(
+                                    &start_point,
+                                    &buffer,
+                                    language,
+                                );
+                            });
+
+                            let doc_delimiter = maybe!({
+                                if !selection_is_empty {
+                                    return None;
+                                }
+
+                                if !multi_buffer.language_settings(cx).extend_comment_on_newline {
+                                    return None;
+                                }
+
+                                return documentation_delimiter_for_newline(
+                                    &start_point,
+                                    &buffer,
+                                    language,
+                                    &mut newline_config,
+                                );
+                            });
+
+                            let list_delimiter = maybe!({
+                                if !selection_is_empty {
+                                    return None;
+                                }
+
+                                if !multi_buffer.language_settings(cx).extend_list_on_newline {
+                                    return None;
+                                }
+
+                                return list_delimiter_for_newline(
+                                    &start_point,
+                                    &buffer,
+                                    language,
+                                    &mut newline_config,
+                                );
+                            });
+
+                            (
+                                comment_delimiter.or(doc_delimiter).or(list_delimiter),
+                                newline_config,
+                            )
+                        } else {
+                            (
+                                None,
+                                NewlineConfig::Newline {
+                                    additional_indent: IndentSize::spaces(0),
+                                    extra_line_additional_indent: None,
+                                    prevent_auto_indent: false,
+                                },
+                            )
+                        };
+
+                        let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
+                            NewlineConfig::ClearCurrentLine => {
+                                let row_start =
+                                    buffer.point_to_offset(Point::new(start_point.row, 0));
+                                (row_start, String::new(), false)
+                            }
+                            NewlineConfig::UnindentCurrentLine { continuation } => {
+                                let row_start =
+                                    buffer.point_to_offset(Point::new(start_point.row, 0));
+                                let tab_size = buffer.language_settings_at(start, cx).tab_size;
+                                let tab_size_indent = IndentSize::spaces(tab_size.get());
+                                let reduced_indent =
+                                    existing_indent.with_delta(Ordering::Less, tab_size_indent);
+                                let mut new_text = String::new();
+                                new_text.extend(reduced_indent.chars());
+                                new_text.push_str(continuation);
+                                (row_start, new_text, true)
+                            }
+                            NewlineConfig::Newline {
+                                additional_indent,
+                                extra_line_additional_indent,
+                                prevent_auto_indent,
+                            } => {
+                                let auto_indent_mode =
+                                    buffer.language_settings_at(start, cx).auto_indent;
+                                let preserve_indent =
+                                    auto_indent_mode != language::AutoIndentMode::None;
+                                let apply_syntax_indent =
+                                    auto_indent_mode == language::AutoIndentMode::SyntaxAware;
+                                let capacity_for_delimiter =
+                                    delimiter.as_deref().map(str::len).unwrap_or_default();
+                                let existing_indent_len = if preserve_indent {
+                                    existing_indent.len as usize
+                                } else {
+                                    0
+                                };
+                                let extra_line_len = extra_line_additional_indent
+                                    .map(|i| 1 + existing_indent_len + i.len as usize)
+                                    .unwrap_or(0);
+                                let mut new_text = String::with_capacity(
+                                    1 + capacity_for_delimiter
+                                        + existing_indent_len
+                                        + additional_indent.len as usize
+                                        + extra_line_len,
+                                );
+                                new_text.push('\n');
+                                if preserve_indent {
+                                    new_text.extend(existing_indent.chars());
+                                }
+                                new_text.extend(additional_indent.chars());
+                                if let Some(delimiter) = &delimiter {
+                                    new_text.push_str(delimiter);
+                                }
+                                if let Some(extra_indent) = extra_line_additional_indent {
+                                    new_text.push('\n');
+                                    if preserve_indent {
+                                        new_text.extend(existing_indent.chars());
+                                    }
+                                    new_text.extend(extra_indent.chars());
+                                }
+                                // Extend the edit to the beginning of the line
+                                // to clear auto-indent whitespace that would
+                                // otherwise remain as trailing whitespace. This
+                                // applies to blank lines and lines where only
+                                // indentation remains before the cursor.
+                                if selection_is_empty
+                                    && preserve_indent
+                                    && full_indent_len > 0
+                                    && start_point.column == full_indent_len
+                                {
+                                    start = buffer.point_to_offset(Point::new(start_point.row, 0));
+                                }
+
+                                (
+                                    start,
+                                    new_text,
+                                    *prevent_auto_indent || !apply_syntax_indent,
+                                )
+                            }
+                        };
+
+                        let anchor = buffer.anchor_after(end);
+                        let new_selection = selection.map(|_| anchor);
+                        (
+                            ((edit_start..end, new_text), prevent_auto_indent),
+                            (newline_config.has_extra_line(), new_selection),
+                        )
+                    })
+                    .unzip()
+            };
+
+            let mut auto_indent_edits = Vec::new();
+            let mut edits = Vec::new();
+            for (edit, prevent_auto_indent) in edits_with_flags {
+                if prevent_auto_indent {
+                    edits.push(edit);
+                } else {
+                    auto_indent_edits.push(edit);
+                }
+            }
+            if !edits.is_empty() {
+                this.edit(edits, cx);
+            }
+            if !auto_indent_edits.is_empty() {
+                this.edit_with_autoindent(auto_indent_edits, cx);
+            }
+
+            let buffer = this.buffer.read(cx).snapshot(cx);
+            let new_selections = selection_info
+                .into_iter()
+                .map(|(extra_newline_inserted, new_selection)| {
+                    let mut cursor = new_selection.end.to_point(&buffer);
+                    if extra_newline_inserted {
+                        cursor.row -= 1;
+                        cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
+                    }
+                    new_selection.map(|_| cursor)
+                })
+                .collect();
+
+            this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
+            this.refresh_edit_prediction(true, false, window, cx);
+            if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
+                task.detach_and_log_err(cx);
+            }
+        });
+    }
+
+    pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
+        if self.read_only(cx) {
+            return;
+        }
+
+        let buffer = self.buffer.read(cx);
+        let snapshot = buffer.snapshot(cx);
+
+        let mut edits = Vec::new();
+        let mut rows = Vec::new();
+
+        for (rows_inserted, selection) in self
+            .selections
+            .all_adjusted(&self.display_snapshot(cx))
+            .into_iter()
+            .enumerate()
+        {
+            let cursor = selection.head();
+            let row = cursor.row;
+
+            let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
+
+            let newline = "\n".to_string();
+            edits.push((start_of_line..start_of_line, newline));
+
+            rows.push(row + rows_inserted as u32);
+        }
+
+        self.transact(window, cx, |editor, window, cx| {
+            editor.edit(edits, cx);
+
+            editor.change_selections(Default::default(), window, cx, |s| {
+                let mut index = 0;
+                s.move_cursors_with(&mut |map, _, _| {
+                    let row = rows[index];
+                    index += 1;
+
+                    let point = Point::new(row, 0);
+                    let boundary = map.next_line_boundary(point).1;
+                    let clipped = map.clip_point(boundary, Bias::Left);
+
+                    (clipped, SelectionGoal::None)
+                });
+            });
+
+            let mut indent_edits = Vec::new();
+            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
+            for row in rows {
+                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
+                for (row, indent) in indents {
+                    if indent.len == 0 {
+                        continue;
+                    }
+
+                    let text = match indent.kind {
+                        IndentKind::Space => " ".repeat(indent.len as usize),
+                        IndentKind::Tab => "\t".repeat(indent.len as usize),
+                    };
+                    let point = Point::new(row.0, 0);
+                    indent_edits.push((point..point, text));
+                }
+            }
+            editor.edit(indent_edits, cx);
+            if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
+                format.detach_and_log_err(cx);
+            }
+        });
+    }
+
+    pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
+        if self.read_only(cx) {
+            return;
+        }
+
+        let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
+        let mut rows = Vec::new();
+        let mut rows_inserted = 0;
+
+        for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
+            let cursor = selection.head();
+            let row = cursor.row;
+
+            let point = Point::new(row, 0);
+            let Some((buffer_handle, buffer_point)) =
+                self.buffer.read(cx).point_to_buffer_point(point, cx)
+            else {
+                continue;
+            };
+
+            buffer_edits
+                .entry(buffer_handle.entity_id())
+                .or_insert_with(|| (buffer_handle, Vec::new()))
+                .1
+                .push(buffer_point);
+
+            rows_inserted += 1;
+            rows.push(row + rows_inserted);
+        }
+
+        self.transact(window, cx, |editor, window, cx| {
+            for (_, (buffer_handle, points)) in &buffer_edits {
+                buffer_handle.update(cx, |buffer, cx| {
+                    let edits: Vec<_> = points
+                        .iter()
+                        .map(|point| {
+                            let target = Point::new(point.row + 1, 0);
+                            let start_of_line = buffer.point_to_offset(target).min(buffer.len());
+                            (start_of_line..start_of_line, "\n")
+                        })
+                        .collect();
+                    buffer.edit(edits, None, cx);
+                });
+            }
+
+            editor.change_selections(Default::default(), window, cx, |s| {
+                let mut index = 0;
+                s.move_cursors_with(&mut |map, _, _| {
+                    let row = rows[index];
+                    index += 1;
+
+                    let point = Point::new(row, 0);
+                    let boundary = map.next_line_boundary(point).1;
+                    let clipped = map.clip_point(boundary, Bias::Left);
+
+                    (clipped, SelectionGoal::None)
+                });
+            });
+
+            let mut indent_edits = Vec::new();
+            let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
+            for row in rows {
+                let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
+                for (row, indent) in indents {
+                    if indent.len == 0 {
+                        continue;
+                    }
+
+                    let text = match indent.kind {
+                        IndentKind::Space => " ".repeat(indent.len as usize),
+                        IndentKind::Tab => "\t".repeat(indent.len as usize),
+                    };
+                    let point = Point::new(row.0, 0);
+                    indent_edits.push((point..point, text));
+                }
+            }
+            editor.edit(indent_edits, cx);
+            if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
+                format.detach_and_log_err(cx);
+            }
+        });
+    }
+
+    pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
+        let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
+            original_indent_columns: Vec::new(),
+        });
+        self.replace_selections(text, autoindent, window, cx, false);
+    }
+
+    /// Collects linked edits for the current selections, pairing each linked
+    /// range with `text`.
+    pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
+        let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
+        let mut linked_edits = LinkedEdits::new();
+        if !self.linked_edit_ranges.is_empty() {
+            for selection in self.selections.disjoint_anchors() {
+                let Some((_, range)) =
+                    multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
+                else {
+                    continue;
+                };
+                linked_edits.push(self, range, text.clone(), cx);
+            }
+        }
+        linked_edits
+    }
+
+    /// Deletes the content covered by the current selections and applies
+    /// linked edits.
+    pub fn delete_selections_with_linked_edits(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.replace_selections("", None, window, cx, true);
+    }
+
+    pub(super) fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        let mut pending: String = window
+            .pending_input_keystrokes()
+            .into_iter()
+            .flatten()
+            .filter_map(|keystroke| keystroke.key_char.clone())
+            .collect();
+
+        if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
+            pending = "".to_string();
+        }
+
+        let existing_pending = self
+            .text_highlights(HighlightKey::PendingInput, cx)
+            .map(|(_, ranges)| ranges.to_vec());
+        if existing_pending.is_none() && pending.is_empty() {
+            return;
+        }
+        let transaction =
+            self.transact(window, cx, |this, window, cx| {
+                let selections = this
+                    .selections
+                    .all::<MultiBufferOffset>(&this.display_snapshot(cx));
+                let edits = selections
+                    .iter()
+                    .map(|selection| (selection.end..selection.end, pending.clone()));
+                this.edit(edits, cx);
+                this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                    s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
+                        sel.start + ix * pending.len()..sel.end + ix * pending.len()
+                    }));
+                });
+                if let Some(existing_ranges) = existing_pending {
+                    let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
+                    this.edit(edits, cx);
+                }
+            });
+
+        let snapshot = self.snapshot(window, cx);
+        let ranges = self
+            .selections
+            .all::<MultiBufferOffset>(&snapshot.display_snapshot)
+            .into_iter()
+            .map(|selection| {
+                snapshot.buffer_snapshot().anchor_after(selection.end)
+                    ..snapshot
+                        .buffer_snapshot()
+                        .anchor_before(selection.end + pending.len())
+            })
+            .collect();
+
+        if pending.is_empty() {
+            self.clear_highlights(HighlightKey::PendingInput, cx);
+        } else {
+            self.highlight_text(
+                HighlightKey::PendingInput,
+                ranges,
+                HighlightStyle {
+                    underline: Some(UnderlineStyle {
+                        thickness: px(1.),
+                        color: None,
+                        wavy: false,
+                    }),
+                    ..Default::default()
+                },
+                cx,
+            );
+        }
+
+        self.ime_transaction = self.ime_transaction.or(transaction);
+        if let Some(transaction) = self.ime_transaction {
+            self.buffer.update(cx, |buffer, cx| {
+                buffer.group_until_transaction(transaction, cx);
+            });
+        }
+
+        if self
+            .text_highlights(HighlightKey::PendingInput, cx)
+            .is_none()
+        {
+            self.ime_transaction.take();
+        }
+    }
+
+    pub(super) fn linked_editing_ranges_for(
+        &self,
+        query_range: Range<text::Anchor>,
+        cx: &App,
+    ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
+        use text::ToOffset as TO;
+
+        if self.linked_edit_ranges.is_empty() {
+            return None;
+        }
+        if query_range.start.buffer_id != query_range.end.buffer_id {
+            return None;
+        };
+        let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
+        let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
+        let buffer_snapshot = buffer.read(cx).snapshot();
+        let (base_range, linked_ranges) = self.linked_edit_ranges.get(
+            buffer_snapshot.remote_id(),
+            query_range.clone(),
+            &buffer_snapshot,
+        )?;
+        // find offset from the start of current range to current cursor position
+        let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
+
+        let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
+        let start_difference = start_offset - start_byte_offset;
+        let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
+        let end_difference = end_offset - start_byte_offset;
+
+        // Current range has associated linked ranges.
+        let mut linked_edits = HashMap::<_, Vec<_>>::default();
+        for range in linked_ranges.iter() {
+            let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
+            let end_offset = start_offset + end_difference;
+            let start_offset = start_offset + start_difference;
+            if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
+                continue;
+            }
+            if self.selections.disjoint_anchor_ranges().any(|s| {
+                let Some((selection_start, _)) =
+                    multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
+                else {
+                    return false;
+                };
+                let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
+                else {
+                    return false;
+                };
+                if selection_start.buffer_id != query_range.start.buffer_id
+                    || selection_end.buffer_id != query_range.end.buffer_id
+                {
+                    return false;
+                }
+                TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
+                    && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
+            }) {
+                continue;
+            }
+            let start = buffer_snapshot.anchor_after(start_offset);
+            let end = buffer_snapshot.anchor_after(end_offset);
+            linked_edits
+                .entry(buffer.clone())
+                .or_default()
+                .push(start..end);
+        }
+        Some(linked_edits)
+    }
+
+    pub(super) fn marked_text_ranges(
+        &self,
+        cx: &App,
+    ) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
+        let snapshot = self.buffer.read(cx).read(cx);
+        let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
+        Some(
+            ranges
+                .iter()
+                .map(move |range| {
+                    range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
+                })
+                .collect(),
+        )
+    }
+
+    /// Replaces the editor's selections with the provided `text`, applying the
+    /// given `autoindent_mode` (`None` will skip autoindentation).
+    ///
+    /// Early returns if the editor is in read-only mode, without applying any
+    /// edits.
+    pub(super) fn replace_selections(
+        &mut self,
+        text: &str,
+        autoindent_mode: Option<AutoindentMode>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+        apply_linked_edits: bool,
+    ) {
+        if self.read_only(cx) {
+            return;
+        }
+
+        let text: Arc<str> = text.into();
+        self.transact(window, cx, |this, window, cx| {
+            let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
+            let linked_edits = if apply_linked_edits {
+                this.linked_edits_for_selections(text.clone(), cx)
+            } else {
+                LinkedEdits::new()
+            };
+
+            let selection_anchors = this.buffer.update(cx, |buffer, cx| {
+                let anchors = {
+                    let snapshot = buffer.read(cx);
+                    old_selections
+                        .iter()
+                        .map(|s| {
+                            let anchor = snapshot.anchor_after(s.head());
+                            s.map(|_| anchor)
+                        })
+                        .collect::<Vec<_>>()
+                };
+                buffer.edit(
+                    old_selections
+                        .iter()
+                        .map(|s| (s.start..s.end, text.clone())),
+                    autoindent_mode,
+                    cx,
+                );
+                anchors
+            });
+
+            linked_edits.apply(cx);
+
+            this.change_selections(Default::default(), window, cx, |s| {
+                s.select_anchors(selection_anchors);
+            });
+
+            if apply_linked_edits {
+                refresh_linked_ranges(this, window, cx);
+            }
+
+            cx.notify();
+        });
+    }
+
+    /// If any empty selections is touching the start of its innermost containing autoclose
+    /// region, expand it to select the brackets.
+    pub(super) fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        let selections = self
+            .selections
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx));
+        let buffer = self.buffer.read(cx).read(cx);
+        let new_selections = self
+            .selections_with_autoclose_regions(selections, &buffer)
+            .map(|(mut selection, region)| {
+                if !selection.is_empty() {
+                    return selection;
+                }
+
+                if let Some(region) = region {
+                    let mut range = region.range.to_offset(&buffer);
+                    if selection.start == range.start && range.start.0 >= region.pair.start.len() {
+                        range.start -= region.pair.start.len();
+                        if buffer.contains_str_at(range.start, &region.pair.start)
+                            && buffer.contains_str_at(range.end, &region.pair.end)
+                        {
+                            range.end += region.pair.end.len();
+                            selection.start = range.start;
+                            selection.end = range.end;
+
+                            return selection;
+                        }
+                    }
+                }
+
+                let always_treat_brackets_as_autoclosed = buffer
+                    .language_settings_at(selection.start, cx)
+                    .always_treat_brackets_as_autoclosed;
+
+                if !always_treat_brackets_as_autoclosed {
+                    return selection;
+                }
+
+                if let Some(scope) = buffer.language_scope_at(selection.start) {
+                    for (pair, enabled) in scope.brackets() {
+                        if !enabled || !pair.close {
+                            continue;
+                        }
+
+                        if buffer.contains_str_at(selection.start, &pair.end) {
+                            let pair_start_len = pair.start.len();
+                            if buffer.contains_str_at(
+                                selection.start.saturating_sub_usize(pair_start_len),
+                                &pair.start,
+                            ) {
+                                selection.start -= pair_start_len;
+                                selection.end += pair.end.len();
+
+                                return selection;
+                            }
+                        }
+                    }
+                }
+
+                selection
+            })
+            .collect();
+
+        drop(buffer);
+        self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
+            selections.select(new_selections)
+        });
+    }
+
+    /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
+    pub(super) fn invalidate_autoclose_regions(
+        &mut self,
+        mut selections: &[Selection<Anchor>],
+        buffer: &MultiBufferSnapshot,
+    ) {
+        self.autoclose_regions.retain(|state| {
+            if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
+                return false;
+            }
+
+            let mut i = 0;
+            while let Some(selection) = selections.get(i) {
+                if selection.end.cmp(&state.range.start, buffer).is_lt() {
+                    selections = &selections[1..];
+                    continue;
+                }
+                if selection.start.cmp(&state.range.end, buffer).is_gt() {
+                    break;
+                }
+                if selection.id == state.selection_id {
+                    return true;
+                } else {
+                    i += 1;
+                }
+            }
+            false
+        });
+    }
+
+    fn set_use_auto_surround(&mut self, auto_surround: bool) {
+        self.use_auto_surround = auto_surround;
+    }
+
+    fn find_possible_emoji_shortcode_at_position(
+        snapshot: &MultiBufferSnapshot,
+        position: Point,
+    ) -> Option<String> {
+        let mut chars = Vec::new();
+        let mut found_colon = false;
+        for char in snapshot.reversed_chars_at(position).take(100) {
+            // Found a possible emoji shortcode in the middle of the buffer
+            if found_colon {
+                if char.is_whitespace() {
+                    chars.reverse();
+                    return Some(chars.iter().collect());
+                }
+                // If the previous character is not a whitespace, we are in the middle of a word
+                // and we only want to complete the shortcode if the word is made up of other emojis
+                let mut containing_word = String::new();
+                for ch in snapshot
+                    .reversed_chars_at(position)
+                    .skip(chars.len() + 1)
+                    .take(100)
+                {
+                    if ch.is_whitespace() {
+                        break;
+                    }
+                    containing_word.push(ch);
+                }
+                let containing_word = containing_word.chars().rev().collect::<String>();
+                if util::word_consists_of_emojis(containing_word.as_str()) {
+                    chars.reverse();
+                    return Some(chars.iter().collect());
+                }
+            }
+
+            if char.is_whitespace() || !char.is_ascii() {
+                return None;
+            }
+            if char == ':' {
+                found_colon = true;
+            } else {
+                chars.push(char);
+            }
+        }
+        // Found a possible emoji shortcode at the beginning of the buffer
+        chars.reverse();
+        Some(chars.iter().collect())
+    }
+
+    /// Iterate the given selections, and for each one, find the smallest surrounding
+    /// autoclose region. This uses the ordering of the selections and the autoclose
+    /// regions to avoid repeated comparisons.
+    fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
+        &'a self,
+        selections: impl IntoIterator<Item = Selection<D>>,
+        buffer: &'a MultiBufferSnapshot,
+    ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
+        let mut i = 0;
+        let mut regions = self.autoclose_regions.as_slice();
+        selections.into_iter().map(move |selection| {
+            let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
+
+            let mut enclosing = None;
+            while let Some(pair_state) = regions.get(i) {
+                if pair_state.range.end.to_offset(buffer) < range.start {
+                    regions = &regions[i + 1..];
+                    i = 0;
+                } else if pair_state.range.start.to_offset(buffer) > range.end {
+                    break;
+                } else {
+                    if pair_state.selection_id == selection.id {
+                        enclosing = Some(pair_state);
+                    }
+                    i += 1;
+                }
+            }
+
+            (selection, enclosing)
+        })
+    }
+}
+
+#[cfg(any(test, feature = "test-support"))]
+impl Editor {
+    pub fn set_linked_edit_ranges_for_testing(
+        &mut self,
+        ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
+        cx: &mut Context<Self>,
+    ) -> Option<()> {
+        let Some((buffer, _)) = self
+            .buffer
+            .read(cx)
+            .text_anchor_for_position(self.selections.newest_anchor().start, cx)
+        else {
+            return None;
+        };
+        let buffer = buffer.read(cx);
+        let buffer_id = buffer.remote_id();
+        let mut linked_ranges = Vec::with_capacity(ranges.len());
+        for (base_range, linked_ranges_points) in ranges {
+            let base_anchor =
+                buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
+            let linked_anchors = linked_ranges_points
+                .into_iter()
+                .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
+                .collect();
+            linked_ranges.push((base_anchor, linked_anchors));
+        }
+        let mut map = HashMap::default();
+        map.insert(buffer_id, linked_ranges);
+        self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
+        Some(())
+    }
+
+    #[cfg(test)]
+    pub(super) fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
+        self.auto_replace_emoji_shortcode = auto_replace;
+    }
+}
+
+pub(super) fn is_list_prefix_row(
+    row: MultiBufferRow,
+    buffer: &MultiBufferSnapshot,
+    language: &LanguageScope,
+) -> bool {
+    let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
+        return false;
+    };
+
+    let num_of_whitespaces = snapshot
+        .chars_for_range(range.clone())
+        .take_while(|c| c.is_whitespace())
+        .count();
+
+    let task_list_prefixes: Vec<_> = language
+        .task_list()
+        .into_iter()
+        .flat_map(|config| {
+            config
+                .prefixes
+                .iter()
+                .map(|p| p.as_ref())
+                .collect::<Vec<_>>()
+        })
+        .collect();
+    let unordered_list_markers: Vec<_> = language
+        .unordered_list()
+        .iter()
+        .map(|marker| marker.as_ref())
+        .collect();
+    let all_prefixes: Vec<_> = task_list_prefixes
+        .into_iter()
+        .chain(unordered_list_markers)
+        .collect();
+    if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
+        let candidate: String = snapshot
+            .chars_for_range(range.clone())
+            .skip(num_of_whitespaces)
+            .take(max_prefix_len)
+            .collect();
+        if all_prefixes
+            .iter()
+            .any(|prefix| candidate.starts_with(*prefix))
+        {
+            return true;
+        }
+    }
+
+    let ordered_list_candidate: String = snapshot
+        .chars_for_range(range)
+        .skip(num_of_whitespaces)
+        .take(ORDERED_LIST_MAX_MARKER_LEN)
+        .collect();
+    for ordered_config in language.ordered_list() {
+        let regex = match Regex::new(&ordered_config.pattern) {
+            Ok(r) => r,
+            Err(_) => continue,
+        };
+        if let Some(captures) = regex.captures(&ordered_list_candidate) {
+            return captures.get(0).is_some();
+        }
+    }
+
+    false
+}
+
+#[derive(Debug)]
+enum NewlineConfig {
+    /// Insert newline with optional additional indent and optional extra blank line
+    Newline {
+        additional_indent: IndentSize,
+        extra_line_additional_indent: Option<IndentSize>,
+        prevent_auto_indent: bool,
+    },
+    /// Clear the current line
+    ClearCurrentLine,
+    /// Unindent the current line and add continuation
+    UnindentCurrentLine { continuation: Arc<str> },
+}
+
+impl NewlineConfig {
+    fn has_extra_line(&self) -> bool {
+        matches!(
+            self,
+            Self::Newline {
+                extra_line_additional_indent: Some(_),
+                ..
+            }
+        )
+    }
+
+    fn insert_extra_newline_brackets(
+        buffer: &MultiBufferSnapshot,
+        range: Range<MultiBufferOffset>,
+        language: &language::LanguageScope,
+    ) -> bool {
+        let leading_whitespace_len = buffer
+            .reversed_chars_at(range.start)
+            .take_while(|c| c.is_whitespace() && *c != '\n')
+            .map(|c| c.len_utf8())
+            .sum::<usize>();
+        let trailing_whitespace_len = buffer
+            .chars_at(range.end)
+            .take_while(|c| c.is_whitespace() && *c != '\n')
+            .map(|c| c.len_utf8())
+            .sum::<usize>();
+        let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
+
+        language.brackets().any(|(pair, enabled)| {
+            let pair_start = pair.start.trim_end();
+            let pair_end = pair.end.trim_start();
+
+            enabled
+                && pair.newline
+                && buffer.contains_str_at(range.end, pair_end)
+                && buffer.contains_str_at(
+                    range.start.saturating_sub_usize(pair_start.len()),
+                    pair_start,
+                )
+        })
+    }
+
+    fn insert_extra_newline_tree_sitter(
+        buffer: &MultiBufferSnapshot,
+        range: Range<MultiBufferOffset>,
+    ) -> bool {
+        let (buffer, range) = match buffer
+            .range_to_buffer_ranges(range.start..range.end)
+            .as_slice()
+        {
+            [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()),
+            _ => return false,
+        };
+        let pair = {
+            let mut result: Option<BracketMatch<usize>> = None;
+
+            for pair in buffer
+                .all_bracket_ranges(range.start.0..range.end.0)
+                .filter(move |pair| {
+                    pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
+                })
+            {
+                let len = pair.close_range.end - pair.open_range.start;
+
+                if let Some(existing) = &result {
+                    let existing_len = existing.close_range.end - existing.open_range.start;
+                    if len > existing_len {
+                        continue;
+                    }
+                }
+
+                result = Some(pair);
+            }
+
+            result
+        };
+        let Some(pair) = pair else {
+            return false;
+        };
+        pair.newline_only
+            && buffer
+                .chars_for_range(pair.open_range.end..range.start.0)
+                .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
+                .all(|c| c.is_whitespace() && c != '\n')
+    }
+}
+
+fn comment_delimiter_for_newline(
+    start_point: &Point,
+    buffer: &MultiBufferSnapshot,
+    language: &LanguageScope,
+) -> Option<Arc<str>> {
+    let delimiters = language.line_comment_prefixes();
+    let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
+    let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
+
+    let num_of_whitespaces = snapshot
+        .chars_for_range(range.clone())
+        .take_while(|c| c.is_whitespace())
+        .count();
+    let comment_candidate = snapshot
+        .chars_for_range(range.clone())
+        .skip(num_of_whitespaces)
+        .take(max_len_of_delimiter + 2)
+        .collect::<String>();
+    let (delimiter, trimmed_len, is_repl) = delimiters
+        .iter()
+        .filter_map(|delimiter| {
+            let prefix = delimiter.trim_end();
+            if comment_candidate.starts_with(prefix) {
+                let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
+                {
+                    stripped_comment.starts_with(" %%")
+                } else {
+                    false
+                };
+                Some((delimiter, prefix.len(), is_repl))
+            } else {
+                None
+            }
+        })
+        .max_by_key(|(_, len, _)| *len)?;
+
+    if let Some(BlockCommentConfig {
+        start: block_start, ..
+    }) = language.block_comment()
+    {
+        let block_start_trimmed = block_start.trim_end();
+        if block_start_trimmed.starts_with(delimiter.trim_end()) {
+            let line_content = snapshot
+                .chars_for_range(range.clone())
+                .skip(num_of_whitespaces)
+                .take(block_start_trimmed.len())
+                .collect::<String>();
+
+            if line_content.starts_with(block_start_trimmed) {
+                return None;
+            }
+        }
+    }
+
+    let cursor_is_placed_after_comment_marker =
+        num_of_whitespaces + trimmed_len <= start_point.column as usize;
+    if cursor_is_placed_after_comment_marker {
+        if !is_repl {
+            return Some(delimiter.clone());
+        }
+
+        let line_content_after_cursor: String = snapshot
+            .chars_for_range(range)
+            .skip(start_point.column as usize)
+            .collect();
+
+        if line_content_after_cursor.trim().is_empty() {
+            return None;
+        } else {
+            return Some(delimiter.clone());
+        }
+    } else {
+        None
+    }
+}
+
+fn documentation_delimiter_for_newline(
+    start_point: &Point,
+    buffer: &MultiBufferSnapshot,
+    language: &LanguageScope,
+    newline_config: &mut NewlineConfig,
+) -> Option<Arc<str>> {
+    let BlockCommentConfig {
+        start: start_tag,
+        end: end_tag,
+        prefix: delimiter,
+        tab_size: len,
+    } = language.documentation_comment()?;
+    let is_within_block_comment = buffer
+        .language_scope_at(*start_point)
+        .is_some_and(|scope| scope.override_name() == Some("comment"));
+    if !is_within_block_comment {
+        return None;
+    }
+
+    let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
+
+    let num_of_whitespaces = snapshot
+        .chars_for_range(range.clone())
+        .take_while(|c| c.is_whitespace())
+        .count();
+
+    // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
+    let column = start_point.column;
+    let cursor_is_after_start_tag = {
+        let start_tag_len = start_tag.len();
+        let start_tag_line = snapshot
+            .chars_for_range(range.clone())
+            .skip(num_of_whitespaces)
+            .take(start_tag_len)
+            .collect::<String>();
+        if start_tag_line.starts_with(start_tag.as_ref()) {
+            num_of_whitespaces + start_tag_len <= column as usize
+        } else {
+            false
+        }
+    };
+
+    let cursor_is_after_delimiter = {
+        let delimiter_trim = delimiter.trim_end();
+        let delimiter_line = snapshot
+            .chars_for_range(range.clone())
+            .skip(num_of_whitespaces)
+            .take(delimiter_trim.len())
+            .collect::<String>();
+        if delimiter_line.starts_with(delimiter_trim) {
+            num_of_whitespaces + delimiter_trim.len() <= column as usize
+        } else {
+            false
+        }
+    };
+
+    let mut needs_extra_line = false;
+    let mut extra_line_additional_indent = IndentSize::spaces(0);
+
+    let cursor_is_before_end_tag_if_exists = {
+        let mut char_position = 0u32;
+        let mut end_tag_offset = None;
+
+        'outer: for chunk in snapshot.text_for_range(range) {
+            if let Some(byte_pos) = chunk.find(&**end_tag) {
+                let chars_before_match = chunk[..byte_pos].chars().count() as u32;
+                end_tag_offset = Some(char_position + chars_before_match);
+                break 'outer;
+            }
+            char_position += chunk.chars().count() as u32;
+        }
+
+        if let Some(end_tag_offset) = end_tag_offset {
+            let cursor_is_before_end_tag = column <= end_tag_offset;
+            if cursor_is_after_start_tag {
+                if cursor_is_before_end_tag {
+                    needs_extra_line = true;
+                }
+                let cursor_is_at_start_of_end_tag = column == end_tag_offset;
+                if cursor_is_at_start_of_end_tag {
+                    extra_line_additional_indent.len = *len;
+                }
+            }
+            cursor_is_before_end_tag
+        } else {
+            true
+        }
+    };
+
+    if (cursor_is_after_start_tag || cursor_is_after_delimiter)
+        && cursor_is_before_end_tag_if_exists
+    {
+        let additional_indent = if cursor_is_after_start_tag {
+            IndentSize::spaces(*len)
+        } else {
+            IndentSize::spaces(0)
+        };
+
+        *newline_config = NewlineConfig::Newline {
+            additional_indent,
+            extra_line_additional_indent: if needs_extra_line {
+                Some(extra_line_additional_indent)
+            } else {
+                None
+            },
+            prevent_auto_indent: true,
+        };
+        Some(delimiter.clone())
+    } else {
+        None
+    }
+}
+
+fn list_delimiter_for_newline(
+    start_point: &Point,
+    buffer: &MultiBufferSnapshot,
+    language: &LanguageScope,
+    newline_config: &mut NewlineConfig,
+) -> Option<Arc<str>> {
+    let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
+
+    let num_of_whitespaces = snapshot
+        .chars_for_range(range.clone())
+        .take_while(|c| c.is_whitespace())
+        .count();
+
+    let task_list_entries: Vec<_> = language
+        .task_list()
+        .into_iter()
+        .flat_map(|config| {
+            config
+                .prefixes
+                .iter()
+                .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
+        })
+        .collect();
+    let unordered_list_entries: Vec<_> = language
+        .unordered_list()
+        .iter()
+        .map(|marker| (marker.as_ref(), marker.as_ref()))
+        .collect();
+
+    let all_entries: Vec<_> = task_list_entries
+        .into_iter()
+        .chain(unordered_list_entries)
+        .collect();
+
+    if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
+        let candidate: String = snapshot
+            .chars_for_range(range.clone())
+            .skip(num_of_whitespaces)
+            .take(max_prefix_len)
+            .collect();
+
+        if let Some((prefix, continuation)) = all_entries
+            .iter()
+            .filter(|(prefix, _)| candidate.starts_with(*prefix))
+            .max_by_key(|(prefix, _)| prefix.len())
+        {
+            let end_of_prefix = num_of_whitespaces + prefix.len();
+            let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
+            let has_content_after_marker = snapshot
+                .chars_for_range(range)
+                .skip(end_of_prefix)
+                .any(|c| !c.is_whitespace());
+
+            if has_content_after_marker && cursor_is_after_prefix {
+                return Some((*continuation).into());
+            }
+
+            if start_point.column as usize == end_of_prefix {
+                if num_of_whitespaces == 0 {
+                    *newline_config = NewlineConfig::ClearCurrentLine;
+                } else {
+                    *newline_config = NewlineConfig::UnindentCurrentLine {
+                        continuation: (*continuation).into(),
+                    };
+                }
+            }
+
+            return None;
+        }
+    }
+
+    let candidate: String = snapshot
+        .chars_for_range(range.clone())
+        .skip(num_of_whitespaces)
+        .take(ORDERED_LIST_MAX_MARKER_LEN)
+        .collect();
+
+    for ordered_config in language.ordered_list() {
+        let regex = match Regex::new(&ordered_config.pattern) {
+            Ok(r) => r,
+            Err(_) => continue,
+        };
+
+        if let Some(captures) = regex.captures(&candidate) {
+            let full_match = captures.get(0)?;
+            let marker_len = full_match.len();
+            let end_of_prefix = num_of_whitespaces + marker_len;
+            let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
+
+            let has_content_after_marker = snapshot
+                .chars_for_range(range)
+                .skip(end_of_prefix)
+                .any(|c| !c.is_whitespace());
+
+            if has_content_after_marker && cursor_is_after_prefix {
+                let number: u32 = captures.get(1)?.as_str().parse().ok()?;
+                let continuation = ordered_config
+                    .format
+                    .replace("{1}", &(number + 1).to_string());
+                return Some(continuation.into());
+            }
+
+            if start_point.column as usize == end_of_prefix {
+                let continuation = ordered_config.format.replace("{1}", "1");
+                if num_of_whitespaces == 0 {
+                    *newline_config = NewlineConfig::ClearCurrentLine;
+                } else {
+                    *newline_config = NewlineConfig::UnindentCurrentLine {
+                        continuation: continuation.into(),
+                    };
+                }
+            }
+
+            return None;
+        }
+    }
+
+    None
+}
+
+impl EntityInputHandler for Editor {
+    fn text_for_range(
+        &mut self,
+        range_utf16: Range<usize>,
+        adjusted_range: &mut Option<Range<usize>>,
+        _: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<String> {
+        let snapshot = self.buffer.read(cx).read(cx);
+        let start = snapshot.clip_offset_utf16(
+            MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
+            Bias::Left,
+        );
+        let end = snapshot.clip_offset_utf16(
+            MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
+            Bias::Right,
+        );
+        if (start.0.0..end.0.0) != range_utf16 {
+            adjusted_range.replace(start.0.0..end.0.0);
+        }
+        Some(snapshot.text_for_range(start..end).collect())
+    }
+
+    fn selected_text_range(
+        &mut self,
+        ignore_disabled_input: bool,
+        _: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<UTF16Selection> {
+        // Prevent the IME menu from appearing when holding down an alphabetic key
+        // while input is disabled.
+        if !ignore_disabled_input && !self.input_enabled {
+            return None;
+        }
+
+        let selection = self
+            .selections
+            .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
+        let range = selection.range();
+
+        Some(UTF16Selection {
+            range: range.start.0.0..range.end.0.0,
+            reversed: selection.reversed,
+        })
+    }
+
+    fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
+        let snapshot = self.buffer.read(cx).read(cx);
+        let range = self
+            .text_highlights(HighlightKey::InputComposition, cx)?
+            .1
+            .first()?;
+        Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
+    }
+
+    fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
+        self.clear_highlights(HighlightKey::InputComposition, cx);
+        self.ime_transaction.take();
+    }
+
+    fn replace_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        text: &str,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if !self.input_enabled {
+            cx.emit(EditorEvent::InputIgnored { text: text.into() });
+            return;
+        }
+
+        self.transact(window, cx, |this, window, cx| {
+            let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
+                if let Some(marked_ranges) = this.marked_text_ranges(cx) {
+                    // During IME composition, macOS reports the replacement range
+                    // relative to the first marked region (the only one visible via
+                    // marked_text_range). The correct targets for replacement are the
+                    // marked ranges themselves — one per cursor — so use them directly.
+                    Some(marked_ranges)
+                } else if range_utf16.start == range_utf16.end {
+                    // An empty replacement range means "insert at cursor" with no text
+                    // to replace. macOS reports the cursor position from its own
+                    // (single-cursor) view of the buffer, which diverges from our actual
+                    // cursor positions after multi-cursor edits have shifted offsets.
+                    // Treating this as range_utf16=None lets each cursor insert in place.
+                    None
+                } else {
+                    // Outside of IME composition (e.g. Accessibility Keyboard word
+                    // completion), the range is an absolute document offset for the
+                    // newest cursor. Fan it out to all cursors via
+                    // selection_replacement_ranges, which applies the delta relative
+                    // to the newest selection to every cursor.
+                    let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
+                        ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
+                    Some(this.selection_replacement_ranges(range_utf16, cx))
+                }
+            } else {
+                this.marked_text_ranges(cx)
+            };
+
+            let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
+                let newest_selection_id = this.selections.newest_anchor().id;
+                this.selections
+                    .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
+                    .iter()
+                    .zip(ranges_to_replace.iter())
+                    .find_map(|(selection, range)| {
+                        if selection.id == newest_selection_id {
+                            Some(
+                                (range.start.0.0 as isize - selection.head().0.0 as isize)
+                                    ..(range.end.0.0 as isize - selection.head().0.0 as isize),
+                            )
+                        } else {
+                            None
+                        }
+                    })
+            });
+
+            cx.emit(EditorEvent::InputHandled {
+                utf16_range_to_replace: range_to_replace,
+                text: text.into(),
+            });
+
+            if let Some(new_selected_ranges) = new_selected_ranges {
+                // Only backspace if at least one range covers actual text. When all
+                // ranges are empty (e.g. a trailing-space insertion from Accessibility
+                // Keyboard sends replacementRange=cursor..cursor), backspace would
+                // incorrectly delete the character just before the cursor.
+                let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
+                this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
+                    selections.select_ranges(new_selected_ranges)
+                });
+                if should_backspace {
+                    this.backspace(&Default::default(), window, cx);
+                }
+            }
+
+            this.handle_input(text, window, cx);
+        });
+
+        if let Some(transaction) = self.ime_transaction {
+            self.buffer.update(cx, |buffer, cx| {
+                buffer.group_until_transaction(transaction, cx);
+            });
+        }
+
+        self.unmark_text(window, cx);
+    }
+
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        text: &str,
+        new_selected_range_utf16: Option<Range<usize>>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if !self.input_enabled {
+            return;
+        }
+
+        let transaction = self.transact(window, cx, |this, window, cx| {
+            let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
+                let snapshot = this.buffer.read(cx).read(cx);
+                if let Some(relative_range_utf16) = range_utf16.as_ref() {
+                    for marked_range in &mut marked_ranges {
+                        marked_range.end = marked_range.start + relative_range_utf16.end;
+                        marked_range.start += relative_range_utf16.start;
+                        marked_range.start =
+                            snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
+                        marked_range.end =
+                            snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
+                    }
+                }
+                Some(marked_ranges)
+            } else if let Some(range_utf16) = range_utf16 {
+                let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
+                    ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
+                Some(this.selection_replacement_ranges(range_utf16, cx))
+            } else {
+                None
+            };
+
+            let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
+                let newest_selection_id = this.selections.newest_anchor().id;
+                this.selections
+                    .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
+                    .iter()
+                    .zip(ranges_to_replace.iter())
+                    .find_map(|(selection, range)| {
+                        if selection.id == newest_selection_id {
+                            Some(
+                                (range.start.0.0 as isize - selection.head().0.0 as isize)
+                                    ..(range.end.0.0 as isize - selection.head().0.0 as isize),
+                            )
+                        } else {
+                            None
+                        }
+                    })
+            });
+
+            cx.emit(EditorEvent::InputHandled {
+                utf16_range_to_replace: range_to_replace,
+                text: text.into(),
+            });
+
+            if let Some(ranges) = ranges_to_replace {
+                this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                    s.select_ranges(ranges)
+                });
+            }
+
+            let marked_ranges = {
+                let snapshot = this.buffer.read(cx).read(cx);
+                this.selections
+                    .disjoint_anchors_arc()
+                    .iter()
+                    .map(|selection| {
+                        selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
+                    })
+                    .collect::<Vec<_>>()
+            };
+
+            if text.is_empty() {
+                this.unmark_text(window, cx);
+            } else {
+                this.highlight_text(
+                    HighlightKey::InputComposition,
+                    marked_ranges.clone(),
+                    HighlightStyle {
+                        underline: Some(UnderlineStyle {
+                            thickness: px(1.),
+                            color: None,
+                            wavy: false,
+                        }),
+                        ..Default::default()
+                    },
+                    cx,
+                );
+            }
+
+            // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
+            let use_autoclose = this.use_autoclose;
+            let use_auto_surround = this.use_auto_surround;
+            this.set_use_autoclose(false);
+            this.set_use_auto_surround(false);
+            this.handle_input(text, window, cx);
+            this.set_use_autoclose(use_autoclose);
+            this.set_use_auto_surround(use_auto_surround);
+
+            if let Some(new_selected_range) = new_selected_range_utf16 {
+                let snapshot = this.buffer.read(cx).read(cx);
+                let new_selected_ranges = marked_ranges
+                    .into_iter()
+                    .map(|marked_range| {
+                        let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
+                        let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
+                            insertion_start.0 + new_selected_range.start,
+                        ));
+                        let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
+                            insertion_start.0 + new_selected_range.end,
+                        ));
+                        snapshot.clip_offset_utf16(new_start, Bias::Left)
+                            ..snapshot.clip_offset_utf16(new_end, Bias::Right)
+                    })
+                    .collect::<Vec<_>>();
+
+                drop(snapshot);
+                this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
+                    selections.select_ranges(new_selected_ranges)
+                });
+            }
+        });
+
+        self.ime_transaction = self.ime_transaction.or(transaction);
+        if let Some(transaction) = self.ime_transaction {
+            self.buffer.update(cx, |buffer, cx| {
+                buffer.group_until_transaction(transaction, cx);
+            });
+        }
+
+        if self
+            .text_highlights(HighlightKey::InputComposition, cx)
+            .is_none()
+        {
+            self.ime_transaction.take();
+        }
+    }
+
+    fn bounds_for_range(
+        &mut self,
+        range_utf16: Range<usize>,
+        element_bounds: gpui::Bounds<Pixels>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<gpui::Bounds<Pixels>> {
+        let text_layout_details = self.text_layout_details(window, cx);
+        let CharacterDimensions {
+            em_width,
+            em_advance,
+            line_height,
+        } = self.character_dimensions(window, cx);
+
+        let snapshot = self.snapshot(window, cx);
+        let scroll_position = snapshot.scroll_position();
+        let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
+
+        let start =
+            MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
+        let x = Pixels::from(
+            ScrollOffset::from(
+                snapshot.x_for_display_point(start, &text_layout_details)
+                    + self.gutter_dimensions.full_width(),
+            ) - scroll_left,
+        );
+        let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
+
+        Some(Bounds {
+            origin: element_bounds.origin + point(x, y),
+            size: size(em_width, line_height),
+        })
+    }
+
+    fn character_index_for_point(
+        &mut self,
+        point: gpui::Point<Pixels>,
+        _window: &mut Window,
+        _cx: &mut Context<Self>,
+    ) -> Option<usize> {
+        let position_map = self.last_position_map.as_ref()?;
+        if !position_map.text_hitbox.contains(&point) {
+            return None;
+        }
+        let display_point = position_map.point_for_position(point).previous_valid;
+        let anchor = position_map
+            .snapshot
+            .display_point_to_anchor(display_point, Bias::Left);
+        let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
+        Some(utf16_offset.0.0)
+    }
+
+    fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
+        self.expects_character_input
+    }
+}