1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behaviour.
15pub mod actions;
16mod blame_entry_tooltip;
17mod blink_manager;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod hunk_diff;
22mod inlay_hint_cache;
23
24mod debounced_delay;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28mod hover_popover;
29mod inline_completion_provider;
30pub mod items;
31mod mouse_context_menu;
32pub mod movement;
33mod persistence;
34mod rust_analyzer_ext;
35pub mod scroll;
36mod selections_collection;
37pub mod tasks;
38
39#[cfg(test)]
40mod editor_tests;
41#[cfg(any(test, feature = "test-support"))]
42pub mod test;
43use ::git::diff::{DiffHunk, DiffHunkStatus};
44use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
45pub(crate) use actions::*;
46use aho_corasick::AhoCorasick;
47use anyhow::{anyhow, Context as _, Result};
48use blink_manager::BlinkManager;
49use client::{Collaborator, ParticipantIndex};
50use clock::ReplicaId;
51use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
52use convert_case::{Case, Casing};
53use debounced_delay::DebouncedDelay;
54pub use display_map::DisplayPoint;
55use display_map::*;
56use editor_settings::CurrentLineHighlight;
57pub use editor_settings::EditorSettings;
58use element::LineWithInvisibles;
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62use futures::FutureExt;
63use fuzzy::{StringMatch, StringMatchCandidate};
64use git::blame::GitBlame;
65use git::diff_hunk_to_display;
66use gpui::{
67 div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
68 AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
69 Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
70 FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, PaintQuad,
71 ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText,
72 Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
73 ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
74};
75use highlight_matching_bracket::refresh_matching_bracket_highlights;
76use hover_popover::{hide_hover, HoverState};
77use hunk_diff::ExpandedHunks;
78pub(crate) use hunk_diff::HunkToExpand;
79use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
80pub use inline_completion_provider::*;
81pub use items::MAX_TAB_TITLE_LEN;
82use itertools::Itertools;
83use language::{
84 char_kind,
85 language_settings::{self, all_language_settings, InlayHintSettings},
86 markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
87 CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
88 Point, Selection, SelectionGoal, TransactionId,
89};
90use language::{BufferRow, Runnable, RunnableRange};
91use task::{ResolvedTask, TaskTemplate, TaskVariables};
92
93use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
94use lsp::{DiagnosticSeverity, LanguageServerId};
95use mouse_context_menu::MouseContextMenu;
96use movement::TextLayoutDetails;
97pub use multi_buffer::{
98 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
99 ToPoint,
100};
101use multi_buffer::{MultiBufferPoint, MultiBufferRow, ToOffsetUtf16};
102use ordered_float::OrderedFloat;
103use parking_lot::{Mutex, RwLock};
104use project::project_settings::{GitGutterSetting, ProjectSettings};
105use project::{
106 CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath,
107 ProjectTransaction, TaskSourceKind, WorktreeId,
108};
109use rand::prelude::*;
110use rpc::{proto::*, ErrorExt};
111use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
112use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
113use serde::{Deserialize, Serialize};
114use settings::{Settings, SettingsStore};
115use smallvec::SmallVec;
116use snippet::Snippet;
117use std::ops::Not as _;
118use std::{
119 any::TypeId,
120 borrow::Cow,
121 cmp::{self, Ordering, Reverse},
122 mem,
123 num::NonZeroU32,
124 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
125 path::Path,
126 sync::Arc,
127 time::{Duration, Instant},
128};
129pub use sum_tree::Bias;
130use sum_tree::TreeMap;
131use text::{BufferId, OffsetUtf16, Rope};
132use theme::{
133 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
134 ThemeColors, ThemeSettings,
135};
136use ui::{
137 h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover,
138 Tooltip,
139};
140use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
141use workspace::item::{ItemHandle, PreviewTabsSettings};
142use workspace::notifications::{DetachAndPromptErr, NotificationId};
143use workspace::{
144 searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
145};
146use workspace::{OpenInTerminal, OpenTerminal, Toast};
147
148use crate::hover_links::find_url;
149
150pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
151const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
152const MAX_LINE_LEN: usize = 1024;
153const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
154const MAX_SELECTION_HISTORY_LEN: usize = 1024;
155pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
156#[doc(hidden)]
157pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
158#[doc(hidden)]
159pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
160
161pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
162
163pub fn render_parsed_markdown(
164 element_id: impl Into<ElementId>,
165 parsed: &language::ParsedMarkdown,
166 editor_style: &EditorStyle,
167 workspace: Option<WeakView<Workspace>>,
168 cx: &mut WindowContext,
169) -> InteractiveText {
170 let code_span_background_color = cx
171 .theme()
172 .colors()
173 .editor_document_highlight_read_background;
174
175 let highlights = gpui::combine_highlights(
176 parsed.highlights.iter().filter_map(|(range, highlight)| {
177 let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
178 Some((range.clone(), highlight))
179 }),
180 parsed
181 .regions
182 .iter()
183 .zip(&parsed.region_ranges)
184 .filter_map(|(region, range)| {
185 if region.code {
186 Some((
187 range.clone(),
188 HighlightStyle {
189 background_color: Some(code_span_background_color),
190 ..Default::default()
191 },
192 ))
193 } else {
194 None
195 }
196 }),
197 );
198
199 let mut links = Vec::new();
200 let mut link_ranges = Vec::new();
201 for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
202 if let Some(link) = region.link.clone() {
203 links.push(link);
204 link_ranges.push(range.clone());
205 }
206 }
207
208 InteractiveText::new(
209 element_id,
210 StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
211 )
212 .on_click(link_ranges, move |clicked_range_ix, cx| {
213 match &links[clicked_range_ix] {
214 markdown::Link::Web { url } => cx.open_url(url),
215 markdown::Link::Path { path } => {
216 if let Some(workspace) = &workspace {
217 _ = workspace.update(cx, |workspace, cx| {
218 workspace.open_abs_path(path.clone(), false, cx).detach();
219 });
220 }
221 }
222 }
223 })
224}
225
226#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
227pub(crate) enum InlayId {
228 Suggestion(usize),
229 Hint(usize),
230}
231
232impl InlayId {
233 fn id(&self) -> usize {
234 match self {
235 Self::Suggestion(id) => *id,
236 Self::Hint(id) => *id,
237 }
238 }
239}
240
241enum DiffRowHighlight {}
242enum DocumentHighlightRead {}
243enum DocumentHighlightWrite {}
244enum InputComposition {}
245
246#[derive(Copy, Clone, PartialEq, Eq)]
247pub enum Direction {
248 Prev,
249 Next,
250}
251
252pub fn init_settings(cx: &mut AppContext) {
253 EditorSettings::register(cx);
254}
255
256pub fn init(cx: &mut AppContext) {
257 init_settings(cx);
258
259 workspace::register_project_item::<Editor>(cx);
260 workspace::register_followable_item::<Editor>(cx);
261 workspace::register_deserializable_item::<Editor>(cx);
262 cx.observe_new_views(
263 |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
264 workspace.register_action(Editor::new_file);
265 workspace.register_action(Editor::new_file_in_direction);
266 },
267 )
268 .detach();
269
270 cx.on_action(move |_: &workspace::NewFile, cx| {
271 let app_state = workspace::AppState::global(cx);
272 if let Some(app_state) = app_state.upgrade() {
273 workspace::open_new(app_state, cx, |workspace, cx| {
274 Editor::new_file(workspace, &Default::default(), cx)
275 })
276 .detach();
277 }
278 });
279 cx.on_action(move |_: &workspace::NewWindow, cx| {
280 let app_state = workspace::AppState::global(cx);
281 if let Some(app_state) = app_state.upgrade() {
282 workspace::open_new(app_state, cx, |workspace, cx| {
283 Editor::new_file(workspace, &Default::default(), cx)
284 })
285 .detach();
286 }
287 });
288}
289
290pub struct SearchWithinRange;
291
292trait InvalidationRegion {
293 fn ranges(&self) -> &[Range<Anchor>];
294}
295
296#[derive(Clone, Debug, PartialEq)]
297pub enum SelectPhase {
298 Begin {
299 position: DisplayPoint,
300 add: bool,
301 click_count: usize,
302 },
303 BeginColumnar {
304 position: DisplayPoint,
305 goal_column: u32,
306 },
307 Extend {
308 position: DisplayPoint,
309 click_count: usize,
310 },
311 Update {
312 position: DisplayPoint,
313 goal_column: u32,
314 scroll_delta: gpui::Point<f32>,
315 },
316 End,
317}
318
319#[derive(Clone, Debug)]
320pub enum SelectMode {
321 Character,
322 Word(Range<Anchor>),
323 Line(Range<Anchor>),
324 All,
325}
326
327#[derive(Copy, Clone, PartialEq, Eq, Debug)]
328pub enum EditorMode {
329 SingleLine,
330 AutoHeight { max_lines: usize },
331 Full,
332}
333
334#[derive(Clone, Debug)]
335pub enum SoftWrap {
336 None,
337 PreferLine,
338 EditorWidth,
339 Column(u32),
340}
341
342#[derive(Clone)]
343pub struct EditorStyle {
344 pub background: Hsla,
345 pub local_player: PlayerColor,
346 pub text: TextStyle,
347 pub scrollbar_width: Pixels,
348 pub syntax: Arc<SyntaxTheme>,
349 pub status: StatusColors,
350 pub inlay_hints_style: HighlightStyle,
351 pub suggestions_style: HighlightStyle,
352}
353
354impl Default for EditorStyle {
355 fn default() -> Self {
356 Self {
357 background: Hsla::default(),
358 local_player: PlayerColor::default(),
359 text: TextStyle::default(),
360 scrollbar_width: Pixels::default(),
361 syntax: Default::default(),
362 // HACK: Status colors don't have a real default.
363 // We should look into removing the status colors from the editor
364 // style and retrieve them directly from the theme.
365 status: StatusColors::dark(),
366 inlay_hints_style: HighlightStyle::default(),
367 suggestions_style: HighlightStyle::default(),
368 }
369 }
370}
371
372type CompletionId = usize;
373
374// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
375// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
376
377type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
378
379struct ScrollbarMarkerState {
380 scrollbar_size: Size<Pixels>,
381 dirty: bool,
382 markers: Arc<[PaintQuad]>,
383 pending_refresh: Option<Task<Result<()>>>,
384}
385
386impl ScrollbarMarkerState {
387 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
388 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
389 }
390}
391
392impl Default for ScrollbarMarkerState {
393 fn default() -> Self {
394 Self {
395 scrollbar_size: Size::default(),
396 dirty: false,
397 markers: Arc::from([]),
398 pending_refresh: None,
399 }
400 }
401}
402
403#[derive(Clone, Debug)]
404struct RunnableTasks {
405 templates: Vec<(TaskSourceKind, TaskTemplate)>,
406 // We need the column at which the task context evaluation should take place.
407 column: u32,
408 extra_variables: HashMap<String, String>,
409}
410
411#[derive(Clone)]
412struct ResolvedTasks {
413 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
414 position: Anchor,
415}
416
417/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
418///
419/// See the [module level documentation](self) for more information.
420pub struct Editor {
421 focus_handle: FocusHandle,
422 /// The text buffer being edited
423 buffer: Model<MultiBuffer>,
424 /// Map of how text in the buffer should be displayed.
425 /// Handles soft wraps, folds, fake inlay text insertions, etc.
426 pub display_map: Model<DisplayMap>,
427 pub selections: SelectionsCollection,
428 pub scroll_manager: ScrollManager,
429 columnar_selection_tail: Option<Anchor>,
430 add_selections_state: Option<AddSelectionsState>,
431 select_next_state: Option<SelectNextState>,
432 select_prev_state: Option<SelectNextState>,
433 selection_history: SelectionHistory,
434 autoclose_regions: Vec<AutocloseRegion>,
435 snippet_stack: InvalidationStack<SnippetState>,
436 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
437 ime_transaction: Option<TransactionId>,
438 active_diagnostics: Option<ActiveDiagnosticGroup>,
439 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
440 project: Option<Model<Project>>,
441 completion_provider: Option<Box<dyn CompletionProvider>>,
442 collaboration_hub: Option<Box<dyn CollaborationHub>>,
443 blink_manager: Model<BlinkManager>,
444 show_cursor_names: bool,
445 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
446 pub show_local_selections: bool,
447 mode: EditorMode,
448 show_breadcrumbs: bool,
449 show_gutter: bool,
450 show_wrap_guides: Option<bool>,
451 placeholder_text: Option<Arc<str>>,
452 highlight_order: usize,
453 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
454 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
455 scrollbar_marker_state: ScrollbarMarkerState,
456 nav_history: Option<ItemNavHistory>,
457 context_menu: RwLock<Option<ContextMenu>>,
458 mouse_context_menu: Option<MouseContextMenu>,
459 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
460 find_all_references_task_sources: Vec<Anchor>,
461 next_completion_id: CompletionId,
462 completion_documentation_pre_resolve_debounce: DebouncedDelay,
463 available_code_actions: Option<(Location, Arc<[CodeAction]>)>,
464 code_actions_task: Option<Task<()>>,
465 document_highlights_task: Option<Task<()>>,
466 pending_rename: Option<RenameState>,
467 searchable: bool,
468 cursor_shape: CursorShape,
469 current_line_highlight: CurrentLineHighlight,
470 collapse_matches: bool,
471 autoindent_mode: Option<AutoindentMode>,
472 workspace: Option<(WeakView<Workspace>, WorkspaceId)>,
473 keymap_context_layers: BTreeMap<TypeId, KeyContext>,
474 input_enabled: bool,
475 use_modal_editing: bool,
476 read_only: bool,
477 leader_peer_id: Option<PeerId>,
478 remote_id: Option<ViewId>,
479 hover_state: HoverState,
480 gutter_hovered: bool,
481 hovered_link_state: Option<HoveredLinkState>,
482 inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
483 active_inline_completion: Option<Inlay>,
484 show_inline_completions: bool,
485 inlay_hint_cache: InlayHintCache,
486 expanded_hunks: ExpandedHunks,
487 next_inlay_id: usize,
488 _subscriptions: Vec<Subscription>,
489 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
490 gutter_dimensions: GutterDimensions,
491 pub vim_replace_map: HashMap<Range<usize>, String>,
492 style: Option<EditorStyle>,
493 editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
494 use_autoclose: bool,
495 auto_replace_emoji_shortcode: bool,
496 show_git_blame_gutter: bool,
497 show_git_blame_inline: bool,
498 show_git_blame_inline_delay_task: Option<Task<()>>,
499 git_blame_inline_enabled: bool,
500 blame: Option<Model<GitBlame>>,
501 blame_subscription: Option<Subscription>,
502 custom_context_menu: Option<
503 Box<
504 dyn 'static
505 + Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
506 >,
507 >,
508 last_bounds: Option<Bounds<Pixels>>,
509 expect_bounds_change: Option<Bounds<Pixels>>,
510 tasks: HashMap<(BufferId, BufferRow), (usize, RunnableTasks)>,
511 tasks_update_task: Option<Task<()>>,
512}
513
514#[derive(Clone)]
515pub struct EditorSnapshot {
516 pub mode: EditorMode,
517 show_gutter: bool,
518 render_git_blame_gutter: bool,
519 pub display_snapshot: DisplaySnapshot,
520 pub placeholder_text: Option<Arc<str>>,
521 is_focused: bool,
522 scroll_anchor: ScrollAnchor,
523 ongoing_scroll: OngoingScroll,
524 current_line_highlight: CurrentLineHighlight,
525}
526
527const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
528
529#[derive(Debug, Clone, Copy)]
530pub struct GutterDimensions {
531 pub left_padding: Pixels,
532 pub right_padding: Pixels,
533 pub width: Pixels,
534 pub margin: Pixels,
535 pub git_blame_entries_width: Option<Pixels>,
536}
537
538impl Default for GutterDimensions {
539 fn default() -> Self {
540 Self {
541 left_padding: Pixels::ZERO,
542 right_padding: Pixels::ZERO,
543 width: Pixels::ZERO,
544 margin: Pixels::ZERO,
545 git_blame_entries_width: None,
546 }
547 }
548}
549
550#[derive(Debug)]
551pub struct RemoteSelection {
552 pub replica_id: ReplicaId,
553 pub selection: Selection<Anchor>,
554 pub cursor_shape: CursorShape,
555 pub peer_id: PeerId,
556 pub line_mode: bool,
557 pub participant_index: Option<ParticipantIndex>,
558 pub user_name: Option<SharedString>,
559}
560
561#[derive(Clone, Debug)]
562struct SelectionHistoryEntry {
563 selections: Arc<[Selection<Anchor>]>,
564 select_next_state: Option<SelectNextState>,
565 select_prev_state: Option<SelectNextState>,
566 add_selections_state: Option<AddSelectionsState>,
567}
568
569enum SelectionHistoryMode {
570 Normal,
571 Undoing,
572 Redoing,
573}
574
575#[derive(Clone, PartialEq, Eq, Hash)]
576struct HoveredCursor {
577 replica_id: u16,
578 selection_id: usize,
579}
580
581impl Default for SelectionHistoryMode {
582 fn default() -> Self {
583 Self::Normal
584 }
585}
586
587#[derive(Default)]
588struct SelectionHistory {
589 #[allow(clippy::type_complexity)]
590 selections_by_transaction:
591 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
592 mode: SelectionHistoryMode,
593 undo_stack: VecDeque<SelectionHistoryEntry>,
594 redo_stack: VecDeque<SelectionHistoryEntry>,
595}
596
597impl SelectionHistory {
598 fn insert_transaction(
599 &mut self,
600 transaction_id: TransactionId,
601 selections: Arc<[Selection<Anchor>]>,
602 ) {
603 self.selections_by_transaction
604 .insert(transaction_id, (selections, None));
605 }
606
607 #[allow(clippy::type_complexity)]
608 fn transaction(
609 &self,
610 transaction_id: TransactionId,
611 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
612 self.selections_by_transaction.get(&transaction_id)
613 }
614
615 #[allow(clippy::type_complexity)]
616 fn transaction_mut(
617 &mut self,
618 transaction_id: TransactionId,
619 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
620 self.selections_by_transaction.get_mut(&transaction_id)
621 }
622
623 fn push(&mut self, entry: SelectionHistoryEntry) {
624 if !entry.selections.is_empty() {
625 match self.mode {
626 SelectionHistoryMode::Normal => {
627 self.push_undo(entry);
628 self.redo_stack.clear();
629 }
630 SelectionHistoryMode::Undoing => self.push_redo(entry),
631 SelectionHistoryMode::Redoing => self.push_undo(entry),
632 }
633 }
634 }
635
636 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
637 if self
638 .undo_stack
639 .back()
640 .map_or(true, |e| e.selections != entry.selections)
641 {
642 self.undo_stack.push_back(entry);
643 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
644 self.undo_stack.pop_front();
645 }
646 }
647 }
648
649 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
650 if self
651 .redo_stack
652 .back()
653 .map_or(true, |e| e.selections != entry.selections)
654 {
655 self.redo_stack.push_back(entry);
656 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
657 self.redo_stack.pop_front();
658 }
659 }
660 }
661}
662
663struct RowHighlight {
664 index: usize,
665 range: RangeInclusive<Anchor>,
666 color: Option<Hsla>,
667 should_autoscroll: bool,
668}
669
670#[derive(Clone, Debug)]
671struct AddSelectionsState {
672 above: bool,
673 stack: Vec<usize>,
674}
675
676#[derive(Clone)]
677struct SelectNextState {
678 query: AhoCorasick,
679 wordwise: bool,
680 done: bool,
681}
682
683impl std::fmt::Debug for SelectNextState {
684 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685 f.debug_struct(std::any::type_name::<Self>())
686 .field("wordwise", &self.wordwise)
687 .field("done", &self.done)
688 .finish()
689 }
690}
691
692#[derive(Debug)]
693struct AutocloseRegion {
694 selection_id: usize,
695 range: Range<Anchor>,
696 pair: BracketPair,
697}
698
699#[derive(Debug)]
700struct SnippetState {
701 ranges: Vec<Vec<Range<Anchor>>>,
702 active_index: usize,
703}
704
705#[doc(hidden)]
706pub struct RenameState {
707 pub range: Range<Anchor>,
708 pub old_name: Arc<str>,
709 pub editor: View<Editor>,
710 block_id: BlockId,
711}
712
713struct InvalidationStack<T>(Vec<T>);
714
715struct RegisteredInlineCompletionProvider {
716 provider: Arc<dyn InlineCompletionProviderHandle>,
717 _subscription: Subscription,
718}
719
720enum ContextMenu {
721 Completions(CompletionsMenu),
722 CodeActions(CodeActionsMenu),
723}
724
725impl ContextMenu {
726 fn select_first(
727 &mut self,
728 project: Option<&Model<Project>>,
729 cx: &mut ViewContext<Editor>,
730 ) -> bool {
731 if self.visible() {
732 match self {
733 ContextMenu::Completions(menu) => menu.select_first(project, cx),
734 ContextMenu::CodeActions(menu) => menu.select_first(cx),
735 }
736 true
737 } else {
738 false
739 }
740 }
741
742 fn select_prev(
743 &mut self,
744 project: Option<&Model<Project>>,
745 cx: &mut ViewContext<Editor>,
746 ) -> bool {
747 if self.visible() {
748 match self {
749 ContextMenu::Completions(menu) => menu.select_prev(project, cx),
750 ContextMenu::CodeActions(menu) => menu.select_prev(cx),
751 }
752 true
753 } else {
754 false
755 }
756 }
757
758 fn select_next(
759 &mut self,
760 project: Option<&Model<Project>>,
761 cx: &mut ViewContext<Editor>,
762 ) -> bool {
763 if self.visible() {
764 match self {
765 ContextMenu::Completions(menu) => menu.select_next(project, cx),
766 ContextMenu::CodeActions(menu) => menu.select_next(cx),
767 }
768 true
769 } else {
770 false
771 }
772 }
773
774 fn select_last(
775 &mut self,
776 project: Option<&Model<Project>>,
777 cx: &mut ViewContext<Editor>,
778 ) -> bool {
779 if self.visible() {
780 match self {
781 ContextMenu::Completions(menu) => menu.select_last(project, cx),
782 ContextMenu::CodeActions(menu) => menu.select_last(cx),
783 }
784 true
785 } else {
786 false
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match self {
792 ContextMenu::Completions(menu) => menu.visible(),
793 ContextMenu::CodeActions(menu) => menu.visible(),
794 }
795 }
796
797 fn render(
798 &self,
799 cursor_position: DisplayPoint,
800 style: &EditorStyle,
801 max_height: Pixels,
802 workspace: Option<WeakView<Workspace>>,
803 cx: &mut ViewContext<Editor>,
804 ) -> (ContextMenuOrigin, AnyElement) {
805 match self {
806 ContextMenu::Completions(menu) => (
807 ContextMenuOrigin::EditorPoint(cursor_position),
808 menu.render(style, max_height, workspace, cx),
809 ),
810 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
811 }
812 }
813}
814
815enum ContextMenuOrigin {
816 EditorPoint(DisplayPoint),
817 GutterIndicator(DisplayRow),
818}
819
820#[derive(Clone)]
821struct CompletionsMenu {
822 id: CompletionId,
823 initial_position: Anchor,
824 buffer: Model<Buffer>,
825 completions: Arc<RwLock<Box<[Completion]>>>,
826 match_candidates: Arc<[StringMatchCandidate]>,
827 matches: Arc<[StringMatch]>,
828 selected_item: usize,
829 scroll_handle: UniformListScrollHandle,
830 selected_completion_documentation_resolve_debounce: Arc<Mutex<DebouncedDelay>>,
831}
832
833impl CompletionsMenu {
834 fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
835 self.selected_item = 0;
836 self.scroll_handle.scroll_to_item(self.selected_item);
837 self.attempt_resolve_selected_completion_documentation(project, cx);
838 cx.notify();
839 }
840
841 fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
842 if self.selected_item > 0 {
843 self.selected_item -= 1;
844 } else {
845 self.selected_item = self.matches.len() - 1;
846 }
847 self.scroll_handle.scroll_to_item(self.selected_item);
848 self.attempt_resolve_selected_completion_documentation(project, cx);
849 cx.notify();
850 }
851
852 fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
853 if self.selected_item + 1 < self.matches.len() {
854 self.selected_item += 1;
855 } else {
856 self.selected_item = 0;
857 }
858 self.scroll_handle.scroll_to_item(self.selected_item);
859 self.attempt_resolve_selected_completion_documentation(project, cx);
860 cx.notify();
861 }
862
863 fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
864 self.selected_item = self.matches.len() - 1;
865 self.scroll_handle.scroll_to_item(self.selected_item);
866 self.attempt_resolve_selected_completion_documentation(project, cx);
867 cx.notify();
868 }
869
870 fn pre_resolve_completion_documentation(
871 buffer: Model<Buffer>,
872 completions: Arc<RwLock<Box<[Completion]>>>,
873 matches: Arc<[StringMatch]>,
874 editor: &Editor,
875 cx: &mut ViewContext<Editor>,
876 ) -> Task<()> {
877 let settings = EditorSettings::get_global(cx);
878 if !settings.show_completion_documentation {
879 return Task::ready(());
880 }
881
882 let Some(provider) = editor.completion_provider.as_ref() else {
883 return Task::ready(());
884 };
885
886 let resolve_task = provider.resolve_completions(
887 buffer,
888 matches.iter().map(|m| m.candidate_id).collect(),
889 completions.clone(),
890 cx,
891 );
892
893 return cx.spawn(move |this, mut cx| async move {
894 if let Some(true) = resolve_task.await.log_err() {
895 this.update(&mut cx, |_, cx| cx.notify()).ok();
896 }
897 });
898 }
899
900 fn attempt_resolve_selected_completion_documentation(
901 &mut self,
902 project: Option<&Model<Project>>,
903 cx: &mut ViewContext<Editor>,
904 ) {
905 let settings = EditorSettings::get_global(cx);
906 if !settings.show_completion_documentation {
907 return;
908 }
909
910 let completion_index = self.matches[self.selected_item].candidate_id;
911 let Some(project) = project else {
912 return;
913 };
914
915 let resolve_task = project.update(cx, |project, cx| {
916 project.resolve_completions(
917 self.buffer.clone(),
918 vec![completion_index],
919 self.completions.clone(),
920 cx,
921 )
922 });
923
924 let delay_ms =
925 EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
926 let delay = Duration::from_millis(delay_ms);
927
928 self.selected_completion_documentation_resolve_debounce
929 .lock()
930 .fire_new(delay, cx, |_, cx| {
931 cx.spawn(move |this, mut cx| async move {
932 if let Some(true) = resolve_task.await.log_err() {
933 this.update(&mut cx, |_, cx| cx.notify()).ok();
934 }
935 })
936 });
937 }
938
939 fn visible(&self) -> bool {
940 !self.matches.is_empty()
941 }
942
943 fn render(
944 &self,
945 style: &EditorStyle,
946 max_height: Pixels,
947 workspace: Option<WeakView<Workspace>>,
948 cx: &mut ViewContext<Editor>,
949 ) -> AnyElement {
950 let settings = EditorSettings::get_global(cx);
951 let show_completion_documentation = settings.show_completion_documentation;
952
953 let widest_completion_ix = self
954 .matches
955 .iter()
956 .enumerate()
957 .max_by_key(|(_, mat)| {
958 let completions = self.completions.read();
959 let completion = &completions[mat.candidate_id];
960 let documentation = &completion.documentation;
961
962 let mut len = completion.label.text.chars().count();
963 if let Some(Documentation::SingleLine(text)) = documentation {
964 if show_completion_documentation {
965 len += text.chars().count();
966 }
967 }
968
969 len
970 })
971 .map(|(ix, _)| ix);
972
973 let completions = self.completions.clone();
974 let matches = self.matches.clone();
975 let selected_item = self.selected_item;
976 let style = style.clone();
977
978 let multiline_docs = if show_completion_documentation {
979 let mat = &self.matches[selected_item];
980 let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
981 Some(Documentation::MultiLinePlainText(text)) => {
982 Some(div().child(SharedString::from(text.clone())))
983 }
984 Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
985 Some(div().child(render_parsed_markdown(
986 "completions_markdown",
987 parsed,
988 &style,
989 workspace,
990 cx,
991 )))
992 }
993 _ => None,
994 };
995 multiline_docs.map(|div| {
996 div.id("multiline_docs")
997 .max_h(max_height)
998 .flex_1()
999 .px_1p5()
1000 .py_1()
1001 .min_w(px(260.))
1002 .max_w(px(640.))
1003 .w(px(500.))
1004 .overflow_y_scroll()
1005 .occlude()
1006 })
1007 } else {
1008 None
1009 };
1010
1011 let list = uniform_list(
1012 cx.view().clone(),
1013 "completions",
1014 matches.len(),
1015 move |_editor, range, cx| {
1016 let start_ix = range.start;
1017 let completions_guard = completions.read();
1018
1019 matches[range]
1020 .iter()
1021 .enumerate()
1022 .map(|(ix, mat)| {
1023 let item_ix = start_ix + ix;
1024 let candidate_id = mat.candidate_id;
1025 let completion = &completions_guard[candidate_id];
1026
1027 let documentation = if show_completion_documentation {
1028 &completion.documentation
1029 } else {
1030 &None
1031 };
1032
1033 let highlights = gpui::combine_highlights(
1034 mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
1035 styled_runs_for_code_label(&completion.label, &style.syntax).map(
1036 |(range, mut highlight)| {
1037 // Ignore font weight for syntax highlighting, as we'll use it
1038 // for fuzzy matches.
1039 highlight.font_weight = None;
1040
1041 if completion.lsp_completion.deprecated.unwrap_or(false) {
1042 highlight.strikethrough = Some(StrikethroughStyle {
1043 thickness: 1.0.into(),
1044 ..Default::default()
1045 });
1046 highlight.color = Some(cx.theme().colors().text_muted);
1047 }
1048
1049 (range, highlight)
1050 },
1051 ),
1052 );
1053 let completion_label = StyledText::new(completion.label.text.clone())
1054 .with_highlights(&style.text, highlights);
1055 let documentation_label =
1056 if let Some(Documentation::SingleLine(text)) = documentation {
1057 if text.trim().is_empty() {
1058 None
1059 } else {
1060 Some(
1061 h_flex().ml_4().child(
1062 Label::new(text.clone())
1063 .size(LabelSize::Small)
1064 .color(Color::Muted),
1065 ),
1066 )
1067 }
1068 } else {
1069 None
1070 };
1071
1072 div().min_w(px(220.)).max_w(px(540.)).child(
1073 ListItem::new(mat.candidate_id)
1074 .inset(true)
1075 .selected(item_ix == selected_item)
1076 .on_click(cx.listener(move |editor, _event, cx| {
1077 cx.stop_propagation();
1078 if let Some(task) = editor.confirm_completion(
1079 &ConfirmCompletion {
1080 item_ix: Some(item_ix),
1081 },
1082 cx,
1083 ) {
1084 task.detach_and_log_err(cx)
1085 }
1086 }))
1087 .child(h_flex().overflow_hidden().child(completion_label))
1088 .end_slot::<Div>(documentation_label),
1089 )
1090 })
1091 .collect()
1092 },
1093 )
1094 .occlude()
1095 .max_h(max_height)
1096 .track_scroll(self.scroll_handle.clone())
1097 .with_width_from_item(widest_completion_ix);
1098
1099 Popover::new()
1100 .child(list)
1101 .when_some(multiline_docs, |popover, multiline_docs| {
1102 popover.aside(multiline_docs)
1103 })
1104 .into_any_element()
1105 }
1106
1107 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
1108 let mut matches = if let Some(query) = query {
1109 fuzzy::match_strings(
1110 &self.match_candidates,
1111 query,
1112 query.chars().any(|c| c.is_uppercase()),
1113 100,
1114 &Default::default(),
1115 executor,
1116 )
1117 .await
1118 } else {
1119 self.match_candidates
1120 .iter()
1121 .enumerate()
1122 .map(|(candidate_id, candidate)| StringMatch {
1123 candidate_id,
1124 score: Default::default(),
1125 positions: Default::default(),
1126 string: candidate.string.clone(),
1127 })
1128 .collect()
1129 };
1130
1131 // Remove all candidates where the query's start does not match the start of any word in the candidate
1132 if let Some(query) = query {
1133 if let Some(query_start) = query.chars().next() {
1134 matches.retain(|string_match| {
1135 split_words(&string_match.string).any(|word| {
1136 // Check that the first codepoint of the word as lowercase matches the first
1137 // codepoint of the query as lowercase
1138 word.chars()
1139 .flat_map(|codepoint| codepoint.to_lowercase())
1140 .zip(query_start.to_lowercase())
1141 .all(|(word_cp, query_cp)| word_cp == query_cp)
1142 })
1143 });
1144 }
1145 }
1146
1147 let completions = self.completions.read();
1148 matches.sort_unstable_by_key(|mat| {
1149 // We do want to strike a balance here between what the language server tells us
1150 // to sort by (the sort_text) and what are "obvious" good matches (i.e. when you type
1151 // `Creat` and there is a local variable called `CreateComponent`).
1152 // So what we do is: we bucket all matches into two buckets
1153 // - Strong matches
1154 // - Weak matches
1155 // Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches)
1156 // and the Weak matches are the rest.
1157 //
1158 // For the strong matches, we sort by the language-servers score first and for the weak
1159 // matches, we prefer our fuzzy finder first.
1160 //
1161 // The thinking behind that: it's useless to take the sort_text the language-server gives
1162 // us into account when it's obviously a bad match.
1163
1164 #[derive(PartialEq, Eq, PartialOrd, Ord)]
1165 enum MatchScore<'a> {
1166 Strong {
1167 sort_text: Option<&'a str>,
1168 score: Reverse<OrderedFloat<f64>>,
1169 sort_key: (usize, &'a str),
1170 },
1171 Weak {
1172 score: Reverse<OrderedFloat<f64>>,
1173 sort_text: Option<&'a str>,
1174 sort_key: (usize, &'a str),
1175 },
1176 }
1177
1178 let completion = &completions[mat.candidate_id];
1179 let sort_key = completion.sort_key();
1180 let sort_text = completion.lsp_completion.sort_text.as_deref();
1181 let score = Reverse(OrderedFloat(mat.score));
1182
1183 if mat.score >= 0.2 {
1184 MatchScore::Strong {
1185 sort_text,
1186 score,
1187 sort_key,
1188 }
1189 } else {
1190 MatchScore::Weak {
1191 score,
1192 sort_text,
1193 sort_key,
1194 }
1195 }
1196 });
1197
1198 for mat in &mut matches {
1199 let completion = &completions[mat.candidate_id];
1200 mat.string.clone_from(&completion.label.text);
1201 for position in &mut mat.positions {
1202 *position += completion.label.filter_range.start;
1203 }
1204 }
1205 drop(completions);
1206
1207 self.matches = matches.into();
1208 self.selected_item = 0;
1209 }
1210}
1211
1212#[derive(Clone)]
1213struct CodeActionContents {
1214 tasks: Option<Arc<ResolvedTasks>>,
1215 actions: Option<Arc<[CodeAction]>>,
1216}
1217
1218impl CodeActionContents {
1219 fn len(&self) -> usize {
1220 match (&self.tasks, &self.actions) {
1221 (Some(tasks), Some(actions)) => actions.len() + tasks.templates.len(),
1222 (Some(tasks), None) => tasks.templates.len(),
1223 (None, Some(actions)) => actions.len(),
1224 (None, None) => 0,
1225 }
1226 }
1227
1228 fn is_empty(&self) -> bool {
1229 match (&self.tasks, &self.actions) {
1230 (Some(tasks), Some(actions)) => actions.is_empty() && tasks.templates.is_empty(),
1231 (Some(tasks), None) => tasks.templates.is_empty(),
1232 (None, Some(actions)) => actions.is_empty(),
1233 (None, None) => true,
1234 }
1235 }
1236
1237 fn iter(&self) -> impl Iterator<Item = CodeActionsItem> + '_ {
1238 self.tasks
1239 .iter()
1240 .flat_map(|tasks| {
1241 tasks
1242 .templates
1243 .iter()
1244 .map(|(kind, task)| CodeActionsItem::Task(kind.clone(), task.clone()))
1245 })
1246 .chain(self.actions.iter().flat_map(|actions| {
1247 actions
1248 .iter()
1249 .map(|action| CodeActionsItem::CodeAction(action.clone()))
1250 }))
1251 }
1252 fn get(&self, index: usize) -> Option<CodeActionsItem> {
1253 match (&self.tasks, &self.actions) {
1254 (Some(tasks), Some(actions)) => {
1255 if index < tasks.templates.len() {
1256 tasks
1257 .templates
1258 .get(index)
1259 .cloned()
1260 .map(|(kind, task)| CodeActionsItem::Task(kind, task))
1261 } else {
1262 actions
1263 .get(index - tasks.templates.len())
1264 .cloned()
1265 .map(CodeActionsItem::CodeAction)
1266 }
1267 }
1268 (Some(tasks), None) => tasks
1269 .templates
1270 .get(index)
1271 .cloned()
1272 .map(|(kind, task)| CodeActionsItem::Task(kind, task)),
1273 (None, Some(actions)) => actions.get(index).cloned().map(CodeActionsItem::CodeAction),
1274 (None, None) => None,
1275 }
1276 }
1277}
1278
1279#[allow(clippy::large_enum_variant)]
1280#[derive(Clone)]
1281enum CodeActionsItem {
1282 Task(TaskSourceKind, ResolvedTask),
1283 CodeAction(CodeAction),
1284}
1285
1286impl CodeActionsItem {
1287 fn as_task(&self) -> Option<&ResolvedTask> {
1288 let Self::Task(_, task) = self else {
1289 return None;
1290 };
1291 Some(task)
1292 }
1293 fn as_code_action(&self) -> Option<&CodeAction> {
1294 let Self::CodeAction(action) = self else {
1295 return None;
1296 };
1297 Some(action)
1298 }
1299 fn label(&self) -> String {
1300 match self {
1301 Self::CodeAction(action) => action.lsp_action.title.clone(),
1302 Self::Task(_, task) => task.resolved_label.clone(),
1303 }
1304 }
1305}
1306
1307struct CodeActionsMenu {
1308 actions: CodeActionContents,
1309 buffer: Model<Buffer>,
1310 selected_item: usize,
1311 scroll_handle: UniformListScrollHandle,
1312 deployed_from_indicator: Option<DisplayRow>,
1313}
1314
1315impl CodeActionsMenu {
1316 fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
1317 self.selected_item = 0;
1318 self.scroll_handle.scroll_to_item(self.selected_item);
1319 cx.notify()
1320 }
1321
1322 fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
1323 if self.selected_item > 0 {
1324 self.selected_item -= 1;
1325 } else {
1326 self.selected_item = self.actions.len() - 1;
1327 }
1328 self.scroll_handle.scroll_to_item(self.selected_item);
1329 cx.notify();
1330 }
1331
1332 fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
1333 if self.selected_item + 1 < self.actions.len() {
1334 self.selected_item += 1;
1335 } else {
1336 self.selected_item = 0;
1337 }
1338 self.scroll_handle.scroll_to_item(self.selected_item);
1339 cx.notify();
1340 }
1341
1342 fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
1343 self.selected_item = self.actions.len() - 1;
1344 self.scroll_handle.scroll_to_item(self.selected_item);
1345 cx.notify()
1346 }
1347
1348 fn visible(&self) -> bool {
1349 !self.actions.is_empty()
1350 }
1351
1352 fn render(
1353 &self,
1354 cursor_position: DisplayPoint,
1355 _style: &EditorStyle,
1356 max_height: Pixels,
1357 cx: &mut ViewContext<Editor>,
1358 ) -> (ContextMenuOrigin, AnyElement) {
1359 let actions = self.actions.clone();
1360 let selected_item = self.selected_item;
1361 let element = uniform_list(
1362 cx.view().clone(),
1363 "code_actions_menu",
1364 self.actions.len(),
1365 move |_this, range, cx| {
1366 actions
1367 .iter()
1368 .skip(range.start)
1369 .take(range.end - range.start)
1370 .enumerate()
1371 .map(|(ix, action)| {
1372 let item_ix = range.start + ix;
1373 let selected = selected_item == item_ix;
1374 let colors = cx.theme().colors();
1375 div()
1376 .px_2()
1377 .text_color(colors.text)
1378 .when(selected, |style| {
1379 style
1380 .bg(colors.element_active)
1381 .text_color(colors.text_accent)
1382 })
1383 .hover(|style| {
1384 style
1385 .bg(colors.element_hover)
1386 .text_color(colors.text_accent)
1387 })
1388 .whitespace_nowrap()
1389 .when_some(action.as_code_action(), |this, action| {
1390 this.on_mouse_down(
1391 MouseButton::Left,
1392 cx.listener(move |editor, _, cx| {
1393 cx.stop_propagation();
1394 if let Some(task) = editor.confirm_code_action(
1395 &ConfirmCodeAction {
1396 item_ix: Some(item_ix),
1397 },
1398 cx,
1399 ) {
1400 task.detach_and_log_err(cx)
1401 }
1402 }),
1403 )
1404 // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
1405 .child(SharedString::from(action.lsp_action.title.clone()))
1406 })
1407 .when_some(action.as_task(), |this, task| {
1408 this.on_mouse_down(
1409 MouseButton::Left,
1410 cx.listener(move |editor, _, cx| {
1411 cx.stop_propagation();
1412 if let Some(task) = editor.confirm_code_action(
1413 &ConfirmCodeAction {
1414 item_ix: Some(item_ix),
1415 },
1416 cx,
1417 ) {
1418 task.detach_and_log_err(cx)
1419 }
1420 }),
1421 )
1422 .child(SharedString::from(task.resolved_label.clone()))
1423 })
1424 })
1425 .collect()
1426 },
1427 )
1428 .elevation_1(cx)
1429 .px_2()
1430 .py_1()
1431 .max_h(max_height)
1432 .occlude()
1433 .track_scroll(self.scroll_handle.clone())
1434 .with_width_from_item(
1435 self.actions
1436 .iter()
1437 .enumerate()
1438 .max_by_key(|(_, action)| match action {
1439 CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(),
1440 CodeActionsItem::CodeAction(action) => action.lsp_action.title.chars().count(),
1441 })
1442 .map(|(ix, _)| ix),
1443 )
1444 .into_any_element();
1445
1446 let cursor_position = if let Some(row) = self.deployed_from_indicator {
1447 ContextMenuOrigin::GutterIndicator(row)
1448 } else {
1449 ContextMenuOrigin::EditorPoint(cursor_position)
1450 };
1451
1452 (cursor_position, element)
1453 }
1454}
1455
1456#[derive(Debug)]
1457struct ActiveDiagnosticGroup {
1458 primary_range: Range<Anchor>,
1459 primary_message: String,
1460 group_id: usize,
1461 blocks: HashMap<BlockId, Diagnostic>,
1462 is_valid: bool,
1463}
1464
1465#[derive(Serialize, Deserialize)]
1466pub struct ClipboardSelection {
1467 pub len: usize,
1468 pub is_entire_line: bool,
1469 pub first_line_indent: u32,
1470}
1471
1472#[derive(Debug)]
1473pub(crate) struct NavigationData {
1474 cursor_anchor: Anchor,
1475 cursor_position: Point,
1476 scroll_anchor: ScrollAnchor,
1477 scroll_top_row: u32,
1478}
1479
1480enum GotoDefinitionKind {
1481 Symbol,
1482 Type,
1483 Implementation,
1484}
1485
1486#[derive(Debug, Clone)]
1487enum InlayHintRefreshReason {
1488 Toggle(bool),
1489 SettingsChange(InlayHintSettings),
1490 NewLinesShown,
1491 BufferEdited(HashSet<Arc<Language>>),
1492 RefreshRequested,
1493 ExcerptsRemoved(Vec<ExcerptId>),
1494}
1495
1496impl InlayHintRefreshReason {
1497 fn description(&self) -> &'static str {
1498 match self {
1499 Self::Toggle(_) => "toggle",
1500 Self::SettingsChange(_) => "settings change",
1501 Self::NewLinesShown => "new lines shown",
1502 Self::BufferEdited(_) => "buffer edited",
1503 Self::RefreshRequested => "refresh requested",
1504 Self::ExcerptsRemoved(_) => "excerpts removed",
1505 }
1506 }
1507}
1508
1509impl Editor {
1510 pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
1511 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1512 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1513 Self::new(EditorMode::SingleLine, buffer, None, cx)
1514 }
1515
1516 pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
1517 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1518 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1519 Self::new(EditorMode::Full, buffer, None, cx)
1520 }
1521
1522 pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
1523 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1524 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1525 Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
1526 }
1527
1528 pub fn for_buffer(
1529 buffer: Model<Buffer>,
1530 project: Option<Model<Project>>,
1531 cx: &mut ViewContext<Self>,
1532 ) -> Self {
1533 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1534 Self::new(EditorMode::Full, buffer, project, cx)
1535 }
1536
1537 pub fn for_multibuffer(
1538 buffer: Model<MultiBuffer>,
1539 project: Option<Model<Project>>,
1540 cx: &mut ViewContext<Self>,
1541 ) -> Self {
1542 Self::new(EditorMode::Full, buffer, project, cx)
1543 }
1544
1545 pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
1546 let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx);
1547 self.display_map.update(cx, |display_map, cx| {
1548 let snapshot = display_map.snapshot(cx);
1549 clone.display_map.update(cx, |display_map, cx| {
1550 display_map.set_state(&snapshot, cx);
1551 });
1552 });
1553 clone.selections.clone_state(&self.selections);
1554 clone.scroll_manager.clone_state(&self.scroll_manager);
1555 clone.searchable = self.searchable;
1556 clone
1557 }
1558
1559 fn new(
1560 mode: EditorMode,
1561 buffer: Model<MultiBuffer>,
1562 project: Option<Model<Project>>,
1563 cx: &mut ViewContext<Self>,
1564 ) -> Self {
1565 let style = cx.text_style();
1566 let font_size = style.font_size.to_pixels(cx.rem_size());
1567 let display_map = cx.new_model(|cx| {
1568 DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
1569 });
1570
1571 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1572
1573 let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1574
1575 let soft_wrap_mode_override =
1576 (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::PreferLine);
1577
1578 let mut project_subscriptions = Vec::new();
1579 if mode == EditorMode::Full {
1580 if let Some(project) = project.as_ref() {
1581 if buffer.read(cx).is_singleton() {
1582 project_subscriptions.push(cx.observe(project, |_, _, cx| {
1583 cx.emit(EditorEvent::TitleChanged);
1584 }));
1585 }
1586 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
1587 if let project::Event::RefreshInlayHints = event {
1588 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1589 };
1590 }));
1591 let task_inventory = project.read(cx).task_inventory().clone();
1592 project_subscriptions.push(cx.observe(&task_inventory, |editor, _, cx| {
1593 editor.tasks_update_task = Some(editor.refresh_runnables(cx));
1594 }));
1595 }
1596 }
1597
1598 let inlay_hint_settings = inlay_hint_settings(
1599 selections.newest_anchor().head(),
1600 &buffer.read(cx).snapshot(cx),
1601 cx,
1602 );
1603
1604 let focus_handle = cx.focus_handle();
1605 cx.on_focus(&focus_handle, Self::handle_focus).detach();
1606 cx.on_blur(&focus_handle, Self::handle_blur).detach();
1607
1608 let mut this = Self {
1609 focus_handle,
1610 buffer: buffer.clone(),
1611 display_map: display_map.clone(),
1612 selections,
1613 scroll_manager: ScrollManager::new(cx),
1614 columnar_selection_tail: None,
1615 add_selections_state: None,
1616 select_next_state: None,
1617 select_prev_state: None,
1618 selection_history: Default::default(),
1619 autoclose_regions: Default::default(),
1620 snippet_stack: Default::default(),
1621 select_larger_syntax_node_stack: Vec::new(),
1622 ime_transaction: Default::default(),
1623 active_diagnostics: None,
1624 soft_wrap_mode_override,
1625 completion_provider: project.clone().map(|project| Box::new(project) as _),
1626 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1627 project,
1628 blink_manager: blink_manager.clone(),
1629 show_local_selections: true,
1630 mode,
1631 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1632 show_gutter: mode == EditorMode::Full,
1633 show_wrap_guides: None,
1634 placeholder_text: None,
1635 highlight_order: 0,
1636 highlighted_rows: HashMap::default(),
1637 background_highlights: Default::default(),
1638 scrollbar_marker_state: ScrollbarMarkerState::default(),
1639 nav_history: None,
1640 context_menu: RwLock::new(None),
1641 mouse_context_menu: None,
1642 completion_tasks: Default::default(),
1643 find_all_references_task_sources: Vec::new(),
1644 next_completion_id: 0,
1645 completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
1646 next_inlay_id: 0,
1647 available_code_actions: Default::default(),
1648 code_actions_task: Default::default(),
1649 document_highlights_task: Default::default(),
1650 pending_rename: Default::default(),
1651 searchable: true,
1652 cursor_shape: Default::default(),
1653 current_line_highlight: EditorSettings::get_global(cx).current_line_highlight,
1654 autoindent_mode: Some(AutoindentMode::EachLine),
1655 collapse_matches: false,
1656 workspace: None,
1657 keymap_context_layers: Default::default(),
1658 input_enabled: true,
1659 use_modal_editing: mode == EditorMode::Full,
1660 read_only: false,
1661 use_autoclose: true,
1662 auto_replace_emoji_shortcode: false,
1663 leader_peer_id: None,
1664 remote_id: None,
1665 hover_state: Default::default(),
1666 hovered_link_state: Default::default(),
1667 inline_completion_provider: None,
1668 active_inline_completion: None,
1669 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1670 expanded_hunks: ExpandedHunks::default(),
1671 gutter_hovered: false,
1672 pixel_position_of_newest_cursor: None,
1673 last_bounds: None,
1674 expect_bounds_change: None,
1675 gutter_dimensions: GutterDimensions::default(),
1676 style: None,
1677 show_cursor_names: false,
1678 hovered_cursors: Default::default(),
1679 editor_actions: Default::default(),
1680 vim_replace_map: Default::default(),
1681 show_inline_completions: mode == EditorMode::Full,
1682 custom_context_menu: None,
1683 show_git_blame_gutter: false,
1684 show_git_blame_inline: false,
1685 show_git_blame_inline_delay_task: None,
1686 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1687 blame: None,
1688 blame_subscription: None,
1689 tasks: Default::default(),
1690 _subscriptions: vec![
1691 cx.observe(&buffer, Self::on_buffer_changed),
1692 cx.subscribe(&buffer, Self::on_buffer_event),
1693 cx.observe(&display_map, Self::on_display_map_changed),
1694 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1695 cx.observe_global::<SettingsStore>(Self::settings_changed),
1696 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1697 cx.observe_window_activation(|editor, cx| {
1698 let active = cx.is_window_active();
1699 editor.blink_manager.update(cx, |blink_manager, cx| {
1700 if active {
1701 blink_manager.enable(cx);
1702 } else {
1703 blink_manager.show_cursor(cx);
1704 blink_manager.disable(cx);
1705 }
1706 });
1707 }),
1708 ],
1709 tasks_update_task: None,
1710 };
1711 this.tasks_update_task = Some(this.refresh_runnables(cx));
1712 this._subscriptions.extend(project_subscriptions);
1713
1714 this.end_selection(cx);
1715 this.scroll_manager.show_scrollbar(cx);
1716
1717 if mode == EditorMode::Full {
1718 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1719 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1720
1721 if this.git_blame_inline_enabled {
1722 this.git_blame_inline_enabled = true;
1723 this.start_git_blame_inline(false, cx);
1724 }
1725 }
1726
1727 this.report_editor_event("open", None, cx);
1728 this
1729 }
1730
1731 pub fn mouse_menu_is_focused(&self, cx: &mut WindowContext) -> bool {
1732 self.mouse_context_menu
1733 .as_ref()
1734 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(cx))
1735 }
1736
1737 fn key_context(&self, cx: &AppContext) -> KeyContext {
1738 let mut key_context = KeyContext::new_with_defaults();
1739 key_context.add("Editor");
1740 let mode = match self.mode {
1741 EditorMode::SingleLine => "single_line",
1742 EditorMode::AutoHeight { .. } => "auto_height",
1743 EditorMode::Full => "full",
1744 };
1745 key_context.set("mode", mode);
1746 if self.pending_rename.is_some() {
1747 key_context.add("renaming");
1748 }
1749 if self.context_menu_visible() {
1750 match self.context_menu.read().as_ref() {
1751 Some(ContextMenu::Completions(_)) => {
1752 key_context.add("menu");
1753 key_context.add("showing_completions")
1754 }
1755 Some(ContextMenu::CodeActions(_)) => {
1756 key_context.add("menu");
1757 key_context.add("showing_code_actions")
1758 }
1759 None => {}
1760 }
1761 }
1762
1763 for layer in self.keymap_context_layers.values() {
1764 key_context.extend(layer);
1765 }
1766
1767 if let Some(extension) = self
1768 .buffer
1769 .read(cx)
1770 .as_singleton()
1771 .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
1772 {
1773 key_context.set("extension", extension.to_string());
1774 }
1775
1776 if self.has_active_inline_completion(cx) {
1777 key_context.add("copilot_suggestion");
1778 key_context.add("inline_completion");
1779 }
1780
1781 key_context
1782 }
1783
1784 pub fn new_file(
1785 workspace: &mut Workspace,
1786 _: &workspace::NewFile,
1787 cx: &mut ViewContext<Workspace>,
1788 ) {
1789 let project = workspace.project().clone();
1790 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1791
1792 cx.spawn(|workspace, mut cx| async move {
1793 let buffer = create.await?;
1794 workspace.update(&mut cx, |workspace, cx| {
1795 workspace.add_item_to_active_pane(
1796 Box::new(
1797 cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
1798 ),
1799 None,
1800 cx,
1801 )
1802 })
1803 })
1804 .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
1805 ErrorCode::RemoteUpgradeRequired => Some(format!(
1806 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1807 e.error_tag("required").unwrap_or("the latest version")
1808 )),
1809 _ => None,
1810 });
1811 }
1812
1813 pub fn new_file_in_direction(
1814 workspace: &mut Workspace,
1815 action: &workspace::NewFileInDirection,
1816 cx: &mut ViewContext<Workspace>,
1817 ) {
1818 let project = workspace.project().clone();
1819 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1820 let direction = action.0;
1821
1822 cx.spawn(|workspace, mut cx| async move {
1823 let buffer = create.await?;
1824 workspace.update(&mut cx, move |workspace, cx| {
1825 workspace.split_item(
1826 direction,
1827 Box::new(
1828 cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
1829 ),
1830 cx,
1831 )
1832 })?;
1833 anyhow::Ok(())
1834 })
1835 .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
1836 ErrorCode::RemoteUpgradeRequired => Some(format!(
1837 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1838 e.error_tag("required").unwrap_or("the latest version")
1839 )),
1840 _ => None,
1841 });
1842 }
1843
1844 pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
1845 self.buffer.read(cx).replica_id()
1846 }
1847
1848 pub fn leader_peer_id(&self) -> Option<PeerId> {
1849 self.leader_peer_id
1850 }
1851
1852 pub fn buffer(&self) -> &Model<MultiBuffer> {
1853 &self.buffer
1854 }
1855
1856 pub fn workspace(&self) -> Option<View<Workspace>> {
1857 self.workspace.as_ref()?.0.upgrade()
1858 }
1859
1860 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
1861 self.buffer().read(cx).title(cx)
1862 }
1863
1864 pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
1865 EditorSnapshot {
1866 mode: self.mode,
1867 show_gutter: self.show_gutter,
1868 render_git_blame_gutter: self.render_git_blame_gutter(cx),
1869 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1870 scroll_anchor: self.scroll_manager.anchor(),
1871 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1872 placeholder_text: self.placeholder_text.clone(),
1873 is_focused: self.focus_handle.is_focused(cx),
1874 current_line_highlight: self.current_line_highlight,
1875 }
1876 }
1877
1878 pub fn language_at<T: ToOffset>(&self, point: T, cx: &AppContext) -> Option<Arc<Language>> {
1879 self.buffer.read(cx).language_at(point, cx)
1880 }
1881
1882 pub fn file_at<T: ToOffset>(
1883 &self,
1884 point: T,
1885 cx: &AppContext,
1886 ) -> Option<Arc<dyn language::File>> {
1887 self.buffer.read(cx).read(cx).file_at(point).cloned()
1888 }
1889
1890 pub fn active_excerpt(
1891 &self,
1892 cx: &AppContext,
1893 ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
1894 self.buffer
1895 .read(cx)
1896 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1897 }
1898
1899 pub fn mode(&self) -> EditorMode {
1900 self.mode
1901 }
1902
1903 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1904 self.collaboration_hub.as_deref()
1905 }
1906
1907 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1908 self.collaboration_hub = Some(hub);
1909 }
1910
1911 pub fn set_custom_context_menu(
1912 &mut self,
1913 f: impl 'static
1914 + Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
1915 ) {
1916 self.custom_context_menu = Some(Box::new(f))
1917 }
1918
1919 pub fn set_completion_provider(&mut self, hub: Box<dyn CompletionProvider>) {
1920 self.completion_provider = Some(hub);
1921 }
1922
1923 pub fn set_inline_completion_provider<T>(
1924 &mut self,
1925 provider: Option<Model<T>>,
1926 cx: &mut ViewContext<Self>,
1927 ) where
1928 T: InlineCompletionProvider,
1929 {
1930 self.inline_completion_provider =
1931 provider.map(|provider| RegisteredInlineCompletionProvider {
1932 _subscription: cx.observe(&provider, |this, _, cx| {
1933 if this.focus_handle.is_focused(cx) {
1934 this.update_visible_inline_completion(cx);
1935 }
1936 }),
1937 provider: Arc::new(provider),
1938 });
1939 self.refresh_inline_completion(false, cx);
1940 }
1941
1942 pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
1943 self.placeholder_text.as_deref()
1944 }
1945
1946 pub fn set_placeholder_text(
1947 &mut self,
1948 placeholder_text: impl Into<Arc<str>>,
1949 cx: &mut ViewContext<Self>,
1950 ) {
1951 let placeholder_text = Some(placeholder_text.into());
1952 if self.placeholder_text != placeholder_text {
1953 self.placeholder_text = placeholder_text;
1954 cx.notify();
1955 }
1956 }
1957
1958 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
1959 self.cursor_shape = cursor_shape;
1960 cx.notify();
1961 }
1962
1963 pub fn set_current_line_highlight(&mut self, current_line_highlight: CurrentLineHighlight) {
1964 self.current_line_highlight = current_line_highlight;
1965 }
1966
1967 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
1968 self.collapse_matches = collapse_matches;
1969 }
1970
1971 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
1972 if self.collapse_matches {
1973 return range.start..range.start;
1974 }
1975 range.clone()
1976 }
1977
1978 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
1979 if self.display_map.read(cx).clip_at_line_ends != clip {
1980 self.display_map
1981 .update(cx, |map, _| map.clip_at_line_ends = clip);
1982 }
1983 }
1984
1985 pub fn set_keymap_context_layer<Tag: 'static>(
1986 &mut self,
1987 context: KeyContext,
1988 cx: &mut ViewContext<Self>,
1989 ) {
1990 self.keymap_context_layers
1991 .insert(TypeId::of::<Tag>(), context);
1992 cx.notify();
1993 }
1994
1995 pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
1996 self.keymap_context_layers.remove(&TypeId::of::<Tag>());
1997 cx.notify();
1998 }
1999
2000 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2001 self.input_enabled = input_enabled;
2002 }
2003
2004 pub fn set_autoindent(&mut self, autoindent: bool) {
2005 if autoindent {
2006 self.autoindent_mode = Some(AutoindentMode::EachLine);
2007 } else {
2008 self.autoindent_mode = None;
2009 }
2010 }
2011
2012 pub fn read_only(&self, cx: &AppContext) -> bool {
2013 self.read_only || self.buffer.read(cx).read_only()
2014 }
2015
2016 pub fn set_read_only(&mut self, read_only: bool) {
2017 self.read_only = read_only;
2018 }
2019
2020 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2021 self.use_autoclose = autoclose;
2022 }
2023
2024 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2025 self.auto_replace_emoji_shortcode = auto_replace;
2026 }
2027
2028 pub fn set_show_inline_completions(&mut self, show_inline_completions: bool) {
2029 self.show_inline_completions = show_inline_completions;
2030 }
2031
2032 pub fn set_use_modal_editing(&mut self, to: bool) {
2033 self.use_modal_editing = to;
2034 }
2035
2036 pub fn use_modal_editing(&self) -> bool {
2037 self.use_modal_editing
2038 }
2039
2040 fn selections_did_change(
2041 &mut self,
2042 local: bool,
2043 old_cursor_position: &Anchor,
2044 cx: &mut ViewContext<Self>,
2045 ) {
2046 // Copy selections to primary selection buffer
2047 #[cfg(target_os = "linux")]
2048 if local {
2049 let selections = self.selections.all::<usize>(cx);
2050 let buffer_handle = self.buffer.read(cx).read(cx);
2051
2052 let mut text = String::new();
2053 for (index, selection) in selections.iter().enumerate() {
2054 let text_for_selection = buffer_handle
2055 .text_for_range(selection.start..selection.end)
2056 .collect::<String>();
2057
2058 text.push_str(&text_for_selection);
2059 if index != selections.len() - 1 {
2060 text.push('\n');
2061 }
2062 }
2063
2064 if !text.is_empty() {
2065 cx.write_to_primary(ClipboardItem::new(text));
2066 }
2067 }
2068
2069 if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
2070 self.buffer.update(cx, |buffer, cx| {
2071 buffer.set_active_selections(
2072 &self.selections.disjoint_anchors(),
2073 self.selections.line_mode,
2074 self.cursor_shape,
2075 cx,
2076 )
2077 });
2078 }
2079
2080 let display_map = self
2081 .display_map
2082 .update(cx, |display_map, cx| display_map.snapshot(cx));
2083 let buffer = &display_map.buffer_snapshot;
2084 self.add_selections_state = None;
2085 self.select_next_state = None;
2086 self.select_prev_state = None;
2087 self.select_larger_syntax_node_stack.clear();
2088 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2089 self.snippet_stack
2090 .invalidate(&self.selections.disjoint_anchors(), buffer);
2091 self.take_rename(false, cx);
2092
2093 let new_cursor_position = self.selections.newest_anchor().head();
2094
2095 self.push_to_nav_history(
2096 *old_cursor_position,
2097 Some(new_cursor_position.to_point(buffer)),
2098 cx,
2099 );
2100
2101 if local {
2102 let new_cursor_position = self.selections.newest_anchor().head();
2103 let mut context_menu = self.context_menu.write();
2104 let completion_menu = match context_menu.as_ref() {
2105 Some(ContextMenu::Completions(menu)) => Some(menu),
2106
2107 _ => {
2108 *context_menu = None;
2109 None
2110 }
2111 };
2112
2113 if let Some(completion_menu) = completion_menu {
2114 let cursor_position = new_cursor_position.to_offset(buffer);
2115 let (word_range, kind) = buffer.surrounding_word(completion_menu.initial_position);
2116 if kind == Some(CharKind::Word)
2117 && word_range.to_inclusive().contains(&cursor_position)
2118 {
2119 let mut completion_menu = completion_menu.clone();
2120 drop(context_menu);
2121
2122 let query = Self::completion_query(buffer, cursor_position);
2123 cx.spawn(move |this, mut cx| async move {
2124 completion_menu
2125 .filter(query.as_deref(), cx.background_executor().clone())
2126 .await;
2127
2128 this.update(&mut cx, |this, cx| {
2129 let mut context_menu = this.context_menu.write();
2130 let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else {
2131 return;
2132 };
2133
2134 if menu.id > completion_menu.id {
2135 return;
2136 }
2137
2138 *context_menu = Some(ContextMenu::Completions(completion_menu));
2139 drop(context_menu);
2140 cx.notify();
2141 })
2142 })
2143 .detach();
2144
2145 self.show_completions(&ShowCompletions, cx);
2146 } else {
2147 drop(context_menu);
2148 self.hide_context_menu(cx);
2149 }
2150 } else {
2151 drop(context_menu);
2152 }
2153
2154 hide_hover(self, cx);
2155
2156 if old_cursor_position.to_display_point(&display_map).row()
2157 != new_cursor_position.to_display_point(&display_map).row()
2158 {
2159 self.available_code_actions.take();
2160 }
2161 self.refresh_code_actions(cx);
2162 self.refresh_document_highlights(cx);
2163 refresh_matching_bracket_highlights(self, cx);
2164 self.discard_inline_completion(cx);
2165 if self.git_blame_inline_enabled {
2166 self.start_inline_blame_timer(cx);
2167 }
2168 }
2169
2170 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2171 cx.emit(EditorEvent::SelectionsChanged { local });
2172
2173 if self.selections.disjoint_anchors().len() == 1 {
2174 cx.emit(SearchEvent::ActiveMatchChanged)
2175 }
2176
2177 cx.notify();
2178 }
2179
2180 pub fn change_selections<R>(
2181 &mut self,
2182 autoscroll: Option<Autoscroll>,
2183 cx: &mut ViewContext<Self>,
2184 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2185 ) -> R {
2186 let old_cursor_position = self.selections.newest_anchor().head();
2187 self.push_to_selection_history();
2188
2189 let (changed, result) = self.selections.change_with(cx, change);
2190
2191 if changed {
2192 if let Some(autoscroll) = autoscroll {
2193 self.request_autoscroll(autoscroll, cx);
2194 }
2195 self.selections_did_change(true, &old_cursor_position, cx);
2196 }
2197
2198 result
2199 }
2200
2201 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2202 where
2203 I: IntoIterator<Item = (Range<S>, T)>,
2204 S: ToOffset,
2205 T: Into<Arc<str>>,
2206 {
2207 if self.read_only(cx) {
2208 return;
2209 }
2210
2211 self.buffer
2212 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2213 }
2214
2215 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2216 where
2217 I: IntoIterator<Item = (Range<S>, T)>,
2218 S: ToOffset,
2219 T: Into<Arc<str>>,
2220 {
2221 if self.read_only(cx) {
2222 return;
2223 }
2224
2225 self.buffer.update(cx, |buffer, cx| {
2226 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2227 });
2228 }
2229
2230 pub fn edit_with_block_indent<I, S, T>(
2231 &mut self,
2232 edits: I,
2233 original_indent_columns: Vec<u32>,
2234 cx: &mut ViewContext<Self>,
2235 ) where
2236 I: IntoIterator<Item = (Range<S>, T)>,
2237 S: ToOffset,
2238 T: Into<Arc<str>>,
2239 {
2240 if self.read_only(cx) {
2241 return;
2242 }
2243
2244 self.buffer.update(cx, |buffer, cx| {
2245 buffer.edit(
2246 edits,
2247 Some(AutoindentMode::Block {
2248 original_indent_columns,
2249 }),
2250 cx,
2251 )
2252 });
2253 }
2254
2255 fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
2256 self.hide_context_menu(cx);
2257
2258 match phase {
2259 SelectPhase::Begin {
2260 position,
2261 add,
2262 click_count,
2263 } => self.begin_selection(position, add, click_count, cx),
2264 SelectPhase::BeginColumnar {
2265 position,
2266 goal_column,
2267 } => self.begin_columnar_selection(position, goal_column, cx),
2268 SelectPhase::Extend {
2269 position,
2270 click_count,
2271 } => self.extend_selection(position, click_count, cx),
2272 SelectPhase::Update {
2273 position,
2274 goal_column,
2275 scroll_delta,
2276 } => self.update_selection(position, goal_column, scroll_delta, cx),
2277 SelectPhase::End => self.end_selection(cx),
2278 }
2279 }
2280
2281 fn extend_selection(
2282 &mut self,
2283 position: DisplayPoint,
2284 click_count: usize,
2285 cx: &mut ViewContext<Self>,
2286 ) {
2287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2288 let tail = self.selections.newest::<usize>(cx).tail();
2289 self.begin_selection(position, false, click_count, cx);
2290
2291 let position = position.to_offset(&display_map, Bias::Left);
2292 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2293
2294 let mut pending_selection = self
2295 .selections
2296 .pending_anchor()
2297 .expect("extend_selection not called with pending selection");
2298 if position >= tail {
2299 pending_selection.start = tail_anchor;
2300 } else {
2301 pending_selection.end = tail_anchor;
2302 pending_selection.reversed = true;
2303 }
2304
2305 let mut pending_mode = self.selections.pending_mode().unwrap();
2306 match &mut pending_mode {
2307 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2308 _ => {}
2309 }
2310
2311 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
2312 s.set_pending(pending_selection, pending_mode)
2313 });
2314 }
2315
2316 fn begin_selection(
2317 &mut self,
2318 position: DisplayPoint,
2319 add: bool,
2320 click_count: usize,
2321 cx: &mut ViewContext<Self>,
2322 ) {
2323 if !self.focus_handle.is_focused(cx) {
2324 cx.focus(&self.focus_handle);
2325 }
2326
2327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2328 let buffer = &display_map.buffer_snapshot;
2329 let newest_selection = self.selections.newest_anchor().clone();
2330 let position = display_map.clip_point(position, Bias::Left);
2331
2332 let start;
2333 let end;
2334 let mode;
2335 let auto_scroll;
2336 match click_count {
2337 1 => {
2338 start = buffer.anchor_before(position.to_point(&display_map));
2339 end = start;
2340 mode = SelectMode::Character;
2341 auto_scroll = true;
2342 }
2343 2 => {
2344 let range = movement::surrounding_word(&display_map, position);
2345 start = buffer.anchor_before(range.start.to_point(&display_map));
2346 end = buffer.anchor_before(range.end.to_point(&display_map));
2347 mode = SelectMode::Word(start..end);
2348 auto_scroll = true;
2349 }
2350 3 => {
2351 let position = display_map
2352 .clip_point(position, Bias::Left)
2353 .to_point(&display_map);
2354 let line_start = display_map.prev_line_boundary(position).0;
2355 let next_line_start = buffer.clip_point(
2356 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2357 Bias::Left,
2358 );
2359 start = buffer.anchor_before(line_start);
2360 end = buffer.anchor_before(next_line_start);
2361 mode = SelectMode::Line(start..end);
2362 auto_scroll = true;
2363 }
2364 _ => {
2365 start = buffer.anchor_before(0);
2366 end = buffer.anchor_before(buffer.len());
2367 mode = SelectMode::All;
2368 auto_scroll = false;
2369 }
2370 }
2371
2372 self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
2373 if !add {
2374 s.clear_disjoint();
2375 } else if click_count > 1 {
2376 s.delete(newest_selection.id)
2377 }
2378
2379 s.set_pending_anchor_range(start..end, mode);
2380 });
2381 }
2382
2383 fn begin_columnar_selection(
2384 &mut self,
2385 position: DisplayPoint,
2386 goal_column: u32,
2387 cx: &mut ViewContext<Self>,
2388 ) {
2389 if !self.focus_handle.is_focused(cx) {
2390 cx.focus(&self.focus_handle);
2391 }
2392
2393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2394 let tail = self.selections.newest::<Point>(cx).tail();
2395 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2396
2397 self.select_columns(
2398 tail.to_display_point(&display_map),
2399 position,
2400 goal_column,
2401 &display_map,
2402 cx,
2403 );
2404 }
2405
2406 fn update_selection(
2407 &mut self,
2408 position: DisplayPoint,
2409 goal_column: u32,
2410 scroll_delta: gpui::Point<f32>,
2411 cx: &mut ViewContext<Self>,
2412 ) {
2413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2414
2415 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2416 let tail = tail.to_display_point(&display_map);
2417 self.select_columns(tail, position, goal_column, &display_map, cx);
2418 } else if let Some(mut pending) = self.selections.pending_anchor() {
2419 let buffer = self.buffer.read(cx).snapshot(cx);
2420 let head;
2421 let tail;
2422 let mode = self.selections.pending_mode().unwrap();
2423 match &mode {
2424 SelectMode::Character => {
2425 head = position.to_point(&display_map);
2426 tail = pending.tail().to_point(&buffer);
2427 }
2428 SelectMode::Word(original_range) => {
2429 let original_display_range = original_range.start.to_display_point(&display_map)
2430 ..original_range.end.to_display_point(&display_map);
2431 let original_buffer_range = original_display_range.start.to_point(&display_map)
2432 ..original_display_range.end.to_point(&display_map);
2433 if movement::is_inside_word(&display_map, position)
2434 || original_display_range.contains(&position)
2435 {
2436 let word_range = movement::surrounding_word(&display_map, position);
2437 if word_range.start < original_display_range.start {
2438 head = word_range.start.to_point(&display_map);
2439 } else {
2440 head = word_range.end.to_point(&display_map);
2441 }
2442 } else {
2443 head = position.to_point(&display_map);
2444 }
2445
2446 if head <= original_buffer_range.start {
2447 tail = original_buffer_range.end;
2448 } else {
2449 tail = original_buffer_range.start;
2450 }
2451 }
2452 SelectMode::Line(original_range) => {
2453 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2454
2455 let position = display_map
2456 .clip_point(position, Bias::Left)
2457 .to_point(&display_map);
2458 let line_start = display_map.prev_line_boundary(position).0;
2459 let next_line_start = buffer.clip_point(
2460 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2461 Bias::Left,
2462 );
2463
2464 if line_start < original_range.start {
2465 head = line_start
2466 } else {
2467 head = next_line_start
2468 }
2469
2470 if head <= original_range.start {
2471 tail = original_range.end;
2472 } else {
2473 tail = original_range.start;
2474 }
2475 }
2476 SelectMode::All => {
2477 return;
2478 }
2479 };
2480
2481 if head < tail {
2482 pending.start = buffer.anchor_before(head);
2483 pending.end = buffer.anchor_before(tail);
2484 pending.reversed = true;
2485 } else {
2486 pending.start = buffer.anchor_before(tail);
2487 pending.end = buffer.anchor_before(head);
2488 pending.reversed = false;
2489 }
2490
2491 self.change_selections(None, cx, |s| {
2492 s.set_pending(pending, mode);
2493 });
2494 } else {
2495 log::error!("update_selection dispatched with no pending selection");
2496 return;
2497 }
2498
2499 self.apply_scroll_delta(scroll_delta, cx);
2500 cx.notify();
2501 }
2502
2503 fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
2504 self.columnar_selection_tail.take();
2505 if self.selections.pending_anchor().is_some() {
2506 let selections = self.selections.all::<usize>(cx);
2507 self.change_selections(None, cx, |s| {
2508 s.select(selections);
2509 s.clear_pending();
2510 });
2511 }
2512 }
2513
2514 fn select_columns(
2515 &mut self,
2516 tail: DisplayPoint,
2517 head: DisplayPoint,
2518 goal_column: u32,
2519 display_map: &DisplaySnapshot,
2520 cx: &mut ViewContext<Self>,
2521 ) {
2522 let start_row = cmp::min(tail.row(), head.row());
2523 let end_row = cmp::max(tail.row(), head.row());
2524 let start_column = cmp::min(tail.column(), goal_column);
2525 let end_column = cmp::max(tail.column(), goal_column);
2526 let reversed = start_column < tail.column();
2527
2528 let selection_ranges = (start_row.0..=end_row.0)
2529 .map(DisplayRow)
2530 .filter_map(|row| {
2531 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2532 let start = display_map
2533 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2534 .to_point(display_map);
2535 let end = display_map
2536 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2537 .to_point(display_map);
2538 if reversed {
2539 Some(end..start)
2540 } else {
2541 Some(start..end)
2542 }
2543 } else {
2544 None
2545 }
2546 })
2547 .collect::<Vec<_>>();
2548
2549 self.change_selections(None, cx, |s| {
2550 s.select_ranges(selection_ranges);
2551 });
2552 cx.notify();
2553 }
2554
2555 pub fn has_pending_nonempty_selection(&self) -> bool {
2556 let pending_nonempty_selection = match self.selections.pending_anchor() {
2557 Some(Selection { start, end, .. }) => start != end,
2558 None => false,
2559 };
2560 pending_nonempty_selection || self.columnar_selection_tail.is_some()
2561 }
2562
2563 pub fn has_pending_selection(&self) -> bool {
2564 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2565 }
2566
2567 pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
2568 self.clear_expanded_diff_hunks(cx);
2569 if self.dismiss_menus_and_popups(cx) {
2570 return;
2571 }
2572
2573 if self.mode == EditorMode::Full {
2574 if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
2575 return;
2576 }
2577 }
2578
2579 cx.propagate();
2580 }
2581
2582 pub fn dismiss_menus_and_popups(&mut self, cx: &mut ViewContext<Self>) -> bool {
2583 if self.take_rename(false, cx).is_some() {
2584 return true;
2585 }
2586
2587 if hide_hover(self, cx) {
2588 return true;
2589 }
2590
2591 if self.hide_context_menu(cx).is_some() {
2592 return true;
2593 }
2594
2595 if self.discard_inline_completion(cx) {
2596 return true;
2597 }
2598
2599 if self.snippet_stack.pop().is_some() {
2600 return true;
2601 }
2602
2603 if self.mode == EditorMode::Full {
2604 if self.active_diagnostics.is_some() {
2605 self.dismiss_diagnostics(cx);
2606 return true;
2607 }
2608 }
2609
2610 false
2611 }
2612
2613 pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
2614 let text: Arc<str> = text.into();
2615
2616 if self.read_only(cx) {
2617 return;
2618 }
2619
2620 let selections = self.selections.all_adjusted(cx);
2621 let mut brace_inserted = false;
2622 let mut edits = Vec::new();
2623 let mut new_selections = Vec::with_capacity(selections.len());
2624 let mut new_autoclose_regions = Vec::new();
2625 let snapshot = self.buffer.read(cx).read(cx);
2626
2627 for (selection, autoclose_region) in
2628 self.selections_with_autoclose_regions(selections, &snapshot)
2629 {
2630 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
2631 // Determine if the inserted text matches the opening or closing
2632 // bracket of any of this language's bracket pairs.
2633 let mut bracket_pair = None;
2634 let mut is_bracket_pair_start = false;
2635 let mut is_bracket_pair_end = false;
2636 if !text.is_empty() {
2637 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
2638 // and they are removing the character that triggered IME popup.
2639 for (pair, enabled) in scope.brackets() {
2640 if !pair.close {
2641 continue;
2642 }
2643
2644 if enabled && pair.start.ends_with(text.as_ref()) {
2645 bracket_pair = Some(pair.clone());
2646 is_bracket_pair_start = true;
2647 break;
2648 }
2649 if pair.end.as_str() == text.as_ref() {
2650 bracket_pair = Some(pair.clone());
2651 is_bracket_pair_end = true;
2652 break;
2653 }
2654 }
2655 }
2656
2657 if let Some(bracket_pair) = bracket_pair {
2658 if selection.is_empty() {
2659 if is_bracket_pair_start {
2660 let prefix_len = bracket_pair.start.len() - text.len();
2661
2662 // If the inserted text is a suffix of an opening bracket and the
2663 // selection is preceded by the rest of the opening bracket, then
2664 // insert the closing bracket.
2665 let following_text_allows_autoclose = snapshot
2666 .chars_at(selection.start)
2667 .next()
2668 .map_or(true, |c| scope.should_autoclose_before(c));
2669 let preceding_text_matches_prefix = prefix_len == 0
2670 || (selection.start.column >= (prefix_len as u32)
2671 && snapshot.contains_str_at(
2672 Point::new(
2673 selection.start.row,
2674 selection.start.column - (prefix_len as u32),
2675 ),
2676 &bracket_pair.start[..prefix_len],
2677 ));
2678 let autoclose = self.use_autoclose
2679 && snapshot.settings_at(selection.start, cx).use_autoclose;
2680 if autoclose
2681 && following_text_allows_autoclose
2682 && preceding_text_matches_prefix
2683 {
2684 let anchor = snapshot.anchor_before(selection.end);
2685 new_selections.push((selection.map(|_| anchor), text.len()));
2686 new_autoclose_regions.push((
2687 anchor,
2688 text.len(),
2689 selection.id,
2690 bracket_pair.clone(),
2691 ));
2692 edits.push((
2693 selection.range(),
2694 format!("{}{}", text, bracket_pair.end).into(),
2695 ));
2696 brace_inserted = true;
2697 continue;
2698 }
2699 }
2700
2701 if let Some(region) = autoclose_region {
2702 // If the selection is followed by an auto-inserted closing bracket,
2703 // then don't insert that closing bracket again; just move the selection
2704 // past the closing bracket.
2705 let should_skip = selection.end == region.range.end.to_point(&snapshot)
2706 && text.as_ref() == region.pair.end.as_str();
2707 if should_skip {
2708 let anchor = snapshot.anchor_after(selection.end);
2709 new_selections
2710 .push((selection.map(|_| anchor), region.pair.end.len()));
2711 continue;
2712 }
2713 }
2714
2715 let always_treat_brackets_as_autoclosed = snapshot
2716 .settings_at(selection.start, cx)
2717 .always_treat_brackets_as_autoclosed;
2718 if always_treat_brackets_as_autoclosed
2719 && is_bracket_pair_end
2720 && snapshot.contains_str_at(selection.end, text.as_ref())
2721 {
2722 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
2723 // and the inserted text is a closing bracket and the selection is followed
2724 // by the closing bracket then move the selection past the closing bracket.
2725 let anchor = snapshot.anchor_after(selection.end);
2726 new_selections.push((selection.map(|_| anchor), text.len()));
2727 continue;
2728 }
2729 }
2730 // If an opening bracket is 1 character long and is typed while
2731 // text is selected, then surround that text with the bracket pair.
2732 else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 {
2733 edits.push((selection.start..selection.start, text.clone()));
2734 edits.push((
2735 selection.end..selection.end,
2736 bracket_pair.end.as_str().into(),
2737 ));
2738 brace_inserted = true;
2739 new_selections.push((
2740 Selection {
2741 id: selection.id,
2742 start: snapshot.anchor_after(selection.start),
2743 end: snapshot.anchor_before(selection.end),
2744 reversed: selection.reversed,
2745 goal: selection.goal,
2746 },
2747 0,
2748 ));
2749 continue;
2750 }
2751 }
2752 }
2753
2754 if self.auto_replace_emoji_shortcode
2755 && selection.is_empty()
2756 && text.as_ref().ends_with(':')
2757 {
2758 if let Some(possible_emoji_short_code) =
2759 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
2760 {
2761 if !possible_emoji_short_code.is_empty() {
2762 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
2763 let emoji_shortcode_start = Point::new(
2764 selection.start.row,
2765 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
2766 );
2767
2768 // Remove shortcode from buffer
2769 edits.push((
2770 emoji_shortcode_start..selection.start,
2771 "".to_string().into(),
2772 ));
2773 new_selections.push((
2774 Selection {
2775 id: selection.id,
2776 start: snapshot.anchor_after(emoji_shortcode_start),
2777 end: snapshot.anchor_before(selection.start),
2778 reversed: selection.reversed,
2779 goal: selection.goal,
2780 },
2781 0,
2782 ));
2783
2784 // Insert emoji
2785 let selection_start_anchor = snapshot.anchor_after(selection.start);
2786 new_selections.push((selection.map(|_| selection_start_anchor), 0));
2787 edits.push((selection.start..selection.end, emoji.to_string().into()));
2788
2789 continue;
2790 }
2791 }
2792 }
2793 }
2794
2795 // If not handling any auto-close operation, then just replace the selected
2796 // text with the given input and move the selection to the end of the
2797 // newly inserted text.
2798 let anchor = snapshot.anchor_after(selection.end);
2799 new_selections.push((selection.map(|_| anchor), 0));
2800 edits.push((selection.start..selection.end, text.clone()));
2801 }
2802
2803 drop(snapshot);
2804 self.transact(cx, |this, cx| {
2805 this.buffer.update(cx, |buffer, cx| {
2806 buffer.edit(edits, this.autoindent_mode.clone(), cx);
2807 });
2808
2809 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
2810 let new_selection_deltas = new_selections.iter().map(|e| e.1);
2811 let snapshot = this.buffer.read(cx).read(cx);
2812 let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
2813 .zip(new_selection_deltas)
2814 .map(|(selection, delta)| Selection {
2815 id: selection.id,
2816 start: selection.start + delta,
2817 end: selection.end + delta,
2818 reversed: selection.reversed,
2819 goal: SelectionGoal::None,
2820 })
2821 .collect::<Vec<_>>();
2822
2823 let mut i = 0;
2824 for (position, delta, selection_id, pair) in new_autoclose_regions {
2825 let position = position.to_offset(&snapshot) + delta;
2826 let start = snapshot.anchor_before(position);
2827 let end = snapshot.anchor_after(position);
2828 while let Some(existing_state) = this.autoclose_regions.get(i) {
2829 match existing_state.range.start.cmp(&start, &snapshot) {
2830 Ordering::Less => i += 1,
2831 Ordering::Greater => break,
2832 Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
2833 Ordering::Less => i += 1,
2834 Ordering::Equal => break,
2835 Ordering::Greater => break,
2836 },
2837 }
2838 }
2839 this.autoclose_regions.insert(
2840 i,
2841 AutocloseRegion {
2842 selection_id,
2843 range: start..end,
2844 pair,
2845 },
2846 );
2847 }
2848
2849 drop(snapshot);
2850 let had_active_inline_completion = this.has_active_inline_completion(cx);
2851 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
2852
2853 if brace_inserted {
2854 // If we inserted a brace while composing text (i.e. typing `"` on a
2855 // Brazilian keyboard), exit the composing state because most likely
2856 // the user wanted to surround the selection.
2857 this.unmark_text(cx);
2858 } else if EditorSettings::get_global(cx).use_on_type_format {
2859 if let Some(on_type_format_task) =
2860 this.trigger_on_type_formatting(text.to_string(), cx)
2861 {
2862 on_type_format_task.detach_and_log_err(cx);
2863 }
2864 }
2865
2866 let trigger_in_words = !had_active_inline_completion;
2867 this.trigger_completion_on_input(&text, trigger_in_words, cx);
2868 this.refresh_inline_completion(true, cx);
2869 });
2870 }
2871
2872 fn find_possible_emoji_shortcode_at_position(
2873 snapshot: &MultiBufferSnapshot,
2874 position: Point,
2875 ) -> Option<String> {
2876 let mut chars = Vec::new();
2877 let mut found_colon = false;
2878 for char in snapshot.reversed_chars_at(position).take(100) {
2879 // Found a possible emoji shortcode in the middle of the buffer
2880 if found_colon {
2881 if char.is_whitespace() {
2882 chars.reverse();
2883 return Some(chars.iter().collect());
2884 }
2885 // If the previous character is not a whitespace, we are in the middle of a word
2886 // and we only want to complete the shortcode if the word is made up of other emojis
2887 let mut containing_word = String::new();
2888 for ch in snapshot
2889 .reversed_chars_at(position)
2890 .skip(chars.len() + 1)
2891 .take(100)
2892 {
2893 if ch.is_whitespace() {
2894 break;
2895 }
2896 containing_word.push(ch);
2897 }
2898 let containing_word = containing_word.chars().rev().collect::<String>();
2899 if util::word_consists_of_emojis(containing_word.as_str()) {
2900 chars.reverse();
2901 return Some(chars.iter().collect());
2902 }
2903 }
2904
2905 if char.is_whitespace() || !char.is_ascii() {
2906 return None;
2907 }
2908 if char == ':' {
2909 found_colon = true;
2910 } else {
2911 chars.push(char);
2912 }
2913 }
2914 // Found a possible emoji shortcode at the beginning of the buffer
2915 chars.reverse();
2916 Some(chars.iter().collect())
2917 }
2918
2919 pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
2920 self.transact(cx, |this, cx| {
2921 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
2922 let selections = this.selections.all::<usize>(cx);
2923 let multi_buffer = this.buffer.read(cx);
2924 let buffer = multi_buffer.snapshot(cx);
2925 selections
2926 .iter()
2927 .map(|selection| {
2928 let start_point = selection.start.to_point(&buffer);
2929 let mut indent =
2930 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
2931 indent.len = cmp::min(indent.len, start_point.column);
2932 let start = selection.start;
2933 let end = selection.end;
2934 let selection_is_empty = start == end;
2935 let language_scope = buffer.language_scope_at(start);
2936 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
2937 &language_scope
2938 {
2939 let leading_whitespace_len = buffer
2940 .reversed_chars_at(start)
2941 .take_while(|c| c.is_whitespace() && *c != '\n')
2942 .map(|c| c.len_utf8())
2943 .sum::<usize>();
2944
2945 let trailing_whitespace_len = buffer
2946 .chars_at(end)
2947 .take_while(|c| c.is_whitespace() && *c != '\n')
2948 .map(|c| c.len_utf8())
2949 .sum::<usize>();
2950
2951 let insert_extra_newline =
2952 language.brackets().any(|(pair, enabled)| {
2953 let pair_start = pair.start.trim_end();
2954 let pair_end = pair.end.trim_start();
2955
2956 enabled
2957 && pair.newline
2958 && buffer.contains_str_at(
2959 end + trailing_whitespace_len,
2960 pair_end,
2961 )
2962 && buffer.contains_str_at(
2963 (start - leading_whitespace_len)
2964 .saturating_sub(pair_start.len()),
2965 pair_start,
2966 )
2967 });
2968
2969 // Comment extension on newline is allowed only for cursor selections
2970 let comment_delimiter = maybe!({
2971 if !selection_is_empty {
2972 return None;
2973 }
2974
2975 if !multi_buffer.settings_at(0, cx).extend_comment_on_newline {
2976 return None;
2977 }
2978
2979 let delimiters = language.line_comment_prefixes();
2980 let max_len_of_delimiter =
2981 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
2982 let (snapshot, range) =
2983 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
2984
2985 let mut index_of_first_non_whitespace = 0;
2986 let comment_candidate = snapshot
2987 .chars_for_range(range)
2988 .skip_while(|c| {
2989 let should_skip = c.is_whitespace();
2990 if should_skip {
2991 index_of_first_non_whitespace += 1;
2992 }
2993 should_skip
2994 })
2995 .take(max_len_of_delimiter)
2996 .collect::<String>();
2997 let comment_prefix = delimiters.iter().find(|comment_prefix| {
2998 comment_candidate.starts_with(comment_prefix.as_ref())
2999 })?;
3000 let cursor_is_placed_after_comment_marker =
3001 index_of_first_non_whitespace + comment_prefix.len()
3002 <= start_point.column as usize;
3003 if cursor_is_placed_after_comment_marker {
3004 Some(comment_prefix.clone())
3005 } else {
3006 None
3007 }
3008 });
3009 (comment_delimiter, insert_extra_newline)
3010 } else {
3011 (None, false)
3012 };
3013
3014 let capacity_for_delimiter = comment_delimiter
3015 .as_deref()
3016 .map(str::len)
3017 .unwrap_or_default();
3018 let mut new_text =
3019 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3020 new_text.push_str("\n");
3021 new_text.extend(indent.chars());
3022 if let Some(delimiter) = &comment_delimiter {
3023 new_text.push_str(&delimiter);
3024 }
3025 if insert_extra_newline {
3026 new_text = new_text.repeat(2);
3027 }
3028
3029 let anchor = buffer.anchor_after(end);
3030 let new_selection = selection.map(|_| anchor);
3031 (
3032 (start..end, new_text),
3033 (insert_extra_newline, new_selection),
3034 )
3035 })
3036 .unzip()
3037 };
3038
3039 this.edit_with_autoindent(edits, cx);
3040 let buffer = this.buffer.read(cx).snapshot(cx);
3041 let new_selections = selection_fixup_info
3042 .into_iter()
3043 .map(|(extra_newline_inserted, new_selection)| {
3044 let mut cursor = new_selection.end.to_point(&buffer);
3045 if extra_newline_inserted {
3046 cursor.row -= 1;
3047 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3048 }
3049 new_selection.map(|_| cursor)
3050 })
3051 .collect();
3052
3053 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
3054 this.refresh_inline_completion(true, cx);
3055 });
3056 }
3057
3058 pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
3059 let buffer = self.buffer.read(cx);
3060 let snapshot = buffer.snapshot(cx);
3061
3062 let mut edits = Vec::new();
3063 let mut rows = Vec::new();
3064
3065 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3066 let cursor = selection.head();
3067 let row = cursor.row;
3068
3069 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3070
3071 let newline = "\n".to_string();
3072 edits.push((start_of_line..start_of_line, newline));
3073
3074 rows.push(row + rows_inserted as u32);
3075 }
3076
3077 self.transact(cx, |editor, cx| {
3078 editor.edit(edits, cx);
3079
3080 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3081 let mut index = 0;
3082 s.move_cursors_with(|map, _, _| {
3083 let row = rows[index];
3084 index += 1;
3085
3086 let point = Point::new(row, 0);
3087 let boundary = map.next_line_boundary(point).1;
3088 let clipped = map.clip_point(boundary, Bias::Left);
3089
3090 (clipped, SelectionGoal::None)
3091 });
3092 });
3093
3094 let mut indent_edits = Vec::new();
3095 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3096 for row in rows {
3097 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3098 for (row, indent) in indents {
3099 if indent.len == 0 {
3100 continue;
3101 }
3102
3103 let text = match indent.kind {
3104 IndentKind::Space => " ".repeat(indent.len as usize),
3105 IndentKind::Tab => "\t".repeat(indent.len as usize),
3106 };
3107 let point = Point::new(row.0, 0);
3108 indent_edits.push((point..point, text));
3109 }
3110 }
3111 editor.edit(indent_edits, cx);
3112 });
3113 }
3114
3115 pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
3116 let buffer = self.buffer.read(cx);
3117 let snapshot = buffer.snapshot(cx);
3118
3119 let mut edits = Vec::new();
3120 let mut rows = Vec::new();
3121 let mut rows_inserted = 0;
3122
3123 for selection in self.selections.all_adjusted(cx) {
3124 let cursor = selection.head();
3125 let row = cursor.row;
3126
3127 let point = Point::new(row + 1, 0);
3128 let start_of_line = snapshot.clip_point(point, Bias::Left);
3129
3130 let newline = "\n".to_string();
3131 edits.push((start_of_line..start_of_line, newline));
3132
3133 rows_inserted += 1;
3134 rows.push(row + rows_inserted);
3135 }
3136
3137 self.transact(cx, |editor, cx| {
3138 editor.edit(edits, cx);
3139
3140 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3141 let mut index = 0;
3142 s.move_cursors_with(|map, _, _| {
3143 let row = rows[index];
3144 index += 1;
3145
3146 let point = Point::new(row, 0);
3147 let boundary = map.next_line_boundary(point).1;
3148 let clipped = map.clip_point(boundary, Bias::Left);
3149
3150 (clipped, SelectionGoal::None)
3151 });
3152 });
3153
3154 let mut indent_edits = Vec::new();
3155 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3156 for row in rows {
3157 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3158 for (row, indent) in indents {
3159 if indent.len == 0 {
3160 continue;
3161 }
3162
3163 let text = match indent.kind {
3164 IndentKind::Space => " ".repeat(indent.len as usize),
3165 IndentKind::Tab => "\t".repeat(indent.len as usize),
3166 };
3167 let point = Point::new(row.0, 0);
3168 indent_edits.push((point..point, text));
3169 }
3170 }
3171 editor.edit(indent_edits, cx);
3172 });
3173 }
3174
3175 pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
3176 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3177 original_indent_columns: Vec::new(),
3178 });
3179 self.insert_with_autoindent_mode(text, autoindent, cx);
3180 }
3181
3182 fn insert_with_autoindent_mode(
3183 &mut self,
3184 text: &str,
3185 autoindent_mode: Option<AutoindentMode>,
3186 cx: &mut ViewContext<Self>,
3187 ) {
3188 if self.read_only(cx) {
3189 return;
3190 }
3191
3192 let text: Arc<str> = text.into();
3193 self.transact(cx, |this, cx| {
3194 let old_selections = this.selections.all_adjusted(cx);
3195 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3196 let anchors = {
3197 let snapshot = buffer.read(cx);
3198 old_selections
3199 .iter()
3200 .map(|s| {
3201 let anchor = snapshot.anchor_after(s.head());
3202 s.map(|_| anchor)
3203 })
3204 .collect::<Vec<_>>()
3205 };
3206 buffer.edit(
3207 old_selections
3208 .iter()
3209 .map(|s| (s.start..s.end, text.clone())),
3210 autoindent_mode,
3211 cx,
3212 );
3213 anchors
3214 });
3215
3216 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3217 s.select_anchors(selection_anchors);
3218 })
3219 });
3220 }
3221
3222 fn trigger_completion_on_input(
3223 &mut self,
3224 text: &str,
3225 trigger_in_words: bool,
3226 cx: &mut ViewContext<Self>,
3227 ) {
3228 if !EditorSettings::get_global(cx).show_completions_on_input {
3229 return;
3230 }
3231
3232 let selection = self.selections.newest_anchor();
3233 if self
3234 .buffer
3235 .read(cx)
3236 .is_completion_trigger(selection.head(), text, trigger_in_words, cx)
3237 {
3238 self.show_completions(&ShowCompletions, cx);
3239 } else {
3240 self.hide_context_menu(cx);
3241 }
3242 }
3243
3244 /// If any empty selections is touching the start of its innermost containing autoclose
3245 /// region, expand it to select the brackets.
3246 fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
3247 let selections = self.selections.all::<usize>(cx);
3248 let buffer = self.buffer.read(cx).read(cx);
3249 let new_selections = self
3250 .selections_with_autoclose_regions(selections, &buffer)
3251 .map(|(mut selection, region)| {
3252 if !selection.is_empty() {
3253 return selection;
3254 }
3255
3256 if let Some(region) = region {
3257 let mut range = region.range.to_offset(&buffer);
3258 if selection.start == range.start && range.start >= region.pair.start.len() {
3259 range.start -= region.pair.start.len();
3260 if buffer.contains_str_at(range.start, ®ion.pair.start)
3261 && buffer.contains_str_at(range.end, ®ion.pair.end)
3262 {
3263 range.end += region.pair.end.len();
3264 selection.start = range.start;
3265 selection.end = range.end;
3266
3267 return selection;
3268 }
3269 }
3270 }
3271
3272 let always_treat_brackets_as_autoclosed = buffer
3273 .settings_at(selection.start, cx)
3274 .always_treat_brackets_as_autoclosed;
3275
3276 if !always_treat_brackets_as_autoclosed {
3277 return selection;
3278 }
3279
3280 if let Some(scope) = buffer.language_scope_at(selection.start) {
3281 for (pair, enabled) in scope.brackets() {
3282 if !enabled || !pair.close {
3283 continue;
3284 }
3285
3286 if buffer.contains_str_at(selection.start, &pair.end) {
3287 let pair_start_len = pair.start.len();
3288 if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
3289 {
3290 selection.start -= pair_start_len;
3291 selection.end += pair.end.len();
3292
3293 return selection;
3294 }
3295 }
3296 }
3297 }
3298
3299 selection
3300 })
3301 .collect();
3302
3303 drop(buffer);
3304 self.change_selections(None, cx, |selections| selections.select(new_selections));
3305 }
3306
3307 /// Iterate the given selections, and for each one, find the smallest surrounding
3308 /// autoclose region. This uses the ordering of the selections and the autoclose
3309 /// regions to avoid repeated comparisons.
3310 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3311 &'a self,
3312 selections: impl IntoIterator<Item = Selection<D>>,
3313 buffer: &'a MultiBufferSnapshot,
3314 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3315 let mut i = 0;
3316 let mut regions = self.autoclose_regions.as_slice();
3317 selections.into_iter().map(move |selection| {
3318 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3319
3320 let mut enclosing = None;
3321 while let Some(pair_state) = regions.get(i) {
3322 if pair_state.range.end.to_offset(buffer) < range.start {
3323 regions = ®ions[i + 1..];
3324 i = 0;
3325 } else if pair_state.range.start.to_offset(buffer) > range.end {
3326 break;
3327 } else {
3328 if pair_state.selection_id == selection.id {
3329 enclosing = Some(pair_state);
3330 }
3331 i += 1;
3332 }
3333 }
3334
3335 (selection.clone(), enclosing)
3336 })
3337 }
3338
3339 /// Remove any autoclose regions that no longer contain their selection.
3340 fn invalidate_autoclose_regions(
3341 &mut self,
3342 mut selections: &[Selection<Anchor>],
3343 buffer: &MultiBufferSnapshot,
3344 ) {
3345 self.autoclose_regions.retain(|state| {
3346 let mut i = 0;
3347 while let Some(selection) = selections.get(i) {
3348 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3349 selections = &selections[1..];
3350 continue;
3351 }
3352 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3353 break;
3354 }
3355 if selection.id == state.selection_id {
3356 return true;
3357 } else {
3358 i += 1;
3359 }
3360 }
3361 false
3362 });
3363 }
3364
3365 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3366 let offset = position.to_offset(buffer);
3367 let (word_range, kind) = buffer.surrounding_word(offset);
3368 if offset > word_range.start && kind == Some(CharKind::Word) {
3369 Some(
3370 buffer
3371 .text_for_range(word_range.start..offset)
3372 .collect::<String>(),
3373 )
3374 } else {
3375 None
3376 }
3377 }
3378
3379 pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
3380 self.refresh_inlay_hints(
3381 InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
3382 cx,
3383 );
3384 }
3385
3386 pub fn inlay_hints_enabled(&self) -> bool {
3387 self.inlay_hint_cache.enabled
3388 }
3389
3390 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
3391 if self.project.is_none() || self.mode != EditorMode::Full {
3392 return;
3393 }
3394
3395 let reason_description = reason.description();
3396 let ignore_debounce = matches!(
3397 reason,
3398 InlayHintRefreshReason::SettingsChange(_)
3399 | InlayHintRefreshReason::Toggle(_)
3400 | InlayHintRefreshReason::ExcerptsRemoved(_)
3401 );
3402 let (invalidate_cache, required_languages) = match reason {
3403 InlayHintRefreshReason::Toggle(enabled) => {
3404 self.inlay_hint_cache.enabled = enabled;
3405 if enabled {
3406 (InvalidationStrategy::RefreshRequested, None)
3407 } else {
3408 self.inlay_hint_cache.clear();
3409 self.splice_inlays(
3410 self.visible_inlay_hints(cx)
3411 .iter()
3412 .map(|inlay| inlay.id)
3413 .collect(),
3414 Vec::new(),
3415 cx,
3416 );
3417 return;
3418 }
3419 }
3420 InlayHintRefreshReason::SettingsChange(new_settings) => {
3421 match self.inlay_hint_cache.update_settings(
3422 &self.buffer,
3423 new_settings,
3424 self.visible_inlay_hints(cx),
3425 cx,
3426 ) {
3427 ControlFlow::Break(Some(InlaySplice {
3428 to_remove,
3429 to_insert,
3430 })) => {
3431 self.splice_inlays(to_remove, to_insert, cx);
3432 return;
3433 }
3434 ControlFlow::Break(None) => return,
3435 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3436 }
3437 }
3438 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3439 if let Some(InlaySplice {
3440 to_remove,
3441 to_insert,
3442 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3443 {
3444 self.splice_inlays(to_remove, to_insert, cx);
3445 }
3446 return;
3447 }
3448 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3449 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3450 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3451 }
3452 InlayHintRefreshReason::RefreshRequested => {
3453 (InvalidationStrategy::RefreshRequested, None)
3454 }
3455 };
3456
3457 if let Some(InlaySplice {
3458 to_remove,
3459 to_insert,
3460 }) = self.inlay_hint_cache.spawn_hint_refresh(
3461 reason_description,
3462 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3463 invalidate_cache,
3464 ignore_debounce,
3465 cx,
3466 ) {
3467 self.splice_inlays(to_remove, to_insert, cx);
3468 }
3469 }
3470
3471 fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
3472 self.display_map
3473 .read(cx)
3474 .current_inlays()
3475 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
3476 .cloned()
3477 .collect()
3478 }
3479
3480 pub fn excerpts_for_inlay_hints_query(
3481 &self,
3482 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3483 cx: &mut ViewContext<Editor>,
3484 ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
3485 let Some(project) = self.project.as_ref() else {
3486 return HashMap::default();
3487 };
3488 let project = project.read(cx);
3489 let multi_buffer = self.buffer().read(cx);
3490 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3491 let multi_buffer_visible_start = self
3492 .scroll_manager
3493 .anchor()
3494 .anchor
3495 .to_point(&multi_buffer_snapshot);
3496 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
3497 multi_buffer_visible_start
3498 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
3499 Bias::Left,
3500 );
3501 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
3502 multi_buffer
3503 .range_to_buffer_ranges(multi_buffer_visible_range, cx)
3504 .into_iter()
3505 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3506 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
3507 let buffer = buffer_handle.read(cx);
3508 let buffer_file = project::File::from_dyn(buffer.file())?;
3509 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3510 let worktree_entry = buffer_worktree
3511 .read(cx)
3512 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3513 if worktree_entry.is_ignored {
3514 return None;
3515 }
3516
3517 let language = buffer.language()?;
3518 if let Some(restrict_to_languages) = restrict_to_languages {
3519 if !restrict_to_languages.contains(language) {
3520 return None;
3521 }
3522 }
3523 Some((
3524 excerpt_id,
3525 (
3526 buffer_handle,
3527 buffer.version().clone(),
3528 excerpt_visible_range,
3529 ),
3530 ))
3531 })
3532 .collect()
3533 }
3534
3535 pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
3536 TextLayoutDetails {
3537 text_system: cx.text_system().clone(),
3538 editor_style: self.style.clone().unwrap(),
3539 rem_size: cx.rem_size(),
3540 scroll_anchor: self.scroll_manager.anchor(),
3541 visible_rows: self.visible_line_count(),
3542 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
3543 }
3544 }
3545
3546 fn splice_inlays(
3547 &self,
3548 to_remove: Vec<InlayId>,
3549 to_insert: Vec<Inlay>,
3550 cx: &mut ViewContext<Self>,
3551 ) {
3552 self.display_map.update(cx, |display_map, cx| {
3553 display_map.splice_inlays(to_remove, to_insert, cx);
3554 });
3555 cx.notify();
3556 }
3557
3558 fn trigger_on_type_formatting(
3559 &self,
3560 input: String,
3561 cx: &mut ViewContext<Self>,
3562 ) -> Option<Task<Result<()>>> {
3563 if input.len() != 1 {
3564 return None;
3565 }
3566
3567 let project = self.project.as_ref()?;
3568 let position = self.selections.newest_anchor().head();
3569 let (buffer, buffer_position) = self
3570 .buffer
3571 .read(cx)
3572 .text_anchor_for_position(position, cx)?;
3573
3574 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
3575 // hence we do LSP request & edit on host side only — add formats to host's history.
3576 let push_to_lsp_host_history = true;
3577 // If this is not the host, append its history with new edits.
3578 let push_to_client_history = project.read(cx).is_remote();
3579
3580 let on_type_formatting = project.update(cx, |project, cx| {
3581 project.on_type_format(
3582 buffer.clone(),
3583 buffer_position,
3584 input,
3585 push_to_lsp_host_history,
3586 cx,
3587 )
3588 });
3589 Some(cx.spawn(|editor, mut cx| async move {
3590 if let Some(transaction) = on_type_formatting.await? {
3591 if push_to_client_history {
3592 buffer
3593 .update(&mut cx, |buffer, _| {
3594 buffer.push_transaction(transaction, Instant::now());
3595 })
3596 .ok();
3597 }
3598 editor.update(&mut cx, |editor, cx| {
3599 editor.refresh_document_highlights(cx);
3600 })?;
3601 }
3602 Ok(())
3603 }))
3604 }
3605
3606 fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
3607 if self.pending_rename.is_some() {
3608 return;
3609 }
3610
3611 let Some(provider) = self.completion_provider.as_ref() else {
3612 return;
3613 };
3614
3615 let position = self.selections.newest_anchor().head();
3616 let (buffer, buffer_position) =
3617 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
3618 output
3619 } else {
3620 return;
3621 };
3622
3623 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
3624 let completions = provider.completions(&buffer, buffer_position, cx);
3625
3626 let id = post_inc(&mut self.next_completion_id);
3627 let task = cx.spawn(|this, mut cx| {
3628 async move {
3629 let completions = completions.await.log_err();
3630 let menu = if let Some(completions) = completions {
3631 let mut menu = CompletionsMenu {
3632 id,
3633 initial_position: position,
3634 match_candidates: completions
3635 .iter()
3636 .enumerate()
3637 .map(|(id, completion)| {
3638 StringMatchCandidate::new(
3639 id,
3640 completion.label.text[completion.label.filter_range.clone()]
3641 .into(),
3642 )
3643 })
3644 .collect(),
3645 buffer: buffer.clone(),
3646 completions: Arc::new(RwLock::new(completions.into())),
3647 matches: Vec::new().into(),
3648 selected_item: 0,
3649 scroll_handle: UniformListScrollHandle::new(),
3650 selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new(
3651 DebouncedDelay::new(),
3652 )),
3653 };
3654 menu.filter(query.as_deref(), cx.background_executor().clone())
3655 .await;
3656
3657 if menu.matches.is_empty() {
3658 None
3659 } else {
3660 this.update(&mut cx, |editor, cx| {
3661 let completions = menu.completions.clone();
3662 let matches = menu.matches.clone();
3663
3664 let delay_ms = EditorSettings::get_global(cx)
3665 .completion_documentation_secondary_query_debounce;
3666 let delay = Duration::from_millis(delay_ms);
3667
3668 editor
3669 .completion_documentation_pre_resolve_debounce
3670 .fire_new(delay, cx, |editor, cx| {
3671 CompletionsMenu::pre_resolve_completion_documentation(
3672 buffer,
3673 completions,
3674 matches,
3675 editor,
3676 cx,
3677 )
3678 });
3679 })
3680 .ok();
3681 Some(menu)
3682 }
3683 } else {
3684 None
3685 };
3686
3687 this.update(&mut cx, |this, cx| {
3688 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
3689
3690 let mut context_menu = this.context_menu.write();
3691 match context_menu.as_ref() {
3692 None => {}
3693
3694 Some(ContextMenu::Completions(prev_menu)) => {
3695 if prev_menu.id > id {
3696 return;
3697 }
3698 }
3699
3700 _ => return,
3701 }
3702
3703 if this.focus_handle.is_focused(cx) && menu.is_some() {
3704 let menu = menu.unwrap();
3705 *context_menu = Some(ContextMenu::Completions(menu));
3706 drop(context_menu);
3707 this.discard_inline_completion(cx);
3708 cx.notify();
3709 } else if this.completion_tasks.len() <= 1 {
3710 // If there are no more completion tasks and the last menu was
3711 // empty, we should hide it. If it was already hidden, we should
3712 // also show the copilot completion when available.
3713 drop(context_menu);
3714 if this.hide_context_menu(cx).is_none() {
3715 this.update_visible_inline_completion(cx);
3716 }
3717 }
3718 })?;
3719
3720 Ok::<_, anyhow::Error>(())
3721 }
3722 .log_err()
3723 });
3724
3725 self.completion_tasks.push((id, task));
3726 }
3727
3728 pub fn confirm_completion(
3729 &mut self,
3730 action: &ConfirmCompletion,
3731 cx: &mut ViewContext<Self>,
3732 ) -> Option<Task<Result<()>>> {
3733 use language::ToOffset as _;
3734
3735 let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
3736 menu
3737 } else {
3738 return None;
3739 };
3740
3741 let mat = completions_menu
3742 .matches
3743 .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
3744 let buffer_handle = completions_menu.buffer;
3745 let completions = completions_menu.completions.read();
3746 let completion = completions.get(mat.candidate_id)?;
3747 cx.stop_propagation();
3748
3749 let snippet;
3750 let text;
3751 if completion.is_snippet() {
3752 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
3753 text = snippet.as_ref().unwrap().text.clone();
3754 } else {
3755 snippet = None;
3756 text = completion.new_text.clone();
3757 };
3758 let selections = self.selections.all::<usize>(cx);
3759 let buffer = buffer_handle.read(cx);
3760 let old_range = completion.old_range.to_offset(buffer);
3761 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
3762
3763 let newest_selection = self.selections.newest_anchor();
3764 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
3765 return None;
3766 }
3767
3768 let lookbehind = newest_selection
3769 .start
3770 .text_anchor
3771 .to_offset(buffer)
3772 .saturating_sub(old_range.start);
3773 let lookahead = old_range
3774 .end
3775 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
3776 let mut common_prefix_len = old_text
3777 .bytes()
3778 .zip(text.bytes())
3779 .take_while(|(a, b)| a == b)
3780 .count();
3781
3782 let snapshot = self.buffer.read(cx).snapshot(cx);
3783 let mut range_to_replace: Option<Range<isize>> = None;
3784 let mut ranges = Vec::new();
3785 for selection in &selections {
3786 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
3787 let start = selection.start.saturating_sub(lookbehind);
3788 let end = selection.end + lookahead;
3789 if selection.id == newest_selection.id {
3790 range_to_replace = Some(
3791 ((start + common_prefix_len) as isize - selection.start as isize)
3792 ..(end as isize - selection.start as isize),
3793 );
3794 }
3795 ranges.push(start + common_prefix_len..end);
3796 } else {
3797 common_prefix_len = 0;
3798 ranges.clear();
3799 ranges.extend(selections.iter().map(|s| {
3800 if s.id == newest_selection.id {
3801 range_to_replace = Some(
3802 old_range.start.to_offset_utf16(&snapshot).0 as isize
3803 - selection.start as isize
3804 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
3805 - selection.start as isize,
3806 );
3807 old_range.clone()
3808 } else {
3809 s.start..s.end
3810 }
3811 }));
3812 break;
3813 }
3814 }
3815 let text = &text[common_prefix_len..];
3816
3817 cx.emit(EditorEvent::InputHandled {
3818 utf16_range_to_replace: range_to_replace,
3819 text: text.into(),
3820 });
3821
3822 self.transact(cx, |this, cx| {
3823 if let Some(mut snippet) = snippet {
3824 snippet.text = text.to_string();
3825 for tabstop in snippet.tabstops.iter_mut().flatten() {
3826 tabstop.start -= common_prefix_len as isize;
3827 tabstop.end -= common_prefix_len as isize;
3828 }
3829
3830 this.insert_snippet(&ranges, snippet, cx).log_err();
3831 } else {
3832 this.buffer.update(cx, |buffer, cx| {
3833 buffer.edit(
3834 ranges.iter().map(|range| (range.clone(), text)),
3835 this.autoindent_mode.clone(),
3836 cx,
3837 );
3838 });
3839 }
3840
3841 this.refresh_inline_completion(true, cx);
3842 });
3843
3844 let provider = self.completion_provider.as_ref()?;
3845 let apply_edits = provider.apply_additional_edits_for_completion(
3846 buffer_handle,
3847 completion.clone(),
3848 true,
3849 cx,
3850 );
3851 Some(cx.foreground_executor().spawn(async move {
3852 apply_edits.await?;
3853 Ok(())
3854 }))
3855 }
3856
3857 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
3858 let mut context_menu = self.context_menu.write();
3859 if let Some(ContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
3860 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
3861 // Toggle if we're selecting the same one
3862 *context_menu = None;
3863 cx.notify();
3864 return;
3865 } else {
3866 // Otherwise, clear it and start a new one
3867 *context_menu = None;
3868 cx.notify();
3869 }
3870 }
3871 drop(context_menu);
3872 let snapshot = self.snapshot(cx);
3873 let deployed_from_indicator = action.deployed_from_indicator;
3874 let mut task = self.code_actions_task.take();
3875 let action = action.clone();
3876 cx.spawn(|this, mut cx| async move {
3877 while let Some(prev_task) = task {
3878 prev_task.await;
3879 task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
3880 }
3881
3882 let spawned_test_task = this.update(&mut cx, |this, cx| {
3883 if this.focus_handle.is_focused(cx) {
3884 let multibuffer_point = action
3885 .deployed_from_indicator
3886 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
3887 .unwrap_or_else(|| this.selections.newest::<Point>(cx).head());
3888 let (buffer, buffer_row) = snapshot
3889 .buffer_snapshot
3890 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
3891 .and_then(|(buffer_snapshot, range)| {
3892 this.buffer
3893 .read(cx)
3894 .buffer(buffer_snapshot.remote_id())
3895 .map(|buffer| (buffer, range.start.row))
3896 })?;
3897 let (_, code_actions) = this
3898 .available_code_actions
3899 .clone()
3900 .and_then(|(location, code_actions)| {
3901 let snapshot = location.buffer.read(cx).snapshot();
3902 let point_range = location.range.to_point(&snapshot);
3903 let point_range = point_range.start.row..=point_range.end.row;
3904 if point_range.contains(&buffer_row) {
3905 Some((location, code_actions))
3906 } else {
3907 None
3908 }
3909 })
3910 .unzip();
3911 let buffer_id = buffer.read(cx).remote_id();
3912 let tasks = this
3913 .tasks
3914 .get(&(buffer_id, buffer_row))
3915 .map(|t| Arc::new(t.to_owned()));
3916 if tasks.is_none() && code_actions.is_none() {
3917 return None;
3918 }
3919
3920 this.completion_tasks.clear();
3921 this.discard_inline_completion(cx);
3922 let task_context = tasks.as_ref().zip(this.workspace.clone()).and_then(
3923 |(tasks, (workspace, _))| {
3924 let position = Point::new(buffer_row, tasks.1.column);
3925 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
3926 let location = Location {
3927 buffer: buffer.clone(),
3928 range: range_start..range_start,
3929 };
3930 workspace
3931 .update(cx, |workspace, cx| {
3932 tasks::task_context_for_location(workspace, location, cx)
3933 })
3934 .ok()
3935 .flatten()
3936 },
3937 );
3938 let tasks = tasks.zip(task_context).map(|(tasks, mut task_context)| {
3939 // Fill in the environmental variables from the tree-sitter captures
3940 let mut additional_task_variables = TaskVariables::default();
3941 for (capture_name, value) in tasks.1.extra_variables.clone() {
3942 additional_task_variables.insert(
3943 task::VariableName::Custom(capture_name.into()),
3944 value.clone(),
3945 );
3946 }
3947 task_context
3948 .task_variables
3949 .extend(additional_task_variables);
3950
3951 Arc::new(ResolvedTasks {
3952 templates: tasks
3953 .1
3954 .templates
3955 .iter()
3956 .filter_map(|(kind, template)| {
3957 template
3958 .resolve_task(&kind.to_id_base(), &task_context)
3959 .map(|task| (kind.clone(), task))
3960 })
3961 .collect(),
3962 position: snapshot
3963 .buffer_snapshot
3964 .anchor_before(Point::new(multibuffer_point.row, tasks.1.column)),
3965 })
3966 });
3967 let spawn_straight_away = tasks
3968 .as_ref()
3969 .map_or(false, |tasks| tasks.templates.len() == 1)
3970 && code_actions
3971 .as_ref()
3972 .map_or(true, |actions| actions.is_empty());
3973 *this.context_menu.write() = Some(ContextMenu::CodeActions(CodeActionsMenu {
3974 buffer,
3975 actions: CodeActionContents {
3976 tasks,
3977 actions: code_actions,
3978 },
3979 selected_item: Default::default(),
3980 scroll_handle: UniformListScrollHandle::default(),
3981 deployed_from_indicator,
3982 }));
3983 if spawn_straight_away {
3984 if let Some(task) =
3985 this.confirm_code_action(&ConfirmCodeAction { item_ix: Some(0) }, cx)
3986 {
3987 cx.notify();
3988 return Some(task);
3989 }
3990 }
3991 cx.notify();
3992 }
3993 Some(Task::ready(Ok(())))
3994 })?;
3995 if let Some(task) = spawned_test_task {
3996 task.await?;
3997 }
3998
3999 Ok::<_, anyhow::Error>(())
4000 })
4001 .detach_and_log_err(cx);
4002 }
4003
4004 pub fn confirm_code_action(
4005 &mut self,
4006 action: &ConfirmCodeAction,
4007 cx: &mut ViewContext<Self>,
4008 ) -> Option<Task<Result<()>>> {
4009 let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
4010 menu
4011 } else {
4012 return None;
4013 };
4014 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4015 let action = actions_menu.actions.get(action_ix)?;
4016 let title = action.label();
4017 let buffer = actions_menu.buffer;
4018 let workspace = self.workspace()?;
4019
4020 match action {
4021 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4022 workspace.update(cx, |workspace, cx| {
4023 workspace::tasks::schedule_resolved_task(
4024 workspace,
4025 task_source_kind,
4026 resolved_task,
4027 false,
4028 cx,
4029 );
4030
4031 Some(Task::ready(Ok(())))
4032 })
4033 }
4034 CodeActionsItem::CodeAction(action) => {
4035 let apply_code_actions = workspace
4036 .read(cx)
4037 .project()
4038 .clone()
4039 .update(cx, |project, cx| {
4040 project.apply_code_action(buffer, action, true, cx)
4041 });
4042 let workspace = workspace.downgrade();
4043 Some(cx.spawn(|editor, cx| async move {
4044 let project_transaction = apply_code_actions.await?;
4045 Self::open_project_transaction(
4046 &editor,
4047 workspace,
4048 project_transaction,
4049 title,
4050 cx,
4051 )
4052 .await
4053 }))
4054 }
4055 }
4056 }
4057
4058 async fn open_project_transaction(
4059 this: &WeakView<Editor>,
4060 workspace: WeakView<Workspace>,
4061 transaction: ProjectTransaction,
4062 title: String,
4063 mut cx: AsyncWindowContext,
4064 ) -> Result<()> {
4065 let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
4066
4067 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4068 cx.update(|cx| {
4069 entries.sort_unstable_by_key(|(buffer, _)| {
4070 buffer.read(cx).file().map(|f| f.path().clone())
4071 });
4072 })?;
4073
4074 // If the project transaction's edits are all contained within this editor, then
4075 // avoid opening a new editor to display them.
4076
4077 if let Some((buffer, transaction)) = entries.first() {
4078 if entries.len() == 1 {
4079 let excerpt = this.update(&mut cx, |editor, cx| {
4080 editor
4081 .buffer()
4082 .read(cx)
4083 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4084 })?;
4085 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4086 if excerpted_buffer == *buffer {
4087 let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
4088 let excerpt_range = excerpt_range.to_offset(buffer);
4089 buffer
4090 .edited_ranges_for_transaction::<usize>(transaction)
4091 .all(|range| {
4092 excerpt_range.start <= range.start
4093 && excerpt_range.end >= range.end
4094 })
4095 })?;
4096
4097 if all_edits_within_excerpt {
4098 return Ok(());
4099 }
4100 }
4101 }
4102 }
4103 } else {
4104 return Ok(());
4105 }
4106
4107 let mut ranges_to_highlight = Vec::new();
4108 let excerpt_buffer = cx.new_model(|cx| {
4109 let mut multibuffer =
4110 MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
4111 for (buffer_handle, transaction) in &entries {
4112 let buffer = buffer_handle.read(cx);
4113 ranges_to_highlight.extend(
4114 multibuffer.push_excerpts_with_context_lines(
4115 buffer_handle.clone(),
4116 buffer
4117 .edited_ranges_for_transaction::<usize>(transaction)
4118 .collect(),
4119 DEFAULT_MULTIBUFFER_CONTEXT,
4120 cx,
4121 ),
4122 );
4123 }
4124 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4125 multibuffer
4126 })?;
4127
4128 workspace.update(&mut cx, |workspace, cx| {
4129 let project = workspace.project().clone();
4130 let editor =
4131 cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
4132 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, cx);
4133 editor.update(cx, |editor, cx| {
4134 editor.highlight_background::<Self>(
4135 &ranges_to_highlight,
4136 |theme| theme.editor_highlighted_line_background,
4137 cx,
4138 );
4139 });
4140 })?;
4141
4142 Ok(())
4143 }
4144
4145 fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4146 let project = self.project.clone()?;
4147 let buffer = self.buffer.read(cx);
4148 let newest_selection = self.selections.newest_anchor().clone();
4149 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4150 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4151 if start_buffer != end_buffer {
4152 return None;
4153 }
4154
4155 self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
4156 cx.background_executor()
4157 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4158 .await;
4159
4160 let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
4161 project.code_actions(&start_buffer, start..end, cx)
4162 }) {
4163 code_actions.await
4164 } else {
4165 Vec::new()
4166 };
4167
4168 this.update(&mut cx, |this, cx| {
4169 this.available_code_actions = if actions.is_empty() {
4170 None
4171 } else {
4172 Some((
4173 Location {
4174 buffer: start_buffer,
4175 range: start..end,
4176 },
4177 actions.into(),
4178 ))
4179 };
4180 cx.notify();
4181 })
4182 .log_err();
4183 }));
4184 None
4185 }
4186
4187 fn start_inline_blame_timer(&mut self, cx: &mut ViewContext<Self>) {
4188 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4189 self.show_git_blame_inline = false;
4190
4191 self.show_git_blame_inline_delay_task = Some(cx.spawn(|this, mut cx| async move {
4192 cx.background_executor().timer(delay).await;
4193
4194 this.update(&mut cx, |this, cx| {
4195 this.show_git_blame_inline = true;
4196 cx.notify();
4197 })
4198 .log_err();
4199 }));
4200 }
4201 }
4202
4203 fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4204 if self.pending_rename.is_some() {
4205 return None;
4206 }
4207
4208 let project = self.project.clone()?;
4209 let buffer = self.buffer.read(cx);
4210 let newest_selection = self.selections.newest_anchor().clone();
4211 let cursor_position = newest_selection.head();
4212 let (cursor_buffer, cursor_buffer_position) =
4213 buffer.text_anchor_for_position(cursor_position, cx)?;
4214 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4215 if cursor_buffer != tail_buffer {
4216 return None;
4217 }
4218
4219 self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
4220 cx.background_executor()
4221 .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
4222 .await;
4223
4224 let highlights = if let Some(highlights) = project
4225 .update(&mut cx, |project, cx| {
4226 project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4227 })
4228 .log_err()
4229 {
4230 highlights.await.log_err()
4231 } else {
4232 None
4233 };
4234
4235 if let Some(highlights) = highlights {
4236 this.update(&mut cx, |this, cx| {
4237 if this.pending_rename.is_some() {
4238 return;
4239 }
4240
4241 let buffer_id = cursor_position.buffer_id;
4242 let buffer = this.buffer.read(cx);
4243 if !buffer
4244 .text_anchor_for_position(cursor_position, cx)
4245 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
4246 {
4247 return;
4248 }
4249
4250 let cursor_buffer_snapshot = cursor_buffer.read(cx);
4251 let mut write_ranges = Vec::new();
4252 let mut read_ranges = Vec::new();
4253 for highlight in highlights {
4254 for (excerpt_id, excerpt_range) in
4255 buffer.excerpts_for_buffer(&cursor_buffer, cx)
4256 {
4257 let start = highlight
4258 .range
4259 .start
4260 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
4261 let end = highlight
4262 .range
4263 .end
4264 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
4265 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
4266 continue;
4267 }
4268
4269 let range = Anchor {
4270 buffer_id,
4271 excerpt_id: excerpt_id,
4272 text_anchor: start,
4273 }..Anchor {
4274 buffer_id,
4275 excerpt_id,
4276 text_anchor: end,
4277 };
4278 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
4279 write_ranges.push(range);
4280 } else {
4281 read_ranges.push(range);
4282 }
4283 }
4284 }
4285
4286 this.highlight_background::<DocumentHighlightRead>(
4287 &read_ranges,
4288 |theme| theme.editor_document_highlight_read_background,
4289 cx,
4290 );
4291 this.highlight_background::<DocumentHighlightWrite>(
4292 &write_ranges,
4293 |theme| theme.editor_document_highlight_write_background,
4294 cx,
4295 );
4296 cx.notify();
4297 })
4298 .log_err();
4299 }
4300 }));
4301 None
4302 }
4303
4304 fn refresh_inline_completion(
4305 &mut self,
4306 debounce: bool,
4307 cx: &mut ViewContext<Self>,
4308 ) -> Option<()> {
4309 let provider = self.inline_completion_provider()?;
4310 let cursor = self.selections.newest_anchor().head();
4311 let (buffer, cursor_buffer_position) =
4312 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4313 if !self.show_inline_completions
4314 || !provider.is_enabled(&buffer, cursor_buffer_position, cx)
4315 {
4316 self.discard_inline_completion(cx);
4317 return None;
4318 }
4319
4320 self.update_visible_inline_completion(cx);
4321 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
4322 Some(())
4323 }
4324
4325 fn cycle_inline_completion(
4326 &mut self,
4327 direction: Direction,
4328 cx: &mut ViewContext<Self>,
4329 ) -> Option<()> {
4330 let provider = self.inline_completion_provider()?;
4331 let cursor = self.selections.newest_anchor().head();
4332 let (buffer, cursor_buffer_position) =
4333 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4334 if !self.show_inline_completions
4335 || !provider.is_enabled(&buffer, cursor_buffer_position, cx)
4336 {
4337 return None;
4338 }
4339
4340 provider.cycle(buffer, cursor_buffer_position, direction, cx);
4341 self.update_visible_inline_completion(cx);
4342
4343 Some(())
4344 }
4345
4346 pub fn show_inline_completion(&mut self, _: &ShowInlineCompletion, cx: &mut ViewContext<Self>) {
4347 if !self.has_active_inline_completion(cx) {
4348 self.refresh_inline_completion(false, cx);
4349 return;
4350 }
4351
4352 self.update_visible_inline_completion(cx);
4353 }
4354
4355 pub fn display_cursor_names(&mut self, _: &DisplayCursorNames, cx: &mut ViewContext<Self>) {
4356 self.show_cursor_names(cx);
4357 }
4358
4359 fn show_cursor_names(&mut self, cx: &mut ViewContext<Self>) {
4360 self.show_cursor_names = true;
4361 cx.notify();
4362 cx.spawn(|this, mut cx| async move {
4363 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
4364 this.update(&mut cx, |this, cx| {
4365 this.show_cursor_names = false;
4366 cx.notify()
4367 })
4368 .ok()
4369 })
4370 .detach();
4371 }
4372
4373 pub fn next_inline_completion(&mut self, _: &NextInlineCompletion, cx: &mut ViewContext<Self>) {
4374 if self.has_active_inline_completion(cx) {
4375 self.cycle_inline_completion(Direction::Next, cx);
4376 } else {
4377 let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
4378 if is_copilot_disabled {
4379 cx.propagate();
4380 }
4381 }
4382 }
4383
4384 pub fn previous_inline_completion(
4385 &mut self,
4386 _: &PreviousInlineCompletion,
4387 cx: &mut ViewContext<Self>,
4388 ) {
4389 if self.has_active_inline_completion(cx) {
4390 self.cycle_inline_completion(Direction::Prev, cx);
4391 } else {
4392 let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
4393 if is_copilot_disabled {
4394 cx.propagate();
4395 }
4396 }
4397 }
4398
4399 fn accept_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> bool {
4400 if let Some(completion) = self.take_active_inline_completion(cx) {
4401 if let Some(provider) = self.inline_completion_provider() {
4402 provider.accept(cx);
4403 }
4404
4405 cx.emit(EditorEvent::InputHandled {
4406 utf16_range_to_replace: None,
4407 text: completion.text.to_string().into(),
4408 });
4409 self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
4410 self.refresh_inline_completion(true, cx);
4411 cx.notify();
4412 true
4413 } else {
4414 false
4415 }
4416 }
4417
4418 pub fn accept_partial_inline_completion(
4419 &mut self,
4420 _: &AcceptPartialInlineCompletion,
4421 cx: &mut ViewContext<Self>,
4422 ) {
4423 if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
4424 if let Some(completion) = self.take_active_inline_completion(cx) {
4425 let mut partial_completion = completion
4426 .text
4427 .chars()
4428 .by_ref()
4429 .take_while(|c| c.is_alphabetic())
4430 .collect::<String>();
4431 if partial_completion.is_empty() {
4432 partial_completion = completion
4433 .text
4434 .chars()
4435 .by_ref()
4436 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
4437 .collect::<String>();
4438 }
4439
4440 cx.emit(EditorEvent::InputHandled {
4441 utf16_range_to_replace: None,
4442 text: partial_completion.clone().into(),
4443 });
4444 self.insert_with_autoindent_mode(&partial_completion, None, cx);
4445 self.refresh_inline_completion(true, cx);
4446 cx.notify();
4447 }
4448 }
4449 }
4450
4451 fn discard_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> bool {
4452 if let Some(provider) = self.inline_completion_provider() {
4453 provider.discard(cx);
4454 }
4455
4456 self.take_active_inline_completion(cx).is_some()
4457 }
4458
4459 pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
4460 if let Some(completion) = self.active_inline_completion.as_ref() {
4461 let buffer = self.buffer.read(cx).read(cx);
4462 completion.position.is_valid(&buffer)
4463 } else {
4464 false
4465 }
4466 }
4467
4468 fn take_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
4469 let completion = self.active_inline_completion.take()?;
4470 self.display_map.update(cx, |map, cx| {
4471 map.splice_inlays(vec![completion.id], Default::default(), cx);
4472 });
4473 let buffer = self.buffer.read(cx).read(cx);
4474
4475 if completion.position.is_valid(&buffer) {
4476 Some(completion)
4477 } else {
4478 None
4479 }
4480 }
4481
4482 fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
4483 let selection = self.selections.newest_anchor();
4484 let cursor = selection.head();
4485
4486 if self.context_menu.read().is_none()
4487 && self.completion_tasks.is_empty()
4488 && selection.start == selection.end
4489 {
4490 if let Some(provider) = self.inline_completion_provider() {
4491 if let Some((buffer, cursor_buffer_position)) =
4492 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
4493 {
4494 if let Some(text) =
4495 provider.active_completion_text(&buffer, cursor_buffer_position, cx)
4496 {
4497 let text = Rope::from(text);
4498 let mut to_remove = Vec::new();
4499 if let Some(completion) = self.active_inline_completion.take() {
4500 to_remove.push(completion.id);
4501 }
4502
4503 let completion_inlay =
4504 Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
4505 self.active_inline_completion = Some(completion_inlay.clone());
4506 self.display_map.update(cx, move |map, cx| {
4507 map.splice_inlays(to_remove, vec![completion_inlay], cx)
4508 });
4509 cx.notify();
4510 return;
4511 }
4512 }
4513 }
4514 }
4515
4516 self.discard_inline_completion(cx);
4517 }
4518
4519 fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
4520 Some(self.inline_completion_provider.as_ref()?.provider.clone())
4521 }
4522
4523 fn render_code_actions_indicator(
4524 &self,
4525 _style: &EditorStyle,
4526 row: DisplayRow,
4527 is_active: bool,
4528 cx: &mut ViewContext<Self>,
4529 ) -> Option<IconButton> {
4530 if self.available_code_actions.is_some() {
4531 Some(
4532 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
4533 .icon_size(IconSize::XSmall)
4534 .size(ui::ButtonSize::None)
4535 .icon_color(Color::Muted)
4536 .selected(is_active)
4537 .on_click(cx.listener(move |editor, _e, cx| {
4538 editor.focus(cx);
4539 editor.toggle_code_actions(
4540 &ToggleCodeActions {
4541 deployed_from_indicator: Some(row),
4542 },
4543 cx,
4544 );
4545 })),
4546 )
4547 } else {
4548 None
4549 }
4550 }
4551
4552 fn clear_tasks(&mut self) {
4553 self.tasks.clear()
4554 }
4555
4556 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: (usize, RunnableTasks)) {
4557 if let Some(_) = self.tasks.insert(key, value) {
4558 // This case should hopefully be rare, but just in case...
4559 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
4560 }
4561 }
4562
4563 fn render_run_indicator(
4564 &self,
4565 _style: &EditorStyle,
4566 is_active: bool,
4567 row: DisplayRow,
4568 cx: &mut ViewContext<Self>,
4569 ) -> IconButton {
4570 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
4571 .icon_size(IconSize::XSmall)
4572 .size(ui::ButtonSize::None)
4573 .icon_color(Color::Muted)
4574 .selected(is_active)
4575 .on_click(cx.listener(move |editor, _e, cx| {
4576 editor.focus(cx);
4577 editor.toggle_code_actions(
4578 &ToggleCodeActions {
4579 deployed_from_indicator: Some(row),
4580 },
4581 cx,
4582 );
4583 }))
4584 }
4585
4586 pub fn render_fold_indicators(
4587 &mut self,
4588 fold_data: Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
4589 _style: &EditorStyle,
4590 gutter_hovered: bool,
4591 _line_height: Pixels,
4592 _gutter_margin: Pixels,
4593 cx: &mut ViewContext<Self>,
4594 ) -> Vec<Option<AnyElement>> {
4595 fold_data
4596 .iter()
4597 .enumerate()
4598 .map(|(ix, fold_data)| {
4599 fold_data
4600 .map(|(fold_status, buffer_row, active)| {
4601 (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
4602 IconButton::new(ix, ui::IconName::ChevronDown)
4603 .on_click(cx.listener(move |this, _e, cx| match fold_status {
4604 FoldStatus::Folded => {
4605 this.unfold_at(&UnfoldAt { buffer_row }, cx);
4606 }
4607 FoldStatus::Foldable => {
4608 this.fold_at(&FoldAt { buffer_row }, cx);
4609 }
4610 }))
4611 .icon_color(ui::Color::Muted)
4612 .icon_size(ui::IconSize::Small)
4613 .selected(fold_status == FoldStatus::Folded)
4614 .selected_icon(ui::IconName::ChevronRight)
4615 .size(ui::ButtonSize::None)
4616 .into_any_element()
4617 })
4618 })
4619 .flatten()
4620 })
4621 .collect()
4622 }
4623
4624 pub fn context_menu_visible(&self) -> bool {
4625 self.context_menu
4626 .read()
4627 .as_ref()
4628 .map_or(false, |menu| menu.visible())
4629 }
4630
4631 fn render_context_menu(
4632 &self,
4633 cursor_position: DisplayPoint,
4634 style: &EditorStyle,
4635 max_height: Pixels,
4636 cx: &mut ViewContext<Editor>,
4637 ) -> Option<(ContextMenuOrigin, AnyElement)> {
4638 self.context_menu.read().as_ref().map(|menu| {
4639 menu.render(
4640 cursor_position,
4641 style,
4642 max_height,
4643 self.workspace.as_ref().map(|(w, _)| w.clone()),
4644 cx,
4645 )
4646 })
4647 }
4648
4649 fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
4650 cx.notify();
4651 self.completion_tasks.clear();
4652 let context_menu = self.context_menu.write().take();
4653 if context_menu.is_some() {
4654 self.update_visible_inline_completion(cx);
4655 }
4656 context_menu
4657 }
4658
4659 pub fn insert_snippet(
4660 &mut self,
4661 insertion_ranges: &[Range<usize>],
4662 snippet: Snippet,
4663 cx: &mut ViewContext<Self>,
4664 ) -> Result<()> {
4665 struct Tabstop<T> {
4666 is_end_tabstop: bool,
4667 ranges: Vec<Range<T>>,
4668 }
4669
4670 let tabstops = self.buffer.update(cx, |buffer, cx| {
4671 let snippet_text: Arc<str> = snippet.text.clone().into();
4672 buffer.edit(
4673 insertion_ranges
4674 .iter()
4675 .cloned()
4676 .map(|range| (range, snippet_text.clone())),
4677 Some(AutoindentMode::EachLine),
4678 cx,
4679 );
4680
4681 let snapshot = &*buffer.read(cx);
4682 let snippet = &snippet;
4683 snippet
4684 .tabstops
4685 .iter()
4686 .map(|tabstop| {
4687 let is_end_tabstop = tabstop.first().map_or(false, |tabstop| {
4688 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
4689 });
4690 let mut tabstop_ranges = tabstop
4691 .iter()
4692 .flat_map(|tabstop_range| {
4693 let mut delta = 0_isize;
4694 insertion_ranges.iter().map(move |insertion_range| {
4695 let insertion_start = insertion_range.start as isize + delta;
4696 delta +=
4697 snippet.text.len() as isize - insertion_range.len() as isize;
4698
4699 let start = ((insertion_start + tabstop_range.start) as usize)
4700 .min(snapshot.len());
4701 let end = ((insertion_start + tabstop_range.end) as usize)
4702 .min(snapshot.len());
4703 snapshot.anchor_before(start)..snapshot.anchor_after(end)
4704 })
4705 })
4706 .collect::<Vec<_>>();
4707 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
4708
4709 Tabstop {
4710 is_end_tabstop,
4711 ranges: tabstop_ranges,
4712 }
4713 })
4714 .collect::<Vec<_>>()
4715 });
4716
4717 if let Some(tabstop) = tabstops.first() {
4718 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
4719 s.select_ranges(tabstop.ranges.iter().cloned());
4720 });
4721
4722 // If we're already at the last tabstop and it's at the end of the snippet,
4723 // we're done, we don't need to keep the state around.
4724 if !tabstop.is_end_tabstop {
4725 let ranges = tabstops
4726 .into_iter()
4727 .map(|tabstop| tabstop.ranges)
4728 .collect::<Vec<_>>();
4729 self.snippet_stack.push(SnippetState {
4730 active_index: 0,
4731 ranges,
4732 });
4733 }
4734
4735 // Check whether the just-entered snippet ends with an auto-closable bracket.
4736 if self.autoclose_regions.is_empty() {
4737 let snapshot = self.buffer.read(cx).snapshot(cx);
4738 for selection in &mut self.selections.all::<Point>(cx) {
4739 let selection_head = selection.head();
4740 let Some(scope) = snapshot.language_scope_at(selection_head) else {
4741 continue;
4742 };
4743
4744 let mut bracket_pair = None;
4745 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
4746 let prev_chars = snapshot
4747 .reversed_chars_at(selection_head)
4748 .collect::<String>();
4749 for (pair, enabled) in scope.brackets() {
4750 if enabled
4751 && pair.close
4752 && prev_chars.starts_with(pair.start.as_str())
4753 && next_chars.starts_with(pair.end.as_str())
4754 {
4755 bracket_pair = Some(pair.clone());
4756 break;
4757 }
4758 }
4759 if let Some(pair) = bracket_pair {
4760 let start = snapshot.anchor_after(selection_head);
4761 let end = snapshot.anchor_after(selection_head);
4762 self.autoclose_regions.push(AutocloseRegion {
4763 selection_id: selection.id,
4764 range: start..end,
4765 pair,
4766 });
4767 }
4768 }
4769 }
4770 }
4771 Ok(())
4772 }
4773
4774 pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
4775 self.move_to_snippet_tabstop(Bias::Right, cx)
4776 }
4777
4778 pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
4779 self.move_to_snippet_tabstop(Bias::Left, cx)
4780 }
4781
4782 pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
4783 if let Some(mut snippet) = self.snippet_stack.pop() {
4784 match bias {
4785 Bias::Left => {
4786 if snippet.active_index > 0 {
4787 snippet.active_index -= 1;
4788 } else {
4789 self.snippet_stack.push(snippet);
4790 return false;
4791 }
4792 }
4793 Bias::Right => {
4794 if snippet.active_index + 1 < snippet.ranges.len() {
4795 snippet.active_index += 1;
4796 } else {
4797 self.snippet_stack.push(snippet);
4798 return false;
4799 }
4800 }
4801 }
4802 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
4803 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
4804 s.select_anchor_ranges(current_ranges.iter().cloned())
4805 });
4806 // If snippet state is not at the last tabstop, push it back on the stack
4807 if snippet.active_index + 1 < snippet.ranges.len() {
4808 self.snippet_stack.push(snippet);
4809 }
4810 return true;
4811 }
4812 }
4813
4814 false
4815 }
4816
4817 pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
4818 self.transact(cx, |this, cx| {
4819 this.select_all(&SelectAll, cx);
4820 this.insert("", cx);
4821 });
4822 }
4823
4824 pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
4825 self.transact(cx, |this, cx| {
4826 this.select_autoclose_pair(cx);
4827 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
4828 if !this.selections.line_mode {
4829 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4830 for selection in &mut selections {
4831 if selection.is_empty() {
4832 let old_head = selection.head();
4833 let mut new_head =
4834 movement::left(&display_map, old_head.to_display_point(&display_map))
4835 .to_point(&display_map);
4836 if let Some((buffer, line_buffer_range)) = display_map
4837 .buffer_snapshot
4838 .buffer_line_for_row(MultiBufferRow(old_head.row))
4839 {
4840 let indent_size =
4841 buffer.indent_size_for_line(line_buffer_range.start.row);
4842 let indent_len = match indent_size.kind {
4843 IndentKind::Space => {
4844 buffer.settings_at(line_buffer_range.start, cx).tab_size
4845 }
4846 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
4847 };
4848 if old_head.column <= indent_size.len && old_head.column > 0 {
4849 let indent_len = indent_len.get();
4850 new_head = cmp::min(
4851 new_head,
4852 MultiBufferPoint::new(
4853 old_head.row,
4854 ((old_head.column - 1) / indent_len) * indent_len,
4855 ),
4856 );
4857 }
4858 }
4859
4860 selection.set_head(new_head, SelectionGoal::None);
4861 }
4862 }
4863 }
4864
4865 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
4866 this.insert("", cx);
4867 this.refresh_inline_completion(true, cx);
4868 });
4869 }
4870
4871 pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
4872 self.transact(cx, |this, cx| {
4873 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
4874 let line_mode = s.line_mode;
4875 s.move_with(|map, selection| {
4876 if selection.is_empty() && !line_mode {
4877 let cursor = movement::right(map, selection.head());
4878 selection.end = cursor;
4879 selection.reversed = true;
4880 selection.goal = SelectionGoal::None;
4881 }
4882 })
4883 });
4884 this.insert("", cx);
4885 this.refresh_inline_completion(true, cx);
4886 });
4887 }
4888
4889 pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
4890 if self.move_to_prev_snippet_tabstop(cx) {
4891 return;
4892 }
4893
4894 self.outdent(&Outdent, cx);
4895 }
4896
4897 pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
4898 if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) {
4899 return;
4900 }
4901
4902 let mut selections = self.selections.all_adjusted(cx);
4903 let buffer = self.buffer.read(cx);
4904 let snapshot = buffer.snapshot(cx);
4905 let rows_iter = selections.iter().map(|s| s.head().row);
4906 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
4907
4908 let mut edits = Vec::new();
4909 let mut prev_edited_row = 0;
4910 let mut row_delta = 0;
4911 for selection in &mut selections {
4912 if selection.start.row != prev_edited_row {
4913 row_delta = 0;
4914 }
4915 prev_edited_row = selection.end.row;
4916
4917 // If the selection is non-empty, then increase the indentation of the selected lines.
4918 if !selection.is_empty() {
4919 row_delta =
4920 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
4921 continue;
4922 }
4923
4924 // If the selection is empty and the cursor is in the leading whitespace before the
4925 // suggested indentation, then auto-indent the line.
4926 let cursor = selection.head();
4927 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
4928 if let Some(suggested_indent) =
4929 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
4930 {
4931 if cursor.column < suggested_indent.len
4932 && cursor.column <= current_indent.len
4933 && current_indent.len <= suggested_indent.len
4934 {
4935 selection.start = Point::new(cursor.row, suggested_indent.len);
4936 selection.end = selection.start;
4937 if row_delta == 0 {
4938 edits.extend(Buffer::edit_for_indent_size_adjustment(
4939 cursor.row,
4940 current_indent,
4941 suggested_indent,
4942 ));
4943 row_delta = suggested_indent.len - current_indent.len;
4944 }
4945 continue;
4946 }
4947 }
4948
4949 // Accept copilot completion if there is only one selection and the cursor is not
4950 // in the leading whitespace.
4951 if self.selections.count() == 1
4952 && cursor.column >= current_indent.len
4953 && self.has_active_inline_completion(cx)
4954 {
4955 self.accept_inline_completion(cx);
4956 return;
4957 }
4958
4959 // Otherwise, insert a hard or soft tab.
4960 let settings = buffer.settings_at(cursor, cx);
4961 let tab_size = if settings.hard_tabs {
4962 IndentSize::tab()
4963 } else {
4964 let tab_size = settings.tab_size.get();
4965 let char_column = snapshot
4966 .text_for_range(Point::new(cursor.row, 0)..cursor)
4967 .flat_map(str::chars)
4968 .count()
4969 + row_delta as usize;
4970 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
4971 IndentSize::spaces(chars_to_next_tab_stop)
4972 };
4973 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
4974 selection.end = selection.start;
4975 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
4976 row_delta += tab_size.len;
4977 }
4978
4979 self.transact(cx, |this, cx| {
4980 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
4981 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
4982 this.refresh_inline_completion(true, cx);
4983 });
4984 }
4985
4986 pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
4987 if self.read_only(cx) {
4988 return;
4989 }
4990 let mut selections = self.selections.all::<Point>(cx);
4991 let mut prev_edited_row = 0;
4992 let mut row_delta = 0;
4993 let mut edits = Vec::new();
4994 let buffer = self.buffer.read(cx);
4995 let snapshot = buffer.snapshot(cx);
4996 for selection in &mut selections {
4997 if selection.start.row != prev_edited_row {
4998 row_delta = 0;
4999 }
5000 prev_edited_row = selection.end.row;
5001
5002 row_delta =
5003 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5004 }
5005
5006 self.transact(cx, |this, cx| {
5007 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5008 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5009 });
5010 }
5011
5012 fn indent_selection(
5013 buffer: &MultiBuffer,
5014 snapshot: &MultiBufferSnapshot,
5015 selection: &mut Selection<Point>,
5016 edits: &mut Vec<(Range<Point>, String)>,
5017 delta_for_start_row: u32,
5018 cx: &AppContext,
5019 ) -> u32 {
5020 let settings = buffer.settings_at(selection.start, cx);
5021 let tab_size = settings.tab_size.get();
5022 let indent_kind = if settings.hard_tabs {
5023 IndentKind::Tab
5024 } else {
5025 IndentKind::Space
5026 };
5027 let mut start_row = selection.start.row;
5028 let mut end_row = selection.end.row + 1;
5029
5030 // If a selection ends at the beginning of a line, don't indent
5031 // that last line.
5032 if selection.end.column == 0 && selection.end.row > selection.start.row {
5033 end_row -= 1;
5034 }
5035
5036 // Avoid re-indenting a row that has already been indented by a
5037 // previous selection, but still update this selection's column
5038 // to reflect that indentation.
5039 if delta_for_start_row > 0 {
5040 start_row += 1;
5041 selection.start.column += delta_for_start_row;
5042 if selection.end.row == selection.start.row {
5043 selection.end.column += delta_for_start_row;
5044 }
5045 }
5046
5047 let mut delta_for_end_row = 0;
5048 let has_multiple_rows = start_row + 1 != end_row;
5049 for row in start_row..end_row {
5050 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
5051 let indent_delta = match (current_indent.kind, indent_kind) {
5052 (IndentKind::Space, IndentKind::Space) => {
5053 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
5054 IndentSize::spaces(columns_to_next_tab_stop)
5055 }
5056 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
5057 (_, IndentKind::Tab) => IndentSize::tab(),
5058 };
5059
5060 let start = if has_multiple_rows || current_indent.len < selection.start.column {
5061 0
5062 } else {
5063 selection.start.column
5064 };
5065 let row_start = Point::new(row, start);
5066 edits.push((
5067 row_start..row_start,
5068 indent_delta.chars().collect::<String>(),
5069 ));
5070
5071 // Update this selection's endpoints to reflect the indentation.
5072 if row == selection.start.row {
5073 selection.start.column += indent_delta.len;
5074 }
5075 if row == selection.end.row {
5076 selection.end.column += indent_delta.len;
5077 delta_for_end_row = indent_delta.len;
5078 }
5079 }
5080
5081 if selection.start.row == selection.end.row {
5082 delta_for_start_row + delta_for_end_row
5083 } else {
5084 delta_for_end_row
5085 }
5086 }
5087
5088 pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
5089 if self.read_only(cx) {
5090 return;
5091 }
5092 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5093 let selections = self.selections.all::<Point>(cx);
5094 let mut deletion_ranges = Vec::new();
5095 let mut last_outdent = None;
5096 {
5097 let buffer = self.buffer.read(cx);
5098 let snapshot = buffer.snapshot(cx);
5099 for selection in &selections {
5100 let settings = buffer.settings_at(selection.start, cx);
5101 let tab_size = settings.tab_size.get();
5102 let mut rows = selection.spanned_rows(false, &display_map);
5103
5104 // Avoid re-outdenting a row that has already been outdented by a
5105 // previous selection.
5106 if let Some(last_row) = last_outdent {
5107 if last_row == rows.start {
5108 rows.start = rows.start.next_row();
5109 }
5110 }
5111 let has_multiple_rows = rows.len() > 1;
5112 for row in rows.iter_rows() {
5113 let indent_size = snapshot.indent_size_for_line(row);
5114 if indent_size.len > 0 {
5115 let deletion_len = match indent_size.kind {
5116 IndentKind::Space => {
5117 let columns_to_prev_tab_stop = indent_size.len % tab_size;
5118 if columns_to_prev_tab_stop == 0 {
5119 tab_size
5120 } else {
5121 columns_to_prev_tab_stop
5122 }
5123 }
5124 IndentKind::Tab => 1,
5125 };
5126 let start = if has_multiple_rows
5127 || deletion_len > selection.start.column
5128 || indent_size.len < selection.start.column
5129 {
5130 0
5131 } else {
5132 selection.start.column - deletion_len
5133 };
5134 deletion_ranges.push(
5135 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
5136 );
5137 last_outdent = Some(row);
5138 }
5139 }
5140 }
5141 }
5142
5143 self.transact(cx, |this, cx| {
5144 this.buffer.update(cx, |buffer, cx| {
5145 let empty_str: Arc<str> = "".into();
5146 buffer.edit(
5147 deletion_ranges
5148 .into_iter()
5149 .map(|range| (range, empty_str.clone())),
5150 None,
5151 cx,
5152 );
5153 });
5154 let selections = this.selections.all::<usize>(cx);
5155 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5156 });
5157 }
5158
5159 pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
5160 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5161 let selections = self.selections.all::<Point>(cx);
5162
5163 let mut new_cursors = Vec::new();
5164 let mut edit_ranges = Vec::new();
5165 let mut selections = selections.iter().peekable();
5166 while let Some(selection) = selections.next() {
5167 let mut rows = selection.spanned_rows(false, &display_map);
5168 let goal_display_column = selection.head().to_display_point(&display_map).column();
5169
5170 // Accumulate contiguous regions of rows that we want to delete.
5171 while let Some(next_selection) = selections.peek() {
5172 let next_rows = next_selection.spanned_rows(false, &display_map);
5173 if next_rows.start <= rows.end {
5174 rows.end = next_rows.end;
5175 selections.next().unwrap();
5176 } else {
5177 break;
5178 }
5179 }
5180
5181 let buffer = &display_map.buffer_snapshot;
5182 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
5183 let edit_end;
5184 let cursor_buffer_row;
5185 if buffer.max_point().row >= rows.end.0 {
5186 // If there's a line after the range, delete the \n from the end of the row range
5187 // and position the cursor on the next line.
5188 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
5189 cursor_buffer_row = rows.end;
5190 } else {
5191 // If there isn't a line after the range, delete the \n from the line before the
5192 // start of the row range and position the cursor there.
5193 edit_start = edit_start.saturating_sub(1);
5194 edit_end = buffer.len();
5195 cursor_buffer_row = rows.start.previous_row();
5196 }
5197
5198 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
5199 *cursor.column_mut() =
5200 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
5201
5202 new_cursors.push((
5203 selection.id,
5204 buffer.anchor_after(cursor.to_point(&display_map)),
5205 ));
5206 edit_ranges.push(edit_start..edit_end);
5207 }
5208
5209 self.transact(cx, |this, cx| {
5210 let buffer = this.buffer.update(cx, |buffer, cx| {
5211 let empty_str: Arc<str> = "".into();
5212 buffer.edit(
5213 edit_ranges
5214 .into_iter()
5215 .map(|range| (range, empty_str.clone())),
5216 None,
5217 cx,
5218 );
5219 buffer.snapshot(cx)
5220 });
5221 let new_selections = new_cursors
5222 .into_iter()
5223 .map(|(id, cursor)| {
5224 let cursor = cursor.to_point(&buffer);
5225 Selection {
5226 id,
5227 start: cursor,
5228 end: cursor,
5229 reversed: false,
5230 goal: SelectionGoal::None,
5231 }
5232 })
5233 .collect();
5234
5235 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5236 s.select(new_selections);
5237 });
5238 });
5239 }
5240
5241 pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
5242 if self.read_only(cx) {
5243 return;
5244 }
5245 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
5246 for selection in self.selections.all::<Point>(cx) {
5247 let start = MultiBufferRow(selection.start.row);
5248 let end = if selection.start.row == selection.end.row {
5249 MultiBufferRow(selection.start.row + 1)
5250 } else {
5251 MultiBufferRow(selection.end.row)
5252 };
5253
5254 if let Some(last_row_range) = row_ranges.last_mut() {
5255 if start <= last_row_range.end {
5256 last_row_range.end = end;
5257 continue;
5258 }
5259 }
5260 row_ranges.push(start..end);
5261 }
5262
5263 let snapshot = self.buffer.read(cx).snapshot(cx);
5264 let mut cursor_positions = Vec::new();
5265 for row_range in &row_ranges {
5266 let anchor = snapshot.anchor_before(Point::new(
5267 row_range.end.previous_row().0,
5268 snapshot.line_len(row_range.end.previous_row()),
5269 ));
5270 cursor_positions.push(anchor..anchor);
5271 }
5272
5273 self.transact(cx, |this, cx| {
5274 for row_range in row_ranges.into_iter().rev() {
5275 for row in row_range.iter_rows().rev() {
5276 let end_of_line = Point::new(row.0, snapshot.line_len(row));
5277 let next_line_row = row.next_row();
5278 let indent = snapshot.indent_size_for_line(next_line_row);
5279 let start_of_next_line = Point::new(next_line_row.0, indent.len);
5280
5281 let replace = if snapshot.line_len(next_line_row) > indent.len {
5282 " "
5283 } else {
5284 ""
5285 };
5286
5287 this.buffer.update(cx, |buffer, cx| {
5288 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
5289 });
5290 }
5291 }
5292
5293 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5294 s.select_anchor_ranges(cursor_positions)
5295 });
5296 });
5297 }
5298
5299 pub fn sort_lines_case_sensitive(
5300 &mut self,
5301 _: &SortLinesCaseSensitive,
5302 cx: &mut ViewContext<Self>,
5303 ) {
5304 self.manipulate_lines(cx, |lines| lines.sort())
5305 }
5306
5307 pub fn sort_lines_case_insensitive(
5308 &mut self,
5309 _: &SortLinesCaseInsensitive,
5310 cx: &mut ViewContext<Self>,
5311 ) {
5312 self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
5313 }
5314
5315 pub fn unique_lines_case_insensitive(
5316 &mut self,
5317 _: &UniqueLinesCaseInsensitive,
5318 cx: &mut ViewContext<Self>,
5319 ) {
5320 self.manipulate_lines(cx, |lines| {
5321 let mut seen = HashSet::default();
5322 lines.retain(|line| seen.insert(line.to_lowercase()));
5323 })
5324 }
5325
5326 pub fn unique_lines_case_sensitive(
5327 &mut self,
5328 _: &UniqueLinesCaseSensitive,
5329 cx: &mut ViewContext<Self>,
5330 ) {
5331 self.manipulate_lines(cx, |lines| {
5332 let mut seen = HashSet::default();
5333 lines.retain(|line| seen.insert(*line));
5334 })
5335 }
5336
5337 pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
5338 let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
5339 if !revert_changes.is_empty() {
5340 self.transact(cx, |editor, cx| {
5341 editor.buffer().update(cx, |multi_buffer, cx| {
5342 for (buffer_id, changes) in revert_changes {
5343 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
5344 buffer.update(cx, |buffer, cx| {
5345 buffer.edit(
5346 changes.into_iter().map(|(range, text)| {
5347 (range, text.to_string().map(Arc::<str>::from))
5348 }),
5349 None,
5350 cx,
5351 );
5352 });
5353 }
5354 }
5355 });
5356 editor.change_selections(None, cx, |selections| selections.refresh());
5357 });
5358 }
5359 }
5360
5361 pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
5362 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
5363 let project_path = buffer.read(cx).project_path(cx)?;
5364 let project = self.project.as_ref()?.read(cx);
5365 let entry = project.entry_for_path(&project_path, cx)?;
5366 let abs_path = project.absolute_path(&project_path, cx)?;
5367 let parent = if entry.is_symlink {
5368 abs_path.canonicalize().ok()?
5369 } else {
5370 abs_path
5371 }
5372 .parent()?
5373 .to_path_buf();
5374 Some(parent)
5375 }) {
5376 cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone());
5377 }
5378 }
5379
5380 fn gather_revert_changes(
5381 &mut self,
5382 selections: &[Selection<Anchor>],
5383 cx: &mut ViewContext<'_, Editor>,
5384 ) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
5385 let mut revert_changes = HashMap::default();
5386 self.buffer.update(cx, |multi_buffer, cx| {
5387 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5388 for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
5389 Self::prepare_revert_change(&mut revert_changes, &multi_buffer, &hunk, cx);
5390 }
5391 });
5392 revert_changes
5393 }
5394
5395 fn prepare_revert_change(
5396 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
5397 multi_buffer: &MultiBuffer,
5398 hunk: &DiffHunk<MultiBufferRow>,
5399 cx: &mut AppContext,
5400 ) -> Option<()> {
5401 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
5402 let buffer = buffer.read(cx);
5403 let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
5404 let buffer_snapshot = buffer.snapshot();
5405 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
5406 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
5407 probe
5408 .0
5409 .start
5410 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
5411 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
5412 }) {
5413 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
5414 Some(())
5415 } else {
5416 None
5417 }
5418 }
5419
5420 pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
5421 self.manipulate_lines(cx, |lines| lines.reverse())
5422 }
5423
5424 pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
5425 self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
5426 }
5427
5428 fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
5429 where
5430 Fn: FnMut(&mut Vec<&str>),
5431 {
5432 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5433 let buffer = self.buffer.read(cx).snapshot(cx);
5434
5435 let mut edits = Vec::new();
5436
5437 let selections = self.selections.all::<Point>(cx);
5438 let mut selections = selections.iter().peekable();
5439 let mut contiguous_row_selections = Vec::new();
5440 let mut new_selections = Vec::new();
5441 let mut added_lines = 0;
5442 let mut removed_lines = 0;
5443
5444 while let Some(selection) = selections.next() {
5445 let (start_row, end_row) = consume_contiguous_rows(
5446 &mut contiguous_row_selections,
5447 selection,
5448 &display_map,
5449 &mut selections,
5450 );
5451
5452 let start_point = Point::new(start_row.0, 0);
5453 let end_point = Point::new(
5454 end_row.previous_row().0,
5455 buffer.line_len(end_row.previous_row()),
5456 );
5457 let text = buffer
5458 .text_for_range(start_point..end_point)
5459 .collect::<String>();
5460
5461 let mut lines = text.split('\n').collect_vec();
5462
5463 let lines_before = lines.len();
5464 callback(&mut lines);
5465 let lines_after = lines.len();
5466
5467 edits.push((start_point..end_point, lines.join("\n")));
5468
5469 // Selections must change based on added and removed line count
5470 let start_row =
5471 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
5472 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
5473 new_selections.push(Selection {
5474 id: selection.id,
5475 start: start_row,
5476 end: end_row,
5477 goal: SelectionGoal::None,
5478 reversed: selection.reversed,
5479 });
5480
5481 if lines_after > lines_before {
5482 added_lines += lines_after - lines_before;
5483 } else if lines_before > lines_after {
5484 removed_lines += lines_before - lines_after;
5485 }
5486 }
5487
5488 self.transact(cx, |this, cx| {
5489 let buffer = this.buffer.update(cx, |buffer, cx| {
5490 buffer.edit(edits, None, cx);
5491 buffer.snapshot(cx)
5492 });
5493
5494 // Recalculate offsets on newly edited buffer
5495 let new_selections = new_selections
5496 .iter()
5497 .map(|s| {
5498 let start_point = Point::new(s.start.0, 0);
5499 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
5500 Selection {
5501 id: s.id,
5502 start: buffer.point_to_offset(start_point),
5503 end: buffer.point_to_offset(end_point),
5504 goal: s.goal,
5505 reversed: s.reversed,
5506 }
5507 })
5508 .collect();
5509
5510 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5511 s.select(new_selections);
5512 });
5513
5514 this.request_autoscroll(Autoscroll::fit(), cx);
5515 });
5516 }
5517
5518 pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
5519 self.manipulate_text(cx, |text| text.to_uppercase())
5520 }
5521
5522 pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
5523 self.manipulate_text(cx, |text| text.to_lowercase())
5524 }
5525
5526 pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
5527 self.manipulate_text(cx, |text| {
5528 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
5529 // https://github.com/rutrum/convert-case/issues/16
5530 text.split('\n')
5531 .map(|line| line.to_case(Case::Title))
5532 .join("\n")
5533 })
5534 }
5535
5536 pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
5537 self.manipulate_text(cx, |text| text.to_case(Case::Snake))
5538 }
5539
5540 pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
5541 self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
5542 }
5543
5544 pub fn convert_to_upper_camel_case(
5545 &mut self,
5546 _: &ConvertToUpperCamelCase,
5547 cx: &mut ViewContext<Self>,
5548 ) {
5549 self.manipulate_text(cx, |text| {
5550 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
5551 // https://github.com/rutrum/convert-case/issues/16
5552 text.split('\n')
5553 .map(|line| line.to_case(Case::UpperCamel))
5554 .join("\n")
5555 })
5556 }
5557
5558 pub fn convert_to_lower_camel_case(
5559 &mut self,
5560 _: &ConvertToLowerCamelCase,
5561 cx: &mut ViewContext<Self>,
5562 ) {
5563 self.manipulate_text(cx, |text| text.to_case(Case::Camel))
5564 }
5565
5566 pub fn convert_to_opposite_case(
5567 &mut self,
5568 _: &ConvertToOppositeCase,
5569 cx: &mut ViewContext<Self>,
5570 ) {
5571 self.manipulate_text(cx, |text| {
5572 text.chars()
5573 .fold(String::with_capacity(text.len()), |mut t, c| {
5574 if c.is_uppercase() {
5575 t.extend(c.to_lowercase());
5576 } else {
5577 t.extend(c.to_uppercase());
5578 }
5579 t
5580 })
5581 })
5582 }
5583
5584 fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
5585 where
5586 Fn: FnMut(&str) -> String,
5587 {
5588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5589 let buffer = self.buffer.read(cx).snapshot(cx);
5590
5591 let mut new_selections = Vec::new();
5592 let mut edits = Vec::new();
5593 let mut selection_adjustment = 0i32;
5594
5595 for selection in self.selections.all::<usize>(cx) {
5596 let selection_is_empty = selection.is_empty();
5597
5598 let (start, end) = if selection_is_empty {
5599 let word_range = movement::surrounding_word(
5600 &display_map,
5601 selection.start.to_display_point(&display_map),
5602 );
5603 let start = word_range.start.to_offset(&display_map, Bias::Left);
5604 let end = word_range.end.to_offset(&display_map, Bias::Left);
5605 (start, end)
5606 } else {
5607 (selection.start, selection.end)
5608 };
5609
5610 let text = buffer.text_for_range(start..end).collect::<String>();
5611 let old_length = text.len() as i32;
5612 let text = callback(&text);
5613
5614 new_selections.push(Selection {
5615 start: (start as i32 - selection_adjustment) as usize,
5616 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
5617 goal: SelectionGoal::None,
5618 ..selection
5619 });
5620
5621 selection_adjustment += old_length - text.len() as i32;
5622
5623 edits.push((start..end, text));
5624 }
5625
5626 self.transact(cx, |this, cx| {
5627 this.buffer.update(cx, |buffer, cx| {
5628 buffer.edit(edits, None, cx);
5629 });
5630
5631 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5632 s.select(new_selections);
5633 });
5634
5635 this.request_autoscroll(Autoscroll::fit(), cx);
5636 });
5637 }
5638
5639 pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
5640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5641 let buffer = &display_map.buffer_snapshot;
5642 let selections = self.selections.all::<Point>(cx);
5643
5644 let mut edits = Vec::new();
5645 let mut selections_iter = selections.iter().peekable();
5646 while let Some(selection) = selections_iter.next() {
5647 // Avoid duplicating the same lines twice.
5648 let mut rows = selection.spanned_rows(false, &display_map);
5649
5650 while let Some(next_selection) = selections_iter.peek() {
5651 let next_rows = next_selection.spanned_rows(false, &display_map);
5652 if next_rows.start < rows.end {
5653 rows.end = next_rows.end;
5654 selections_iter.next().unwrap();
5655 } else {
5656 break;
5657 }
5658 }
5659
5660 // Copy the text from the selected row region and splice it either at the start
5661 // or end of the region.
5662 let start = Point::new(rows.start.0, 0);
5663 let end = Point::new(
5664 rows.end.previous_row().0,
5665 buffer.line_len(rows.end.previous_row()),
5666 );
5667 let text = buffer
5668 .text_for_range(start..end)
5669 .chain(Some("\n"))
5670 .collect::<String>();
5671 let insert_location = if upwards {
5672 Point::new(rows.end.0, 0)
5673 } else {
5674 start
5675 };
5676 edits.push((insert_location..insert_location, text));
5677 }
5678
5679 self.transact(cx, |this, cx| {
5680 this.buffer.update(cx, |buffer, cx| {
5681 buffer.edit(edits, None, cx);
5682 });
5683
5684 this.request_autoscroll(Autoscroll::fit(), cx);
5685 });
5686 }
5687
5688 pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
5689 self.duplicate_line(true, cx);
5690 }
5691
5692 pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
5693 self.duplicate_line(false, cx);
5694 }
5695
5696 pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
5697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5698 let buffer = self.buffer.read(cx).snapshot(cx);
5699
5700 let mut edits = Vec::new();
5701 let mut unfold_ranges = Vec::new();
5702 let mut refold_ranges = Vec::new();
5703
5704 let selections = self.selections.all::<Point>(cx);
5705 let mut selections = selections.iter().peekable();
5706 let mut contiguous_row_selections = Vec::new();
5707 let mut new_selections = Vec::new();
5708
5709 while let Some(selection) = selections.next() {
5710 // Find all the selections that span a contiguous row range
5711 let (start_row, end_row) = consume_contiguous_rows(
5712 &mut contiguous_row_selections,
5713 selection,
5714 &display_map,
5715 &mut selections,
5716 );
5717
5718 // Move the text spanned by the row range to be before the line preceding the row range
5719 if start_row.0 > 0 {
5720 let range_to_move = Point::new(
5721 start_row.previous_row().0,
5722 buffer.line_len(start_row.previous_row()),
5723 )
5724 ..Point::new(
5725 end_row.previous_row().0,
5726 buffer.line_len(end_row.previous_row()),
5727 );
5728 let insertion_point = display_map
5729 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
5730 .0;
5731
5732 // Don't move lines across excerpts
5733 if buffer
5734 .excerpt_boundaries_in_range((
5735 Bound::Excluded(insertion_point),
5736 Bound::Included(range_to_move.end),
5737 ))
5738 .next()
5739 .is_none()
5740 {
5741 let text = buffer
5742 .text_for_range(range_to_move.clone())
5743 .flat_map(|s| s.chars())
5744 .skip(1)
5745 .chain(['\n'])
5746 .collect::<String>();
5747
5748 edits.push((
5749 buffer.anchor_after(range_to_move.start)
5750 ..buffer.anchor_before(range_to_move.end),
5751 String::new(),
5752 ));
5753 let insertion_anchor = buffer.anchor_after(insertion_point);
5754 edits.push((insertion_anchor..insertion_anchor, text));
5755
5756 let row_delta = range_to_move.start.row - insertion_point.row + 1;
5757
5758 // Move selections up
5759 new_selections.extend(contiguous_row_selections.drain(..).map(
5760 |mut selection| {
5761 selection.start.row -= row_delta;
5762 selection.end.row -= row_delta;
5763 selection
5764 },
5765 ));
5766
5767 // Move folds up
5768 unfold_ranges.push(range_to_move.clone());
5769 for fold in display_map.folds_in_range(
5770 buffer.anchor_before(range_to_move.start)
5771 ..buffer.anchor_after(range_to_move.end),
5772 ) {
5773 let mut start = fold.range.start.to_point(&buffer);
5774 let mut end = fold.range.end.to_point(&buffer);
5775 start.row -= row_delta;
5776 end.row -= row_delta;
5777 refold_ranges.push(start..end);
5778 }
5779 }
5780 }
5781
5782 // If we didn't move line(s), preserve the existing selections
5783 new_selections.append(&mut contiguous_row_selections);
5784 }
5785
5786 self.transact(cx, |this, cx| {
5787 this.unfold_ranges(unfold_ranges, true, true, cx);
5788 this.buffer.update(cx, |buffer, cx| {
5789 for (range, text) in edits {
5790 buffer.edit([(range, text)], None, cx);
5791 }
5792 });
5793 this.fold_ranges(refold_ranges, true, cx);
5794 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5795 s.select(new_selections);
5796 })
5797 });
5798 }
5799
5800 pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
5801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5802 let buffer = self.buffer.read(cx).snapshot(cx);
5803
5804 let mut edits = Vec::new();
5805 let mut unfold_ranges = Vec::new();
5806 let mut refold_ranges = Vec::new();
5807
5808 let selections = self.selections.all::<Point>(cx);
5809 let mut selections = selections.iter().peekable();
5810 let mut contiguous_row_selections = Vec::new();
5811 let mut new_selections = Vec::new();
5812
5813 while let Some(selection) = selections.next() {
5814 // Find all the selections that span a contiguous row range
5815 let (start_row, end_row) = consume_contiguous_rows(
5816 &mut contiguous_row_selections,
5817 selection,
5818 &display_map,
5819 &mut selections,
5820 );
5821
5822 // Move the text spanned by the row range to be after the last line of the row range
5823 if end_row.0 <= buffer.max_point().row {
5824 let range_to_move =
5825 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
5826 let insertion_point = display_map
5827 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
5828 .0;
5829
5830 // Don't move lines across excerpt boundaries
5831 if buffer
5832 .excerpt_boundaries_in_range((
5833 Bound::Excluded(range_to_move.start),
5834 Bound::Included(insertion_point),
5835 ))
5836 .next()
5837 .is_none()
5838 {
5839 let mut text = String::from("\n");
5840 text.extend(buffer.text_for_range(range_to_move.clone()));
5841 text.pop(); // Drop trailing newline
5842 edits.push((
5843 buffer.anchor_after(range_to_move.start)
5844 ..buffer.anchor_before(range_to_move.end),
5845 String::new(),
5846 ));
5847 let insertion_anchor = buffer.anchor_after(insertion_point);
5848 edits.push((insertion_anchor..insertion_anchor, text));
5849
5850 let row_delta = insertion_point.row - range_to_move.end.row + 1;
5851
5852 // Move selections down
5853 new_selections.extend(contiguous_row_selections.drain(..).map(
5854 |mut selection| {
5855 selection.start.row += row_delta;
5856 selection.end.row += row_delta;
5857 selection
5858 },
5859 ));
5860
5861 // Move folds down
5862 unfold_ranges.push(range_to_move.clone());
5863 for fold in display_map.folds_in_range(
5864 buffer.anchor_before(range_to_move.start)
5865 ..buffer.anchor_after(range_to_move.end),
5866 ) {
5867 let mut start = fold.range.start.to_point(&buffer);
5868 let mut end = fold.range.end.to_point(&buffer);
5869 start.row += row_delta;
5870 end.row += row_delta;
5871 refold_ranges.push(start..end);
5872 }
5873 }
5874 }
5875
5876 // If we didn't move line(s), preserve the existing selections
5877 new_selections.append(&mut contiguous_row_selections);
5878 }
5879
5880 self.transact(cx, |this, cx| {
5881 this.unfold_ranges(unfold_ranges, true, true, cx);
5882 this.buffer.update(cx, |buffer, cx| {
5883 for (range, text) in edits {
5884 buffer.edit([(range, text)], None, cx);
5885 }
5886 });
5887 this.fold_ranges(refold_ranges, true, cx);
5888 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
5889 });
5890 }
5891
5892 pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext<Self>) {
5893 let text_layout_details = &self.text_layout_details(cx);
5894 self.transact(cx, |this, cx| {
5895 let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5896 let mut edits: Vec<(Range<usize>, String)> = Default::default();
5897 let line_mode = s.line_mode;
5898 s.move_with(|display_map, selection| {
5899 if !selection.is_empty() || line_mode {
5900 return;
5901 }
5902
5903 let mut head = selection.head();
5904 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
5905 if head.column() == display_map.line_len(head.row()) {
5906 transpose_offset = display_map
5907 .buffer_snapshot
5908 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
5909 }
5910
5911 if transpose_offset == 0 {
5912 return;
5913 }
5914
5915 *head.column_mut() += 1;
5916 head = display_map.clip_point(head, Bias::Right);
5917 let goal = SelectionGoal::HorizontalPosition(
5918 display_map
5919 .x_for_display_point(head, &text_layout_details)
5920 .into(),
5921 );
5922 selection.collapse_to(head, goal);
5923
5924 let transpose_start = display_map
5925 .buffer_snapshot
5926 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
5927 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
5928 let transpose_end = display_map
5929 .buffer_snapshot
5930 .clip_offset(transpose_offset + 1, Bias::Right);
5931 if let Some(ch) =
5932 display_map.buffer_snapshot.chars_at(transpose_start).next()
5933 {
5934 edits.push((transpose_start..transpose_offset, String::new()));
5935 edits.push((transpose_end..transpose_end, ch.to_string()));
5936 }
5937 }
5938 });
5939 edits
5940 });
5941 this.buffer
5942 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
5943 let selections = this.selections.all::<usize>(cx);
5944 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5945 s.select(selections);
5946 });
5947 });
5948 }
5949
5950 pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
5951 let mut text = String::new();
5952 let buffer = self.buffer.read(cx).snapshot(cx);
5953 let mut selections = self.selections.all::<Point>(cx);
5954 let mut clipboard_selections = Vec::with_capacity(selections.len());
5955 {
5956 let max_point = buffer.max_point();
5957 let mut is_first = true;
5958 for selection in &mut selections {
5959 let is_entire_line = selection.is_empty() || self.selections.line_mode;
5960 if is_entire_line {
5961 selection.start = Point::new(selection.start.row, 0);
5962 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
5963 selection.goal = SelectionGoal::None;
5964 }
5965 if is_first {
5966 is_first = false;
5967 } else {
5968 text += "\n";
5969 }
5970 let mut len = 0;
5971 for chunk in buffer.text_for_range(selection.start..selection.end) {
5972 text.push_str(chunk);
5973 len += chunk.len();
5974 }
5975 clipboard_selections.push(ClipboardSelection {
5976 len,
5977 is_entire_line,
5978 first_line_indent: buffer
5979 .indent_size_for_line(MultiBufferRow(selection.start.row))
5980 .len,
5981 });
5982 }
5983 }
5984
5985 self.transact(cx, |this, cx| {
5986 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5987 s.select(selections);
5988 });
5989 this.insert("", cx);
5990 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
5991 });
5992 }
5993
5994 pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
5995 let selections = self.selections.all::<Point>(cx);
5996 let buffer = self.buffer.read(cx).read(cx);
5997 let mut text = String::new();
5998
5999 let mut clipboard_selections = Vec::with_capacity(selections.len());
6000 {
6001 let max_point = buffer.max_point();
6002 let mut is_first = true;
6003 for selection in selections.iter() {
6004 let mut start = selection.start;
6005 let mut end = selection.end;
6006 let is_entire_line = selection.is_empty() || self.selections.line_mode;
6007 if is_entire_line {
6008 start = Point::new(start.row, 0);
6009 end = cmp::min(max_point, Point::new(end.row + 1, 0));
6010 }
6011 if is_first {
6012 is_first = false;
6013 } else {
6014 text += "\n";
6015 }
6016 let mut len = 0;
6017 for chunk in buffer.text_for_range(start..end) {
6018 text.push_str(chunk);
6019 len += chunk.len();
6020 }
6021 clipboard_selections.push(ClipboardSelection {
6022 len,
6023 is_entire_line,
6024 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
6025 });
6026 }
6027 }
6028
6029 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
6030 }
6031
6032 pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
6033 if self.read_only(cx) {
6034 return;
6035 }
6036
6037 self.transact(cx, |this, cx| {
6038 if let Some(item) = cx.read_from_clipboard() {
6039 let clipboard_text = Cow::Borrowed(item.text());
6040 if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
6041 let old_selections = this.selections.all::<usize>(cx);
6042 let all_selections_were_entire_line =
6043 clipboard_selections.iter().all(|s| s.is_entire_line);
6044 let first_selection_indent_column =
6045 clipboard_selections.first().map(|s| s.first_line_indent);
6046 if clipboard_selections.len() != old_selections.len() {
6047 clipboard_selections.drain(..);
6048 }
6049
6050 this.buffer.update(cx, |buffer, cx| {
6051 let snapshot = buffer.read(cx);
6052 let mut start_offset = 0;
6053 let mut edits = Vec::new();
6054 let mut original_indent_columns = Vec::new();
6055 let line_mode = this.selections.line_mode;
6056 for (ix, selection) in old_selections.iter().enumerate() {
6057 let to_insert;
6058 let entire_line;
6059 let original_indent_column;
6060 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
6061 let end_offset = start_offset + clipboard_selection.len;
6062 to_insert = &clipboard_text[start_offset..end_offset];
6063 entire_line = clipboard_selection.is_entire_line;
6064 start_offset = end_offset + 1;
6065 original_indent_column =
6066 Some(clipboard_selection.first_line_indent);
6067 } else {
6068 to_insert = clipboard_text.as_str();
6069 entire_line = all_selections_were_entire_line;
6070 original_indent_column = first_selection_indent_column
6071 }
6072
6073 // If the corresponding selection was empty when this slice of the
6074 // clipboard text was written, then the entire line containing the
6075 // selection was copied. If this selection is also currently empty,
6076 // then paste the line before the current line of the buffer.
6077 let range = if selection.is_empty() && !line_mode && entire_line {
6078 let column = selection.start.to_point(&snapshot).column as usize;
6079 let line_start = selection.start - column;
6080 line_start..line_start
6081 } else {
6082 selection.range()
6083 };
6084
6085 edits.push((range, to_insert));
6086 original_indent_columns.extend(original_indent_column);
6087 }
6088 drop(snapshot);
6089
6090 buffer.edit(
6091 edits,
6092 Some(AutoindentMode::Block {
6093 original_indent_columns,
6094 }),
6095 cx,
6096 );
6097 });
6098
6099 let selections = this.selections.all::<usize>(cx);
6100 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
6101 } else {
6102 this.insert(&clipboard_text, cx);
6103 }
6104 }
6105 });
6106 }
6107
6108 pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
6109 if self.read_only(cx) {
6110 return;
6111 }
6112
6113 if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
6114 if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() {
6115 self.change_selections(None, cx, |s| {
6116 s.select_anchors(selections.to_vec());
6117 });
6118 }
6119 self.request_autoscroll(Autoscroll::fit(), cx);
6120 self.unmark_text(cx);
6121 self.refresh_inline_completion(true, cx);
6122 cx.emit(EditorEvent::Edited);
6123 cx.emit(EditorEvent::TransactionUndone {
6124 transaction_id: tx_id,
6125 });
6126 }
6127 }
6128
6129 pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
6130 if self.read_only(cx) {
6131 return;
6132 }
6133
6134 if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
6135 if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned()
6136 {
6137 self.change_selections(None, cx, |s| {
6138 s.select_anchors(selections.to_vec());
6139 });
6140 }
6141 self.request_autoscroll(Autoscroll::fit(), cx);
6142 self.unmark_text(cx);
6143 self.refresh_inline_completion(true, cx);
6144 cx.emit(EditorEvent::Edited);
6145 }
6146 }
6147
6148 pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext<Self>) {
6149 self.buffer
6150 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
6151 }
6152
6153 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut ViewContext<Self>) {
6154 self.buffer
6155 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
6156 }
6157
6158 pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
6159 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6160 let line_mode = s.line_mode;
6161 s.move_with(|map, selection| {
6162 let cursor = if selection.is_empty() && !line_mode {
6163 movement::left(map, selection.start)
6164 } else {
6165 selection.start
6166 };
6167 selection.collapse_to(cursor, SelectionGoal::None);
6168 });
6169 })
6170 }
6171
6172 pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
6173 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6174 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
6175 })
6176 }
6177
6178 pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
6179 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6180 let line_mode = s.line_mode;
6181 s.move_with(|map, selection| {
6182 let cursor = if selection.is_empty() && !line_mode {
6183 movement::right(map, selection.end)
6184 } else {
6185 selection.end
6186 };
6187 selection.collapse_to(cursor, SelectionGoal::None)
6188 });
6189 })
6190 }
6191
6192 pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
6193 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6194 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
6195 })
6196 }
6197
6198 pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
6199 if self.take_rename(true, cx).is_some() {
6200 return;
6201 }
6202
6203 if matches!(self.mode, EditorMode::SingleLine) {
6204 cx.propagate();
6205 return;
6206 }
6207
6208 let text_layout_details = &self.text_layout_details(cx);
6209
6210 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6211 let line_mode = s.line_mode;
6212 s.move_with(|map, selection| {
6213 if !selection.is_empty() && !line_mode {
6214 selection.goal = SelectionGoal::None;
6215 }
6216 let (cursor, goal) = movement::up(
6217 map,
6218 selection.start,
6219 selection.goal,
6220 false,
6221 &text_layout_details,
6222 );
6223 selection.collapse_to(cursor, goal);
6224 });
6225 })
6226 }
6227
6228 pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) {
6229 if self.take_rename(true, cx).is_some() {
6230 return;
6231 }
6232
6233 if matches!(self.mode, EditorMode::SingleLine) {
6234 cx.propagate();
6235 return;
6236 }
6237
6238 let text_layout_details = &self.text_layout_details(cx);
6239
6240 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6241 let line_mode = s.line_mode;
6242 s.move_with(|map, selection| {
6243 if !selection.is_empty() && !line_mode {
6244 selection.goal = SelectionGoal::None;
6245 }
6246 let (cursor, goal) = movement::up_by_rows(
6247 map,
6248 selection.start,
6249 action.lines,
6250 selection.goal,
6251 false,
6252 &text_layout_details,
6253 );
6254 selection.collapse_to(cursor, goal);
6255 });
6256 })
6257 }
6258
6259 pub fn move_down_by_lines(&mut self, action: &MoveDownByLines, cx: &mut ViewContext<Self>) {
6260 if self.take_rename(true, cx).is_some() {
6261 return;
6262 }
6263
6264 if matches!(self.mode, EditorMode::SingleLine) {
6265 cx.propagate();
6266 return;
6267 }
6268
6269 let text_layout_details = &self.text_layout_details(cx);
6270
6271 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6272 let line_mode = s.line_mode;
6273 s.move_with(|map, selection| {
6274 if !selection.is_empty() && !line_mode {
6275 selection.goal = SelectionGoal::None;
6276 }
6277 let (cursor, goal) = movement::down_by_rows(
6278 map,
6279 selection.start,
6280 action.lines,
6281 selection.goal,
6282 false,
6283 &text_layout_details,
6284 );
6285 selection.collapse_to(cursor, goal);
6286 });
6287 })
6288 }
6289
6290 pub fn select_down_by_lines(&mut self, action: &SelectDownByLines, cx: &mut ViewContext<Self>) {
6291 let text_layout_details = &self.text_layout_details(cx);
6292 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6293 s.move_heads_with(|map, head, goal| {
6294 movement::down_by_rows(map, head, action.lines, goal, false, &text_layout_details)
6295 })
6296 })
6297 }
6298
6299 pub fn select_up_by_lines(&mut self, action: &SelectUpByLines, cx: &mut ViewContext<Self>) {
6300 let text_layout_details = &self.text_layout_details(cx);
6301 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6302 s.move_heads_with(|map, head, goal| {
6303 movement::up_by_rows(map, head, action.lines, goal, false, &text_layout_details)
6304 })
6305 })
6306 }
6307
6308 pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
6309 if self.take_rename(true, cx).is_some() {
6310 return;
6311 }
6312
6313 if matches!(self.mode, EditorMode::SingleLine) {
6314 cx.propagate();
6315 return;
6316 }
6317
6318 let row_count = if let Some(row_count) = self.visible_line_count() {
6319 row_count as u32 - 1
6320 } else {
6321 return;
6322 };
6323
6324 let autoscroll = if action.center_cursor {
6325 Autoscroll::center()
6326 } else {
6327 Autoscroll::fit()
6328 };
6329
6330 let text_layout_details = &self.text_layout_details(cx);
6331
6332 self.change_selections(Some(autoscroll), cx, |s| {
6333 let line_mode = s.line_mode;
6334 s.move_with(|map, selection| {
6335 if !selection.is_empty() && !line_mode {
6336 selection.goal = SelectionGoal::None;
6337 }
6338 let (cursor, goal) = movement::up_by_rows(
6339 map,
6340 selection.end,
6341 row_count,
6342 selection.goal,
6343 false,
6344 &text_layout_details,
6345 );
6346 selection.collapse_to(cursor, goal);
6347 });
6348 });
6349 }
6350
6351 pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
6352 let text_layout_details = &self.text_layout_details(cx);
6353 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6354 s.move_heads_with(|map, head, goal| {
6355 movement::up(map, head, goal, false, &text_layout_details)
6356 })
6357 })
6358 }
6359
6360 pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
6361 self.take_rename(true, cx);
6362
6363 if self.mode == EditorMode::SingleLine {
6364 cx.propagate();
6365 return;
6366 }
6367
6368 let text_layout_details = &self.text_layout_details(cx);
6369 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6370 let line_mode = s.line_mode;
6371 s.move_with(|map, selection| {
6372 if !selection.is_empty() && !line_mode {
6373 selection.goal = SelectionGoal::None;
6374 }
6375 let (cursor, goal) = movement::down(
6376 map,
6377 selection.end,
6378 selection.goal,
6379 false,
6380 &text_layout_details,
6381 );
6382 selection.collapse_to(cursor, goal);
6383 });
6384 });
6385 }
6386
6387 pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
6388 if self.take_rename(true, cx).is_some() {
6389 return;
6390 }
6391
6392 if self
6393 .context_menu
6394 .write()
6395 .as_mut()
6396 .map(|menu| menu.select_last(self.project.as_ref(), cx))
6397 .unwrap_or(false)
6398 {
6399 return;
6400 }
6401
6402 if matches!(self.mode, EditorMode::SingleLine) {
6403 cx.propagate();
6404 return;
6405 }
6406
6407 let row_count = if let Some(row_count) = self.visible_line_count() {
6408 row_count as u32 - 1
6409 } else {
6410 return;
6411 };
6412
6413 let autoscroll = if action.center_cursor {
6414 Autoscroll::center()
6415 } else {
6416 Autoscroll::fit()
6417 };
6418
6419 let text_layout_details = &self.text_layout_details(cx);
6420 self.change_selections(Some(autoscroll), cx, |s| {
6421 let line_mode = s.line_mode;
6422 s.move_with(|map, selection| {
6423 if !selection.is_empty() && !line_mode {
6424 selection.goal = SelectionGoal::None;
6425 }
6426 let (cursor, goal) = movement::down_by_rows(
6427 map,
6428 selection.end,
6429 row_count,
6430 selection.goal,
6431 false,
6432 &text_layout_details,
6433 );
6434 selection.collapse_to(cursor, goal);
6435 });
6436 });
6437 }
6438
6439 pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
6440 let text_layout_details = &self.text_layout_details(cx);
6441 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6442 s.move_heads_with(|map, head, goal| {
6443 movement::down(map, head, goal, false, &text_layout_details)
6444 })
6445 });
6446 }
6447
6448 pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
6449 if let Some(context_menu) = self.context_menu.write().as_mut() {
6450 context_menu.select_first(self.project.as_ref(), cx);
6451 }
6452 }
6453
6454 pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
6455 if let Some(context_menu) = self.context_menu.write().as_mut() {
6456 context_menu.select_prev(self.project.as_ref(), cx);
6457 }
6458 }
6459
6460 pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
6461 if let Some(context_menu) = self.context_menu.write().as_mut() {
6462 context_menu.select_next(self.project.as_ref(), cx);
6463 }
6464 }
6465
6466 pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
6467 if let Some(context_menu) = self.context_menu.write().as_mut() {
6468 context_menu.select_last(self.project.as_ref(), cx);
6469 }
6470 }
6471
6472 pub fn move_to_previous_word_start(
6473 &mut self,
6474 _: &MoveToPreviousWordStart,
6475 cx: &mut ViewContext<Self>,
6476 ) {
6477 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6478 s.move_cursors_with(|map, head, _| {
6479 (
6480 movement::previous_word_start(map, head),
6481 SelectionGoal::None,
6482 )
6483 });
6484 })
6485 }
6486
6487 pub fn move_to_previous_subword_start(
6488 &mut self,
6489 _: &MoveToPreviousSubwordStart,
6490 cx: &mut ViewContext<Self>,
6491 ) {
6492 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6493 s.move_cursors_with(|map, head, _| {
6494 (
6495 movement::previous_subword_start(map, head),
6496 SelectionGoal::None,
6497 )
6498 });
6499 })
6500 }
6501
6502 pub fn select_to_previous_word_start(
6503 &mut self,
6504 _: &SelectToPreviousWordStart,
6505 cx: &mut ViewContext<Self>,
6506 ) {
6507 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6508 s.move_heads_with(|map, head, _| {
6509 (
6510 movement::previous_word_start(map, head),
6511 SelectionGoal::None,
6512 )
6513 });
6514 })
6515 }
6516
6517 pub fn select_to_previous_subword_start(
6518 &mut self,
6519 _: &SelectToPreviousSubwordStart,
6520 cx: &mut ViewContext<Self>,
6521 ) {
6522 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6523 s.move_heads_with(|map, head, _| {
6524 (
6525 movement::previous_subword_start(map, head),
6526 SelectionGoal::None,
6527 )
6528 });
6529 })
6530 }
6531
6532 pub fn delete_to_previous_word_start(
6533 &mut self,
6534 _: &DeleteToPreviousWordStart,
6535 cx: &mut ViewContext<Self>,
6536 ) {
6537 self.transact(cx, |this, cx| {
6538 this.select_autoclose_pair(cx);
6539 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6540 let line_mode = s.line_mode;
6541 s.move_with(|map, selection| {
6542 if selection.is_empty() && !line_mode {
6543 let cursor = movement::previous_word_start(map, selection.head());
6544 selection.set_head(cursor, SelectionGoal::None);
6545 }
6546 });
6547 });
6548 this.insert("", cx);
6549 });
6550 }
6551
6552 pub fn delete_to_previous_subword_start(
6553 &mut self,
6554 _: &DeleteToPreviousSubwordStart,
6555 cx: &mut ViewContext<Self>,
6556 ) {
6557 self.transact(cx, |this, cx| {
6558 this.select_autoclose_pair(cx);
6559 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6560 let line_mode = s.line_mode;
6561 s.move_with(|map, selection| {
6562 if selection.is_empty() && !line_mode {
6563 let cursor = movement::previous_subword_start(map, selection.head());
6564 selection.set_head(cursor, SelectionGoal::None);
6565 }
6566 });
6567 });
6568 this.insert("", cx);
6569 });
6570 }
6571
6572 pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
6573 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6574 s.move_cursors_with(|map, head, _| {
6575 (movement::next_word_end(map, head), SelectionGoal::None)
6576 });
6577 })
6578 }
6579
6580 pub fn move_to_next_subword_end(
6581 &mut self,
6582 _: &MoveToNextSubwordEnd,
6583 cx: &mut ViewContext<Self>,
6584 ) {
6585 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6586 s.move_cursors_with(|map, head, _| {
6587 (movement::next_subword_end(map, head), SelectionGoal::None)
6588 });
6589 })
6590 }
6591
6592 pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext<Self>) {
6593 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6594 s.move_heads_with(|map, head, _| {
6595 (movement::next_word_end(map, head), SelectionGoal::None)
6596 });
6597 })
6598 }
6599
6600 pub fn select_to_next_subword_end(
6601 &mut self,
6602 _: &SelectToNextSubwordEnd,
6603 cx: &mut ViewContext<Self>,
6604 ) {
6605 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6606 s.move_heads_with(|map, head, _| {
6607 (movement::next_subword_end(map, head), SelectionGoal::None)
6608 });
6609 })
6610 }
6611
6612 pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
6613 self.transact(cx, |this, cx| {
6614 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6615 let line_mode = s.line_mode;
6616 s.move_with(|map, selection| {
6617 if selection.is_empty() && !line_mode {
6618 let cursor = movement::next_word_end(map, selection.head());
6619 selection.set_head(cursor, SelectionGoal::None);
6620 }
6621 });
6622 });
6623 this.insert("", cx);
6624 });
6625 }
6626
6627 pub fn delete_to_next_subword_end(
6628 &mut self,
6629 _: &DeleteToNextSubwordEnd,
6630 cx: &mut ViewContext<Self>,
6631 ) {
6632 self.transact(cx, |this, cx| {
6633 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6634 s.move_with(|map, selection| {
6635 if selection.is_empty() {
6636 let cursor = movement::next_subword_end(map, selection.head());
6637 selection.set_head(cursor, SelectionGoal::None);
6638 }
6639 });
6640 });
6641 this.insert("", cx);
6642 });
6643 }
6644
6645 pub fn move_to_beginning_of_line(
6646 &mut self,
6647 action: &MoveToBeginningOfLine,
6648 cx: &mut ViewContext<Self>,
6649 ) {
6650 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6651 s.move_cursors_with(|map, head, _| {
6652 (
6653 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
6654 SelectionGoal::None,
6655 )
6656 });
6657 })
6658 }
6659
6660 pub fn select_to_beginning_of_line(
6661 &mut self,
6662 action: &SelectToBeginningOfLine,
6663 cx: &mut ViewContext<Self>,
6664 ) {
6665 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6666 s.move_heads_with(|map, head, _| {
6667 (
6668 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
6669 SelectionGoal::None,
6670 )
6671 });
6672 });
6673 }
6674
6675 pub fn delete_to_beginning_of_line(
6676 &mut self,
6677 _: &DeleteToBeginningOfLine,
6678 cx: &mut ViewContext<Self>,
6679 ) {
6680 self.transact(cx, |this, cx| {
6681 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6682 s.move_with(|_, selection| {
6683 selection.reversed = true;
6684 });
6685 });
6686
6687 this.select_to_beginning_of_line(
6688 &SelectToBeginningOfLine {
6689 stop_at_soft_wraps: false,
6690 },
6691 cx,
6692 );
6693 this.backspace(&Backspace, cx);
6694 });
6695 }
6696
6697 pub fn move_to_end_of_line(&mut self, action: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
6698 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6699 s.move_cursors_with(|map, head, _| {
6700 (
6701 movement::line_end(map, head, action.stop_at_soft_wraps),
6702 SelectionGoal::None,
6703 )
6704 });
6705 })
6706 }
6707
6708 pub fn select_to_end_of_line(
6709 &mut self,
6710 action: &SelectToEndOfLine,
6711 cx: &mut ViewContext<Self>,
6712 ) {
6713 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6714 s.move_heads_with(|map, head, _| {
6715 (
6716 movement::line_end(map, head, action.stop_at_soft_wraps),
6717 SelectionGoal::None,
6718 )
6719 });
6720 })
6721 }
6722
6723 pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
6724 self.transact(cx, |this, cx| {
6725 this.select_to_end_of_line(
6726 &SelectToEndOfLine {
6727 stop_at_soft_wraps: false,
6728 },
6729 cx,
6730 );
6731 this.delete(&Delete, cx);
6732 });
6733 }
6734
6735 pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
6736 self.transact(cx, |this, cx| {
6737 this.select_to_end_of_line(
6738 &SelectToEndOfLine {
6739 stop_at_soft_wraps: false,
6740 },
6741 cx,
6742 );
6743 this.cut(&Cut, cx);
6744 });
6745 }
6746
6747 pub fn move_to_start_of_paragraph(
6748 &mut self,
6749 _: &MoveToStartOfParagraph,
6750 cx: &mut ViewContext<Self>,
6751 ) {
6752 if matches!(self.mode, EditorMode::SingleLine) {
6753 cx.propagate();
6754 return;
6755 }
6756
6757 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6758 s.move_with(|map, selection| {
6759 selection.collapse_to(
6760 movement::start_of_paragraph(map, selection.head(), 1),
6761 SelectionGoal::None,
6762 )
6763 });
6764 })
6765 }
6766
6767 pub fn move_to_end_of_paragraph(
6768 &mut self,
6769 _: &MoveToEndOfParagraph,
6770 cx: &mut ViewContext<Self>,
6771 ) {
6772 if matches!(self.mode, EditorMode::SingleLine) {
6773 cx.propagate();
6774 return;
6775 }
6776
6777 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6778 s.move_with(|map, selection| {
6779 selection.collapse_to(
6780 movement::end_of_paragraph(map, selection.head(), 1),
6781 SelectionGoal::None,
6782 )
6783 });
6784 })
6785 }
6786
6787 pub fn select_to_start_of_paragraph(
6788 &mut self,
6789 _: &SelectToStartOfParagraph,
6790 cx: &mut ViewContext<Self>,
6791 ) {
6792 if matches!(self.mode, EditorMode::SingleLine) {
6793 cx.propagate();
6794 return;
6795 }
6796
6797 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6798 s.move_heads_with(|map, head, _| {
6799 (
6800 movement::start_of_paragraph(map, head, 1),
6801 SelectionGoal::None,
6802 )
6803 });
6804 })
6805 }
6806
6807 pub fn select_to_end_of_paragraph(
6808 &mut self,
6809 _: &SelectToEndOfParagraph,
6810 cx: &mut ViewContext<Self>,
6811 ) {
6812 if matches!(self.mode, EditorMode::SingleLine) {
6813 cx.propagate();
6814 return;
6815 }
6816
6817 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6818 s.move_heads_with(|map, head, _| {
6819 (
6820 movement::end_of_paragraph(map, head, 1),
6821 SelectionGoal::None,
6822 )
6823 });
6824 })
6825 }
6826
6827 pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
6828 if matches!(self.mode, EditorMode::SingleLine) {
6829 cx.propagate();
6830 return;
6831 }
6832
6833 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6834 s.select_ranges(vec![0..0]);
6835 });
6836 }
6837
6838 pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
6839 let mut selection = self.selections.last::<Point>(cx);
6840 selection.set_head(Point::zero(), SelectionGoal::None);
6841
6842 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6843 s.select(vec![selection]);
6844 });
6845 }
6846
6847 pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
6848 if matches!(self.mode, EditorMode::SingleLine) {
6849 cx.propagate();
6850 return;
6851 }
6852
6853 let cursor = self.buffer.read(cx).read(cx).len();
6854 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6855 s.select_ranges(vec![cursor..cursor])
6856 });
6857 }
6858
6859 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
6860 self.nav_history = nav_history;
6861 }
6862
6863 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
6864 self.nav_history.as_ref()
6865 }
6866
6867 fn push_to_nav_history(
6868 &mut self,
6869 cursor_anchor: Anchor,
6870 new_position: Option<Point>,
6871 cx: &mut ViewContext<Self>,
6872 ) {
6873 if let Some(nav_history) = self.nav_history.as_mut() {
6874 let buffer = self.buffer.read(cx).read(cx);
6875 let cursor_position = cursor_anchor.to_point(&buffer);
6876 let scroll_state = self.scroll_manager.anchor();
6877 let scroll_top_row = scroll_state.top_row(&buffer);
6878 drop(buffer);
6879
6880 if let Some(new_position) = new_position {
6881 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
6882 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
6883 return;
6884 }
6885 }
6886
6887 nav_history.push(
6888 Some(NavigationData {
6889 cursor_anchor,
6890 cursor_position,
6891 scroll_anchor: scroll_state,
6892 scroll_top_row,
6893 }),
6894 cx,
6895 );
6896 }
6897 }
6898
6899 pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
6900 let buffer = self.buffer.read(cx).snapshot(cx);
6901 let mut selection = self.selections.first::<usize>(cx);
6902 selection.set_head(buffer.len(), SelectionGoal::None);
6903 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6904 s.select(vec![selection]);
6905 });
6906 }
6907
6908 pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
6909 let end = self.buffer.read(cx).read(cx).len();
6910 self.change_selections(None, cx, |s| {
6911 s.select_ranges(vec![0..end]);
6912 });
6913 }
6914
6915 pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
6916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6917 let mut selections = self.selections.all::<Point>(cx);
6918 let max_point = display_map.buffer_snapshot.max_point();
6919 for selection in &mut selections {
6920 let rows = selection.spanned_rows(true, &display_map);
6921 selection.start = Point::new(rows.start.0, 0);
6922 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
6923 selection.reversed = false;
6924 }
6925 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6926 s.select(selections);
6927 });
6928 }
6929
6930 pub fn split_selection_into_lines(
6931 &mut self,
6932 _: &SplitSelectionIntoLines,
6933 cx: &mut ViewContext<Self>,
6934 ) {
6935 let mut to_unfold = Vec::new();
6936 let mut new_selection_ranges = Vec::new();
6937 {
6938 let selections = self.selections.all::<Point>(cx);
6939 let buffer = self.buffer.read(cx).read(cx);
6940 for selection in selections {
6941 for row in selection.start.row..selection.end.row {
6942 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
6943 new_selection_ranges.push(cursor..cursor);
6944 }
6945 new_selection_ranges.push(selection.end..selection.end);
6946 to_unfold.push(selection.start..selection.end);
6947 }
6948 }
6949 self.unfold_ranges(to_unfold, true, true, cx);
6950 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6951 s.select_ranges(new_selection_ranges);
6952 });
6953 }
6954
6955 pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
6956 self.add_selection(true, cx);
6957 }
6958
6959 pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
6960 self.add_selection(false, cx);
6961 }
6962
6963 fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
6964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6965 let mut selections = self.selections.all::<Point>(cx);
6966 let text_layout_details = self.text_layout_details(cx);
6967 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
6968 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
6969 let range = oldest_selection.display_range(&display_map).sorted();
6970
6971 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
6972 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
6973 let positions = start_x.min(end_x)..start_x.max(end_x);
6974
6975 selections.clear();
6976 let mut stack = Vec::new();
6977 for row in range.start.row().0..=range.end.row().0 {
6978 if let Some(selection) = self.selections.build_columnar_selection(
6979 &display_map,
6980 DisplayRow(row),
6981 &positions,
6982 oldest_selection.reversed,
6983 &text_layout_details,
6984 ) {
6985 stack.push(selection.id);
6986 selections.push(selection);
6987 }
6988 }
6989
6990 if above {
6991 stack.reverse();
6992 }
6993
6994 AddSelectionsState { above, stack }
6995 });
6996
6997 let last_added_selection = *state.stack.last().unwrap();
6998 let mut new_selections = Vec::new();
6999 if above == state.above {
7000 let end_row = if above {
7001 DisplayRow(0)
7002 } else {
7003 display_map.max_point().row()
7004 };
7005
7006 'outer: for selection in selections {
7007 if selection.id == last_added_selection {
7008 let range = selection.display_range(&display_map).sorted();
7009 debug_assert_eq!(range.start.row(), range.end.row());
7010 let mut row = range.start.row();
7011 let positions =
7012 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
7013 px(start)..px(end)
7014 } else {
7015 let start_x =
7016 display_map.x_for_display_point(range.start, &text_layout_details);
7017 let end_x =
7018 display_map.x_for_display_point(range.end, &text_layout_details);
7019 start_x.min(end_x)..start_x.max(end_x)
7020 };
7021
7022 while row != end_row {
7023 if above {
7024 row.0 -= 1;
7025 } else {
7026 row.0 += 1;
7027 }
7028
7029 if let Some(new_selection) = self.selections.build_columnar_selection(
7030 &display_map,
7031 row,
7032 &positions,
7033 selection.reversed,
7034 &text_layout_details,
7035 ) {
7036 state.stack.push(new_selection.id);
7037 if above {
7038 new_selections.push(new_selection);
7039 new_selections.push(selection);
7040 } else {
7041 new_selections.push(selection);
7042 new_selections.push(new_selection);
7043 }
7044
7045 continue 'outer;
7046 }
7047 }
7048 }
7049
7050 new_selections.push(selection);
7051 }
7052 } else {
7053 new_selections = selections;
7054 new_selections.retain(|s| s.id != last_added_selection);
7055 state.stack.pop();
7056 }
7057
7058 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7059 s.select(new_selections);
7060 });
7061 if state.stack.len() > 1 {
7062 self.add_selections_state = Some(state);
7063 }
7064 }
7065
7066 pub fn select_next_match_internal(
7067 &mut self,
7068 display_map: &DisplaySnapshot,
7069 replace_newest: bool,
7070 autoscroll: Option<Autoscroll>,
7071 cx: &mut ViewContext<Self>,
7072 ) -> Result<()> {
7073 fn select_next_match_ranges(
7074 this: &mut Editor,
7075 range: Range<usize>,
7076 replace_newest: bool,
7077 auto_scroll: Option<Autoscroll>,
7078 cx: &mut ViewContext<Editor>,
7079 ) {
7080 this.unfold_ranges([range.clone()], false, true, cx);
7081 this.change_selections(auto_scroll, cx, |s| {
7082 if replace_newest {
7083 s.delete(s.newest_anchor().id);
7084 }
7085 s.insert_range(range.clone());
7086 });
7087 }
7088
7089 let buffer = &display_map.buffer_snapshot;
7090 let mut selections = self.selections.all::<usize>(cx);
7091 if let Some(mut select_next_state) = self.select_next_state.take() {
7092 let query = &select_next_state.query;
7093 if !select_next_state.done {
7094 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
7095 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
7096 let mut next_selected_range = None;
7097
7098 let bytes_after_last_selection =
7099 buffer.bytes_in_range(last_selection.end..buffer.len());
7100 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
7101 let query_matches = query
7102 .stream_find_iter(bytes_after_last_selection)
7103 .map(|result| (last_selection.end, result))
7104 .chain(
7105 query
7106 .stream_find_iter(bytes_before_first_selection)
7107 .map(|result| (0, result)),
7108 );
7109
7110 for (start_offset, query_match) in query_matches {
7111 let query_match = query_match.unwrap(); // can only fail due to I/O
7112 let offset_range =
7113 start_offset + query_match.start()..start_offset + query_match.end();
7114 let display_range = offset_range.start.to_display_point(&display_map)
7115 ..offset_range.end.to_display_point(&display_map);
7116
7117 if !select_next_state.wordwise
7118 || (!movement::is_inside_word(&display_map, display_range.start)
7119 && !movement::is_inside_word(&display_map, display_range.end))
7120 {
7121 // TODO: This is n^2, because we might check all the selections
7122 if !selections
7123 .iter()
7124 .any(|selection| selection.range().overlaps(&offset_range))
7125 {
7126 next_selected_range = Some(offset_range);
7127 break;
7128 }
7129 }
7130 }
7131
7132 if let Some(next_selected_range) = next_selected_range {
7133 select_next_match_ranges(
7134 self,
7135 next_selected_range,
7136 replace_newest,
7137 autoscroll,
7138 cx,
7139 );
7140 } else {
7141 select_next_state.done = true;
7142 }
7143 }
7144
7145 self.select_next_state = Some(select_next_state);
7146 } else {
7147 let mut only_carets = true;
7148 let mut same_text_selected = true;
7149 let mut selected_text = None;
7150
7151 let mut selections_iter = selections.iter().peekable();
7152 while let Some(selection) = selections_iter.next() {
7153 if selection.start != selection.end {
7154 only_carets = false;
7155 }
7156
7157 if same_text_selected {
7158 if selected_text.is_none() {
7159 selected_text =
7160 Some(buffer.text_for_range(selection.range()).collect::<String>());
7161 }
7162
7163 if let Some(next_selection) = selections_iter.peek() {
7164 if next_selection.range().len() == selection.range().len() {
7165 let next_selected_text = buffer
7166 .text_for_range(next_selection.range())
7167 .collect::<String>();
7168 if Some(next_selected_text) != selected_text {
7169 same_text_selected = false;
7170 selected_text = None;
7171 }
7172 } else {
7173 same_text_selected = false;
7174 selected_text = None;
7175 }
7176 }
7177 }
7178 }
7179
7180 if only_carets {
7181 for selection in &mut selections {
7182 let word_range = movement::surrounding_word(
7183 &display_map,
7184 selection.start.to_display_point(&display_map),
7185 );
7186 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
7187 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
7188 selection.goal = SelectionGoal::None;
7189 selection.reversed = false;
7190 select_next_match_ranges(
7191 self,
7192 selection.start..selection.end,
7193 replace_newest,
7194 autoscroll,
7195 cx,
7196 );
7197 }
7198
7199 if selections.len() == 1 {
7200 let selection = selections
7201 .last()
7202 .expect("ensured that there's only one selection");
7203 let query = buffer
7204 .text_for_range(selection.start..selection.end)
7205 .collect::<String>();
7206 let is_empty = query.is_empty();
7207 let select_state = SelectNextState {
7208 query: AhoCorasick::new(&[query])?,
7209 wordwise: true,
7210 done: is_empty,
7211 };
7212 self.select_next_state = Some(select_state);
7213 } else {
7214 self.select_next_state = None;
7215 }
7216 } else if let Some(selected_text) = selected_text {
7217 self.select_next_state = Some(SelectNextState {
7218 query: AhoCorasick::new(&[selected_text])?,
7219 wordwise: false,
7220 done: false,
7221 });
7222 self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?;
7223 }
7224 }
7225 Ok(())
7226 }
7227
7228 pub fn select_all_matches(
7229 &mut self,
7230 _action: &SelectAllMatches,
7231 cx: &mut ViewContext<Self>,
7232 ) -> Result<()> {
7233 self.push_to_selection_history();
7234 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7235
7236 self.select_next_match_internal(&display_map, false, None, cx)?;
7237 let Some(select_next_state) = self.select_next_state.as_mut() else {
7238 return Ok(());
7239 };
7240 if select_next_state.done {
7241 return Ok(());
7242 }
7243
7244 let mut new_selections = self.selections.all::<usize>(cx);
7245
7246 let buffer = &display_map.buffer_snapshot;
7247 let query_matches = select_next_state
7248 .query
7249 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
7250
7251 for query_match in query_matches {
7252 let query_match = query_match.unwrap(); // can only fail due to I/O
7253 let offset_range = query_match.start()..query_match.end();
7254 let display_range = offset_range.start.to_display_point(&display_map)
7255 ..offset_range.end.to_display_point(&display_map);
7256
7257 if !select_next_state.wordwise
7258 || (!movement::is_inside_word(&display_map, display_range.start)
7259 && !movement::is_inside_word(&display_map, display_range.end))
7260 {
7261 self.selections.change_with(cx, |selections| {
7262 new_selections.push(Selection {
7263 id: selections.new_selection_id(),
7264 start: offset_range.start,
7265 end: offset_range.end,
7266 reversed: false,
7267 goal: SelectionGoal::None,
7268 });
7269 });
7270 }
7271 }
7272
7273 new_selections.sort_by_key(|selection| selection.start);
7274 let mut ix = 0;
7275 while ix + 1 < new_selections.len() {
7276 let current_selection = &new_selections[ix];
7277 let next_selection = &new_selections[ix + 1];
7278 if current_selection.range().overlaps(&next_selection.range()) {
7279 if current_selection.id < next_selection.id {
7280 new_selections.remove(ix + 1);
7281 } else {
7282 new_selections.remove(ix);
7283 }
7284 } else {
7285 ix += 1;
7286 }
7287 }
7288
7289 select_next_state.done = true;
7290 self.unfold_ranges(
7291 new_selections.iter().map(|selection| selection.range()),
7292 false,
7293 false,
7294 cx,
7295 );
7296 self.change_selections(Some(Autoscroll::fit()), cx, |selections| {
7297 selections.select(new_selections)
7298 });
7299
7300 Ok(())
7301 }
7302
7303 pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) -> Result<()> {
7304 self.push_to_selection_history();
7305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7306 self.select_next_match_internal(
7307 &display_map,
7308 action.replace_newest,
7309 Some(Autoscroll::newest()),
7310 cx,
7311 )?;
7312 Ok(())
7313 }
7314
7315 pub fn select_previous(
7316 &mut self,
7317 action: &SelectPrevious,
7318 cx: &mut ViewContext<Self>,
7319 ) -> Result<()> {
7320 self.push_to_selection_history();
7321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7322 let buffer = &display_map.buffer_snapshot;
7323 let mut selections = self.selections.all::<usize>(cx);
7324 if let Some(mut select_prev_state) = self.select_prev_state.take() {
7325 let query = &select_prev_state.query;
7326 if !select_prev_state.done {
7327 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
7328 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
7329 let mut next_selected_range = None;
7330 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
7331 let bytes_before_last_selection =
7332 buffer.reversed_bytes_in_range(0..last_selection.start);
7333 let bytes_after_first_selection =
7334 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
7335 let query_matches = query
7336 .stream_find_iter(bytes_before_last_selection)
7337 .map(|result| (last_selection.start, result))
7338 .chain(
7339 query
7340 .stream_find_iter(bytes_after_first_selection)
7341 .map(|result| (buffer.len(), result)),
7342 );
7343 for (end_offset, query_match) in query_matches {
7344 let query_match = query_match.unwrap(); // can only fail due to I/O
7345 let offset_range =
7346 end_offset - query_match.end()..end_offset - query_match.start();
7347 let display_range = offset_range.start.to_display_point(&display_map)
7348 ..offset_range.end.to_display_point(&display_map);
7349
7350 if !select_prev_state.wordwise
7351 || (!movement::is_inside_word(&display_map, display_range.start)
7352 && !movement::is_inside_word(&display_map, display_range.end))
7353 {
7354 next_selected_range = Some(offset_range);
7355 break;
7356 }
7357 }
7358
7359 if let Some(next_selected_range) = next_selected_range {
7360 self.unfold_ranges([next_selected_range.clone()], false, true, cx);
7361 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
7362 if action.replace_newest {
7363 s.delete(s.newest_anchor().id);
7364 }
7365 s.insert_range(next_selected_range);
7366 });
7367 } else {
7368 select_prev_state.done = true;
7369 }
7370 }
7371
7372 self.select_prev_state = Some(select_prev_state);
7373 } else {
7374 let mut only_carets = true;
7375 let mut same_text_selected = true;
7376 let mut selected_text = None;
7377
7378 let mut selections_iter = selections.iter().peekable();
7379 while let Some(selection) = selections_iter.next() {
7380 if selection.start != selection.end {
7381 only_carets = false;
7382 }
7383
7384 if same_text_selected {
7385 if selected_text.is_none() {
7386 selected_text =
7387 Some(buffer.text_for_range(selection.range()).collect::<String>());
7388 }
7389
7390 if let Some(next_selection) = selections_iter.peek() {
7391 if next_selection.range().len() == selection.range().len() {
7392 let next_selected_text = buffer
7393 .text_for_range(next_selection.range())
7394 .collect::<String>();
7395 if Some(next_selected_text) != selected_text {
7396 same_text_selected = false;
7397 selected_text = None;
7398 }
7399 } else {
7400 same_text_selected = false;
7401 selected_text = None;
7402 }
7403 }
7404 }
7405 }
7406
7407 if only_carets {
7408 for selection in &mut selections {
7409 let word_range = movement::surrounding_word(
7410 &display_map,
7411 selection.start.to_display_point(&display_map),
7412 );
7413 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
7414 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
7415 selection.goal = SelectionGoal::None;
7416 selection.reversed = false;
7417 }
7418 if selections.len() == 1 {
7419 let selection = selections
7420 .last()
7421 .expect("ensured that there's only one selection");
7422 let query = buffer
7423 .text_for_range(selection.start..selection.end)
7424 .collect::<String>();
7425 let is_empty = query.is_empty();
7426 let select_state = SelectNextState {
7427 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
7428 wordwise: true,
7429 done: is_empty,
7430 };
7431 self.select_prev_state = Some(select_state);
7432 } else {
7433 self.select_prev_state = None;
7434 }
7435
7436 self.unfold_ranges(
7437 selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
7438 false,
7439 true,
7440 cx,
7441 );
7442 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
7443 s.select(selections);
7444 });
7445 } else if let Some(selected_text) = selected_text {
7446 self.select_prev_state = Some(SelectNextState {
7447 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
7448 wordwise: false,
7449 done: false,
7450 });
7451 self.select_previous(action, cx)?;
7452 }
7453 }
7454 Ok(())
7455 }
7456
7457 pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
7458 let text_layout_details = &self.text_layout_details(cx);
7459 self.transact(cx, |this, cx| {
7460 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7461 let mut edits = Vec::new();
7462 let mut selection_edit_ranges = Vec::new();
7463 let mut last_toggled_row = None;
7464 let snapshot = this.buffer.read(cx).read(cx);
7465 let empty_str: Arc<str> = "".into();
7466 let mut suffixes_inserted = Vec::new();
7467
7468 fn comment_prefix_range(
7469 snapshot: &MultiBufferSnapshot,
7470 row: MultiBufferRow,
7471 comment_prefix: &str,
7472 comment_prefix_whitespace: &str,
7473 ) -> Range<Point> {
7474 let start = Point::new(row.0, snapshot.indent_size_for_line(row).len);
7475
7476 let mut line_bytes = snapshot
7477 .bytes_in_range(start..snapshot.max_point())
7478 .flatten()
7479 .copied();
7480
7481 // If this line currently begins with the line comment prefix, then record
7482 // the range containing the prefix.
7483 if line_bytes
7484 .by_ref()
7485 .take(comment_prefix.len())
7486 .eq(comment_prefix.bytes())
7487 {
7488 // Include any whitespace that matches the comment prefix.
7489 let matching_whitespace_len = line_bytes
7490 .zip(comment_prefix_whitespace.bytes())
7491 .take_while(|(a, b)| a == b)
7492 .count() as u32;
7493 let end = Point::new(
7494 start.row,
7495 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
7496 );
7497 start..end
7498 } else {
7499 start..start
7500 }
7501 }
7502
7503 fn comment_suffix_range(
7504 snapshot: &MultiBufferSnapshot,
7505 row: MultiBufferRow,
7506 comment_suffix: &str,
7507 comment_suffix_has_leading_space: bool,
7508 ) -> Range<Point> {
7509 let end = Point::new(row.0, snapshot.line_len(row));
7510 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
7511
7512 let mut line_end_bytes = snapshot
7513 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
7514 .flatten()
7515 .copied();
7516
7517 let leading_space_len = if suffix_start_column > 0
7518 && line_end_bytes.next() == Some(b' ')
7519 && comment_suffix_has_leading_space
7520 {
7521 1
7522 } else {
7523 0
7524 };
7525
7526 // If this line currently begins with the line comment prefix, then record
7527 // the range containing the prefix.
7528 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
7529 let start = Point::new(end.row, suffix_start_column - leading_space_len);
7530 start..end
7531 } else {
7532 end..end
7533 }
7534 }
7535
7536 // TODO: Handle selections that cross excerpts
7537 for selection in &mut selections {
7538 let start_column = snapshot
7539 .indent_size_for_line(MultiBufferRow(selection.start.row))
7540 .len;
7541 let language = if let Some(language) =
7542 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
7543 {
7544 language
7545 } else {
7546 continue;
7547 };
7548
7549 selection_edit_ranges.clear();
7550
7551 // If multiple selections contain a given row, avoid processing that
7552 // row more than once.
7553 let mut start_row = MultiBufferRow(selection.start.row);
7554 if last_toggled_row == Some(start_row) {
7555 start_row = start_row.next_row();
7556 }
7557 let end_row =
7558 if selection.end.row > selection.start.row && selection.end.column == 0 {
7559 MultiBufferRow(selection.end.row - 1)
7560 } else {
7561 MultiBufferRow(selection.end.row)
7562 };
7563 last_toggled_row = Some(end_row);
7564
7565 if start_row > end_row {
7566 continue;
7567 }
7568
7569 // If the language has line comments, toggle those.
7570 let full_comment_prefixes = language.line_comment_prefixes();
7571 if !full_comment_prefixes.is_empty() {
7572 let first_prefix = full_comment_prefixes
7573 .first()
7574 .expect("prefixes is non-empty");
7575 let prefix_trimmed_lengths = full_comment_prefixes
7576 .iter()
7577 .map(|p| p.trim_end_matches(' ').len())
7578 .collect::<SmallVec<[usize; 4]>>();
7579
7580 let mut all_selection_lines_are_comments = true;
7581
7582 for row in start_row.0..=end_row.0 {
7583 let row = MultiBufferRow(row);
7584 if start_row < end_row && snapshot.is_line_blank(row) {
7585 continue;
7586 }
7587
7588 let prefix_range = full_comment_prefixes
7589 .iter()
7590 .zip(prefix_trimmed_lengths.iter().copied())
7591 .map(|(prefix, trimmed_prefix_len)| {
7592 comment_prefix_range(
7593 snapshot.deref(),
7594 row,
7595 &prefix[..trimmed_prefix_len],
7596 &prefix[trimmed_prefix_len..],
7597 )
7598 })
7599 .max_by_key(|range| range.end.column - range.start.column)
7600 .expect("prefixes is non-empty");
7601
7602 if prefix_range.is_empty() {
7603 all_selection_lines_are_comments = false;
7604 }
7605
7606 selection_edit_ranges.push(prefix_range);
7607 }
7608
7609 if all_selection_lines_are_comments {
7610 edits.extend(
7611 selection_edit_ranges
7612 .iter()
7613 .cloned()
7614 .map(|range| (range, empty_str.clone())),
7615 );
7616 } else {
7617 let min_column = selection_edit_ranges
7618 .iter()
7619 .map(|range| range.start.column)
7620 .min()
7621 .unwrap_or(0);
7622 edits.extend(selection_edit_ranges.iter().map(|range| {
7623 let position = Point::new(range.start.row, min_column);
7624 (position..position, first_prefix.clone())
7625 }));
7626 }
7627 } else if let Some((full_comment_prefix, comment_suffix)) =
7628 language.block_comment_delimiters()
7629 {
7630 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
7631 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
7632 let prefix_range = comment_prefix_range(
7633 snapshot.deref(),
7634 start_row,
7635 comment_prefix,
7636 comment_prefix_whitespace,
7637 );
7638 let suffix_range = comment_suffix_range(
7639 snapshot.deref(),
7640 end_row,
7641 comment_suffix.trim_start_matches(' '),
7642 comment_suffix.starts_with(' '),
7643 );
7644
7645 if prefix_range.is_empty() || suffix_range.is_empty() {
7646 edits.push((
7647 prefix_range.start..prefix_range.start,
7648 full_comment_prefix.clone(),
7649 ));
7650 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
7651 suffixes_inserted.push((end_row, comment_suffix.len()));
7652 } else {
7653 edits.push((prefix_range, empty_str.clone()));
7654 edits.push((suffix_range, empty_str.clone()));
7655 }
7656 } else {
7657 continue;
7658 }
7659 }
7660
7661 drop(snapshot);
7662 this.buffer.update(cx, |buffer, cx| {
7663 buffer.edit(edits, None, cx);
7664 });
7665
7666 // Adjust selections so that they end before any comment suffixes that
7667 // were inserted.
7668 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
7669 let mut selections = this.selections.all::<Point>(cx);
7670 let snapshot = this.buffer.read(cx).read(cx);
7671 for selection in &mut selections {
7672 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
7673 match row.cmp(&MultiBufferRow(selection.end.row)) {
7674 Ordering::Less => {
7675 suffixes_inserted.next();
7676 continue;
7677 }
7678 Ordering::Greater => break,
7679 Ordering::Equal => {
7680 if selection.end.column == snapshot.line_len(row) {
7681 if selection.is_empty() {
7682 selection.start.column -= suffix_len as u32;
7683 }
7684 selection.end.column -= suffix_len as u32;
7685 }
7686 break;
7687 }
7688 }
7689 }
7690 }
7691
7692 drop(snapshot);
7693 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
7694
7695 let selections = this.selections.all::<Point>(cx);
7696 let selections_on_single_row = selections.windows(2).all(|selections| {
7697 selections[0].start.row == selections[1].start.row
7698 && selections[0].end.row == selections[1].end.row
7699 && selections[0].start.row == selections[0].end.row
7700 });
7701 let selections_selecting = selections
7702 .iter()
7703 .any(|selection| selection.start != selection.end);
7704 let advance_downwards = action.advance_downwards
7705 && selections_on_single_row
7706 && !selections_selecting
7707 && this.mode != EditorMode::SingleLine;
7708
7709 if advance_downwards {
7710 let snapshot = this.buffer.read(cx).snapshot(cx);
7711
7712 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7713 s.move_cursors_with(|display_snapshot, display_point, _| {
7714 let mut point = display_point.to_point(display_snapshot);
7715 point.row += 1;
7716 point = snapshot.clip_point(point, Bias::Left);
7717 let display_point = point.to_display_point(display_snapshot);
7718 let goal = SelectionGoal::HorizontalPosition(
7719 display_snapshot
7720 .x_for_display_point(display_point, &text_layout_details)
7721 .into(),
7722 );
7723 (display_point, goal)
7724 })
7725 });
7726 }
7727 });
7728 }
7729
7730 pub fn select_larger_syntax_node(
7731 &mut self,
7732 _: &SelectLargerSyntaxNode,
7733 cx: &mut ViewContext<Self>,
7734 ) {
7735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7736 let buffer = self.buffer.read(cx).snapshot(cx);
7737 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
7738
7739 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
7740 let mut selected_larger_node = false;
7741 let new_selections = old_selections
7742 .iter()
7743 .map(|selection| {
7744 let old_range = selection.start..selection.end;
7745 let mut new_range = old_range.clone();
7746 while let Some(containing_range) =
7747 buffer.range_for_syntax_ancestor(new_range.clone())
7748 {
7749 new_range = containing_range;
7750 if !display_map.intersects_fold(new_range.start)
7751 && !display_map.intersects_fold(new_range.end)
7752 {
7753 break;
7754 }
7755 }
7756
7757 selected_larger_node |= new_range != old_range;
7758 Selection {
7759 id: selection.id,
7760 start: new_range.start,
7761 end: new_range.end,
7762 goal: SelectionGoal::None,
7763 reversed: selection.reversed,
7764 }
7765 })
7766 .collect::<Vec<_>>();
7767
7768 if selected_larger_node {
7769 stack.push(old_selections);
7770 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7771 s.select(new_selections);
7772 });
7773 }
7774 self.select_larger_syntax_node_stack = stack;
7775 }
7776
7777 pub fn select_smaller_syntax_node(
7778 &mut self,
7779 _: &SelectSmallerSyntaxNode,
7780 cx: &mut ViewContext<Self>,
7781 ) {
7782 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
7783 if let Some(selections) = stack.pop() {
7784 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7785 s.select(selections.to_vec());
7786 });
7787 }
7788 self.select_larger_syntax_node_stack = stack;
7789 }
7790
7791 fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
7792 let project = self.project.clone();
7793 cx.spawn(|this, mut cx| async move {
7794 let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
7795 this.display_map.update(cx, |map, cx| map.snapshot(cx))
7796 }) else {
7797 return;
7798 };
7799
7800 let Some(project) = project else {
7801 return;
7802 };
7803 if project
7804 .update(&mut cx, |this, _| this.is_remote())
7805 .unwrap_or(true)
7806 {
7807 // Do not display any test indicators in remote projects.
7808 return;
7809 }
7810 let new_rows =
7811 cx.background_executor()
7812 .spawn({
7813 let snapshot = display_snapshot.clone();
7814 async move {
7815 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
7816 }
7817 })
7818 .await;
7819 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
7820
7821 this.update(&mut cx, |this, _| {
7822 this.clear_tasks();
7823 for (key, value) in rows {
7824 this.insert_tasks(key, value);
7825 }
7826 })
7827 .ok();
7828 })
7829 }
7830 fn fetch_runnable_ranges(
7831 snapshot: &DisplaySnapshot,
7832 range: Range<Anchor>,
7833 ) -> Vec<language::RunnableRange> {
7834 snapshot.buffer_snapshot.runnable_ranges(range).collect()
7835 }
7836
7837 fn runnable_rows(
7838 project: Model<Project>,
7839 snapshot: DisplaySnapshot,
7840 runnable_ranges: Vec<RunnableRange>,
7841 mut cx: AsyncWindowContext,
7842 ) -> Vec<((BufferId, u32), (usize, RunnableTasks))> {
7843 runnable_ranges
7844 .into_iter()
7845 .filter_map(|mut runnable| {
7846 let (tasks, _) = cx
7847 .update(|cx| {
7848 Self::resolve_runnable(project.clone(), &mut runnable.runnable, cx)
7849 })
7850 .ok()?;
7851 if tasks.is_empty() {
7852 return None;
7853 }
7854
7855 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
7856
7857 let row = snapshot
7858 .buffer_snapshot
7859 .buffer_line_for_row(MultiBufferRow(point.row))?
7860 .1
7861 .start
7862 .row;
7863
7864 Some((
7865 (runnable.buffer_id, row),
7866 (
7867 runnable.run_range.start,
7868 RunnableTasks {
7869 templates: tasks,
7870 column: point.column,
7871 extra_variables: runnable.extra_captures,
7872 },
7873 ),
7874 ))
7875 })
7876 .collect()
7877 }
7878
7879 fn resolve_runnable(
7880 project: Model<Project>,
7881 runnable: &mut Runnable,
7882 cx: &WindowContext<'_>,
7883 ) -> (Vec<(TaskSourceKind, TaskTemplate)>, Option<WorktreeId>) {
7884 let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
7885 let worktree_id = project
7886 .buffer_for_id(runnable.buffer)
7887 .and_then(|buffer| buffer.read(cx).file())
7888 .map(|file| WorktreeId::from_usize(file.worktree_id()));
7889
7890 (project.task_inventory().clone(), worktree_id)
7891 });
7892
7893 let inventory = inventory.read(cx);
7894 let tags = mem::take(&mut runnable.tags);
7895 let mut tags: Vec<_> = tags
7896 .into_iter()
7897 .flat_map(|tag| {
7898 let tag = tag.0.clone();
7899 inventory
7900 .list_tasks(Some(runnable.language.clone()), worktree_id)
7901 .into_iter()
7902 .filter(move |(_, template)| {
7903 template.tags.iter().any(|source_tag| source_tag == &tag)
7904 })
7905 })
7906 .sorted_by_key(|(kind, _)| kind.to_owned())
7907 .collect();
7908 if let Some((leading_tag_source, _)) = tags.first() {
7909 // Strongest source wins; if we have worktree tag binding, prefer that to
7910 // global and language bindings;
7911 // if we have a global binding, prefer that to language binding.
7912 let first_mismatch = tags
7913 .iter()
7914 .position(|(tag_source, _)| tag_source != leading_tag_source);
7915 if let Some(index) = first_mismatch {
7916 tags.truncate(index);
7917 }
7918 }
7919
7920 (tags, worktree_id)
7921 }
7922
7923 pub fn move_to_enclosing_bracket(
7924 &mut self,
7925 _: &MoveToEnclosingBracket,
7926 cx: &mut ViewContext<Self>,
7927 ) {
7928 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7929 s.move_offsets_with(|snapshot, selection| {
7930 let Some(enclosing_bracket_ranges) =
7931 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
7932 else {
7933 return;
7934 };
7935
7936 let mut best_length = usize::MAX;
7937 let mut best_inside = false;
7938 let mut best_in_bracket_range = false;
7939 let mut best_destination = None;
7940 for (open, close) in enclosing_bracket_ranges {
7941 let close = close.to_inclusive();
7942 let length = close.end() - open.start;
7943 let inside = selection.start >= open.end && selection.end <= *close.start();
7944 let in_bracket_range = open.to_inclusive().contains(&selection.head())
7945 || close.contains(&selection.head());
7946
7947 // If best is next to a bracket and current isn't, skip
7948 if !in_bracket_range && best_in_bracket_range {
7949 continue;
7950 }
7951
7952 // Prefer smaller lengths unless best is inside and current isn't
7953 if length > best_length && (best_inside || !inside) {
7954 continue;
7955 }
7956
7957 best_length = length;
7958 best_inside = inside;
7959 best_in_bracket_range = in_bracket_range;
7960 best_destination = Some(
7961 if close.contains(&selection.start) && close.contains(&selection.end) {
7962 if inside {
7963 open.end
7964 } else {
7965 open.start
7966 }
7967 } else {
7968 if inside {
7969 *close.start()
7970 } else {
7971 *close.end()
7972 }
7973 },
7974 );
7975 }
7976
7977 if let Some(destination) = best_destination {
7978 selection.collapse_to(destination, SelectionGoal::None);
7979 }
7980 })
7981 });
7982 }
7983
7984 pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext<Self>) {
7985 self.end_selection(cx);
7986 self.selection_history.mode = SelectionHistoryMode::Undoing;
7987 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
7988 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
7989 self.select_next_state = entry.select_next_state;
7990 self.select_prev_state = entry.select_prev_state;
7991 self.add_selections_state = entry.add_selections_state;
7992 self.request_autoscroll(Autoscroll::newest(), cx);
7993 }
7994 self.selection_history.mode = SelectionHistoryMode::Normal;
7995 }
7996
7997 pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext<Self>) {
7998 self.end_selection(cx);
7999 self.selection_history.mode = SelectionHistoryMode::Redoing;
8000 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
8001 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
8002 self.select_next_state = entry.select_next_state;
8003 self.select_prev_state = entry.select_prev_state;
8004 self.add_selections_state = entry.add_selections_state;
8005 self.request_autoscroll(Autoscroll::newest(), cx);
8006 }
8007 self.selection_history.mode = SelectionHistoryMode::Normal;
8008 }
8009
8010 pub fn expand_excerpts(&mut self, action: &ExpandExcerpts, cx: &mut ViewContext<Self>) {
8011 let selections = self.selections.disjoint_anchors();
8012
8013 let lines = if action.lines == 0 { 3 } else { action.lines };
8014
8015 self.buffer.update(cx, |buffer, cx| {
8016 buffer.expand_excerpts(
8017 selections
8018 .into_iter()
8019 .map(|selection| selection.head().excerpt_id)
8020 .dedup(),
8021 lines,
8022 cx,
8023 )
8024 })
8025 }
8026
8027 pub fn expand_excerpt(&mut self, excerpt: ExcerptId, cx: &mut ViewContext<Self>) {
8028 self.buffer
8029 .update(cx, |buffer, cx| buffer.expand_excerpts([excerpt], 3, cx))
8030 }
8031
8032 fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
8033 self.go_to_diagnostic_impl(Direction::Next, cx)
8034 }
8035
8036 fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
8037 self.go_to_diagnostic_impl(Direction::Prev, cx)
8038 }
8039
8040 pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
8041 let buffer = self.buffer.read(cx).snapshot(cx);
8042 let selection = self.selections.newest::<usize>(cx);
8043
8044 // If there is an active Diagnostic Popover jump to its diagnostic instead.
8045 if direction == Direction::Next {
8046 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
8047 let (group_id, jump_to) = popover.activation_info();
8048 if self.activate_diagnostics(group_id, cx) {
8049 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8050 let mut new_selection = s.newest_anchor().clone();
8051 new_selection.collapse_to(jump_to, SelectionGoal::None);
8052 s.select_anchors(vec![new_selection.clone()]);
8053 });
8054 }
8055 return;
8056 }
8057 }
8058
8059 let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
8060 active_diagnostics
8061 .primary_range
8062 .to_offset(&buffer)
8063 .to_inclusive()
8064 });
8065 let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
8066 if active_primary_range.contains(&selection.head()) {
8067 *active_primary_range.start()
8068 } else {
8069 selection.head()
8070 }
8071 } else {
8072 selection.head()
8073 };
8074 let snapshot = self.snapshot(cx);
8075 loop {
8076 let diagnostics = if direction == Direction::Prev {
8077 buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
8078 } else {
8079 buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
8080 }
8081 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
8082 let group = diagnostics
8083 // relies on diagnostics_in_range to return diagnostics with the same starting range to
8084 // be sorted in a stable way
8085 // skip until we are at current active diagnostic, if it exists
8086 .skip_while(|entry| {
8087 (match direction {
8088 Direction::Prev => entry.range.start >= search_start,
8089 Direction::Next => entry.range.start <= search_start,
8090 }) && self
8091 .active_diagnostics
8092 .as_ref()
8093 .is_some_and(|a| a.group_id != entry.diagnostic.group_id)
8094 })
8095 .find_map(|entry| {
8096 if entry.diagnostic.is_primary
8097 && entry.diagnostic.severity <= DiagnosticSeverity::WARNING
8098 && !entry.range.is_empty()
8099 // if we match with the active diagnostic, skip it
8100 && Some(entry.diagnostic.group_id)
8101 != self.active_diagnostics.as_ref().map(|d| d.group_id)
8102 {
8103 Some((entry.range, entry.diagnostic.group_id))
8104 } else {
8105 None
8106 }
8107 });
8108
8109 if let Some((primary_range, group_id)) = group {
8110 if self.activate_diagnostics(group_id, cx) {
8111 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8112 s.select(vec![Selection {
8113 id: selection.id,
8114 start: primary_range.start,
8115 end: primary_range.start,
8116 reversed: false,
8117 goal: SelectionGoal::None,
8118 }]);
8119 });
8120 }
8121 break;
8122 } else {
8123 // Cycle around to the start of the buffer, potentially moving back to the start of
8124 // the currently active diagnostic.
8125 active_primary_range.take();
8126 if direction == Direction::Prev {
8127 if search_start == buffer.len() {
8128 break;
8129 } else {
8130 search_start = buffer.len();
8131 }
8132 } else if search_start == 0 {
8133 break;
8134 } else {
8135 search_start = 0;
8136 }
8137 }
8138 }
8139 }
8140
8141 fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
8142 let snapshot = self
8143 .display_map
8144 .update(cx, |display_map, cx| display_map.snapshot(cx));
8145 let selection = self.selections.newest::<Point>(cx);
8146
8147 if !self.seek_in_direction(
8148 &snapshot,
8149 selection.head(),
8150 false,
8151 snapshot.buffer_snapshot.git_diff_hunks_in_range(
8152 MultiBufferRow(selection.head().row + 1)..MultiBufferRow::MAX,
8153 ),
8154 cx,
8155 ) {
8156 let wrapped_point = Point::zero();
8157 self.seek_in_direction(
8158 &snapshot,
8159 wrapped_point,
8160 true,
8161 snapshot.buffer_snapshot.git_diff_hunks_in_range(
8162 MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX,
8163 ),
8164 cx,
8165 );
8166 }
8167 }
8168
8169 fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
8170 let snapshot = self
8171 .display_map
8172 .update(cx, |display_map, cx| display_map.snapshot(cx));
8173 let selection = self.selections.newest::<Point>(cx);
8174
8175 if !self.seek_in_direction(
8176 &snapshot,
8177 selection.head(),
8178 false,
8179 snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
8180 MultiBufferRow(0)..MultiBufferRow(selection.head().row),
8181 ),
8182 cx,
8183 ) {
8184 let wrapped_point = snapshot.buffer_snapshot.max_point();
8185 self.seek_in_direction(
8186 &snapshot,
8187 wrapped_point,
8188 true,
8189 snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
8190 MultiBufferRow(0)..MultiBufferRow(wrapped_point.row),
8191 ),
8192 cx,
8193 );
8194 }
8195 }
8196
8197 fn seek_in_direction(
8198 &mut self,
8199 snapshot: &DisplaySnapshot,
8200 initial_point: Point,
8201 is_wrapped: bool,
8202 hunks: impl Iterator<Item = DiffHunk<MultiBufferRow>>,
8203 cx: &mut ViewContext<Editor>,
8204 ) -> bool {
8205 let display_point = initial_point.to_display_point(snapshot);
8206 let mut hunks = hunks
8207 .map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
8208 .filter(|hunk| {
8209 if is_wrapped {
8210 true
8211 } else {
8212 !hunk.contains_display_row(display_point.row())
8213 }
8214 })
8215 .dedup();
8216
8217 if let Some(hunk) = hunks.next() {
8218 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8219 let row = hunk.start_display_row();
8220 let point = DisplayPoint::new(row, 0);
8221 s.select_display_ranges([point..point]);
8222 });
8223
8224 true
8225 } else {
8226 false
8227 }
8228 }
8229
8230 pub fn go_to_definition(
8231 &mut self,
8232 _: &GoToDefinition,
8233 cx: &mut ViewContext<Self>,
8234 ) -> Task<Result<bool>> {
8235 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx)
8236 }
8237
8238 pub fn go_to_implementation(
8239 &mut self,
8240 _: &GoToImplementation,
8241 cx: &mut ViewContext<Self>,
8242 ) -> Task<Result<bool>> {
8243 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx)
8244 }
8245
8246 pub fn go_to_implementation_split(
8247 &mut self,
8248 _: &GoToImplementationSplit,
8249 cx: &mut ViewContext<Self>,
8250 ) -> Task<Result<bool>> {
8251 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx)
8252 }
8253
8254 pub fn go_to_type_definition(
8255 &mut self,
8256 _: &GoToTypeDefinition,
8257 cx: &mut ViewContext<Self>,
8258 ) -> Task<Result<bool>> {
8259 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx)
8260 }
8261
8262 pub fn go_to_definition_split(
8263 &mut self,
8264 _: &GoToDefinitionSplit,
8265 cx: &mut ViewContext<Self>,
8266 ) -> Task<Result<bool>> {
8267 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx)
8268 }
8269
8270 pub fn go_to_type_definition_split(
8271 &mut self,
8272 _: &GoToTypeDefinitionSplit,
8273 cx: &mut ViewContext<Self>,
8274 ) -> Task<Result<bool>> {
8275 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx)
8276 }
8277
8278 fn go_to_definition_of_kind(
8279 &mut self,
8280 kind: GotoDefinitionKind,
8281 split: bool,
8282 cx: &mut ViewContext<Self>,
8283 ) -> Task<Result<bool>> {
8284 let Some(workspace) = self.workspace() else {
8285 return Task::ready(Ok(false));
8286 };
8287 let buffer = self.buffer.read(cx);
8288 let head = self.selections.newest::<usize>(cx).head();
8289 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
8290 text_anchor
8291 } else {
8292 return Task::ready(Ok(false));
8293 };
8294
8295 let project = workspace.read(cx).project().clone();
8296 let definitions = project.update(cx, |project, cx| match kind {
8297 GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
8298 GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
8299 GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
8300 });
8301
8302 cx.spawn(|editor, mut cx| async move {
8303 let definitions = definitions.await?;
8304 let navigated = editor
8305 .update(&mut cx, |editor, cx| {
8306 editor.navigate_to_hover_links(
8307 Some(kind),
8308 definitions
8309 .into_iter()
8310 .filter(|location| {
8311 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
8312 })
8313 .map(HoverLink::Text)
8314 .collect::<Vec<_>>(),
8315 split,
8316 cx,
8317 )
8318 })?
8319 .await?;
8320 anyhow::Ok(navigated)
8321 })
8322 }
8323
8324 pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
8325 let position = self.selections.newest_anchor().head();
8326 let Some((buffer, buffer_position)) =
8327 self.buffer.read(cx).text_anchor_for_position(position, cx)
8328 else {
8329 return;
8330 };
8331
8332 cx.spawn(|editor, mut cx| async move {
8333 if let Some((_, url)) = find_url(&buffer, buffer_position, cx.clone()) {
8334 editor.update(&mut cx, |_, cx| {
8335 cx.open_url(&url);
8336 })
8337 } else {
8338 Ok(())
8339 }
8340 })
8341 .detach();
8342 }
8343
8344 pub(crate) fn navigate_to_hover_links(
8345 &mut self,
8346 kind: Option<GotoDefinitionKind>,
8347 mut definitions: Vec<HoverLink>,
8348 split: bool,
8349 cx: &mut ViewContext<Editor>,
8350 ) -> Task<Result<bool>> {
8351 // If there is one definition, just open it directly
8352 if definitions.len() == 1 {
8353 let definition = definitions.pop().unwrap();
8354 let target_task = match definition {
8355 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
8356 HoverLink::InlayHint(lsp_location, server_id) => {
8357 self.compute_target_location(lsp_location, server_id, cx)
8358 }
8359 HoverLink::Url(url) => {
8360 cx.open_url(&url);
8361 Task::ready(Ok(None))
8362 }
8363 };
8364 cx.spawn(|editor, mut cx| async move {
8365 let target = target_task.await.context("target resolution task")?;
8366 if let Some(target) = target {
8367 editor.update(&mut cx, |editor, cx| {
8368 let Some(workspace) = editor.workspace() else {
8369 return false;
8370 };
8371 let pane = workspace.read(cx).active_pane().clone();
8372
8373 let range = target.range.to_offset(target.buffer.read(cx));
8374 let range = editor.range_for_match(&range);
8375
8376 /// If select range has more than one line, we
8377 /// just point the cursor to range.start.
8378 fn check_multiline_range(
8379 buffer: &Buffer,
8380 range: Range<usize>,
8381 ) -> Range<usize> {
8382 if buffer.offset_to_point(range.start).row
8383 == buffer.offset_to_point(range.end).row
8384 {
8385 range
8386 } else {
8387 range.start..range.start
8388 }
8389 }
8390
8391 if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
8392 let buffer = target.buffer.read(cx);
8393 let range = check_multiline_range(buffer, range);
8394 editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
8395 s.select_ranges([range]);
8396 });
8397 } else {
8398 cx.window_context().defer(move |cx| {
8399 let target_editor: View<Self> =
8400 workspace.update(cx, |workspace, cx| {
8401 let pane = if split {
8402 workspace.adjacent_pane(cx)
8403 } else {
8404 workspace.active_pane().clone()
8405 };
8406
8407 workspace.open_project_item(pane, target.buffer.clone(), cx)
8408 });
8409 target_editor.update(cx, |target_editor, cx| {
8410 // When selecting a definition in a different buffer, disable the nav history
8411 // to avoid creating a history entry at the previous cursor location.
8412 pane.update(cx, |pane, _| pane.disable_history());
8413 let buffer = target.buffer.read(cx);
8414 let range = check_multiline_range(buffer, range);
8415 target_editor.change_selections(
8416 Some(Autoscroll::focused()),
8417 cx,
8418 |s| {
8419 s.select_ranges([range]);
8420 },
8421 );
8422 pane.update(cx, |pane, _| pane.enable_history());
8423 });
8424 });
8425 }
8426 true
8427 })
8428 } else {
8429 Ok(false)
8430 }
8431 })
8432 } else if !definitions.is_empty() {
8433 let replica_id = self.replica_id(cx);
8434 cx.spawn(|editor, mut cx| async move {
8435 let (title, location_tasks, workspace) = editor
8436 .update(&mut cx, |editor, cx| {
8437 let tab_kind = match kind {
8438 Some(GotoDefinitionKind::Implementation) => "Implementations",
8439 _ => "Definitions",
8440 };
8441 let title = definitions
8442 .iter()
8443 .find_map(|definition| match definition {
8444 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
8445 let buffer = origin.buffer.read(cx);
8446 format!(
8447 "{} for {}",
8448 tab_kind,
8449 buffer
8450 .text_for_range(origin.range.clone())
8451 .collect::<String>()
8452 )
8453 }),
8454 HoverLink::InlayHint(_, _) => None,
8455 HoverLink::Url(_) => None,
8456 })
8457 .unwrap_or(tab_kind.to_string());
8458 let location_tasks = definitions
8459 .into_iter()
8460 .map(|definition| match definition {
8461 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
8462 HoverLink::InlayHint(lsp_location, server_id) => {
8463 editor.compute_target_location(lsp_location, server_id, cx)
8464 }
8465 HoverLink::Url(_) => Task::ready(Ok(None)),
8466 })
8467 .collect::<Vec<_>>();
8468 (title, location_tasks, editor.workspace().clone())
8469 })
8470 .context("location tasks preparation")?;
8471
8472 let locations = futures::future::join_all(location_tasks)
8473 .await
8474 .into_iter()
8475 .filter_map(|location| location.transpose())
8476 .collect::<Result<_>>()
8477 .context("location tasks")?;
8478
8479 let Some(workspace) = workspace else {
8480 return Ok(false);
8481 };
8482 let opened = workspace
8483 .update(&mut cx, |workspace, cx| {
8484 Self::open_locations_in_multibuffer(
8485 workspace, locations, replica_id, title, split, cx,
8486 )
8487 })
8488 .ok();
8489
8490 anyhow::Ok(opened.is_some())
8491 })
8492 } else {
8493 Task::ready(Ok(false))
8494 }
8495 }
8496
8497 fn compute_target_location(
8498 &self,
8499 lsp_location: lsp::Location,
8500 server_id: LanguageServerId,
8501 cx: &mut ViewContext<Editor>,
8502 ) -> Task<anyhow::Result<Option<Location>>> {
8503 let Some(project) = self.project.clone() else {
8504 return Task::Ready(Some(Ok(None)));
8505 };
8506
8507 cx.spawn(move |editor, mut cx| async move {
8508 let location_task = editor.update(&mut cx, |editor, cx| {
8509 project.update(cx, |project, cx| {
8510 let language_server_name =
8511 editor.buffer.read(cx).as_singleton().and_then(|buffer| {
8512 project
8513 .language_server_for_buffer(buffer.read(cx), server_id, cx)
8514 .map(|(lsp_adapter, _)| lsp_adapter.name.clone())
8515 });
8516 language_server_name.map(|language_server_name| {
8517 project.open_local_buffer_via_lsp(
8518 lsp_location.uri.clone(),
8519 server_id,
8520 language_server_name,
8521 cx,
8522 )
8523 })
8524 })
8525 })?;
8526 let location = match location_task {
8527 Some(task) => Some({
8528 let target_buffer_handle = task.await.context("open local buffer")?;
8529 let range = target_buffer_handle.update(&mut cx, |target_buffer, _| {
8530 let target_start = target_buffer
8531 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
8532 let target_end = target_buffer
8533 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
8534 target_buffer.anchor_after(target_start)
8535 ..target_buffer.anchor_before(target_end)
8536 })?;
8537 Location {
8538 buffer: target_buffer_handle,
8539 range,
8540 }
8541 }),
8542 None => None,
8543 };
8544 Ok(location)
8545 })
8546 }
8547
8548 pub fn find_all_references(
8549 &mut self,
8550 _: &FindAllReferences,
8551 cx: &mut ViewContext<Self>,
8552 ) -> Option<Task<Result<()>>> {
8553 let multi_buffer = self.buffer.read(cx);
8554 let selection = self.selections.newest::<usize>(cx);
8555 let head = selection.head();
8556
8557 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
8558 let head_anchor = multi_buffer_snapshot.anchor_at(
8559 head,
8560 if head < selection.tail() {
8561 Bias::Right
8562 } else {
8563 Bias::Left
8564 },
8565 );
8566
8567 match self
8568 .find_all_references_task_sources
8569 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
8570 {
8571 Ok(_) => {
8572 log::info!(
8573 "Ignoring repeated FindAllReferences invocation with the position of already running task"
8574 );
8575 return None;
8576 }
8577 Err(i) => {
8578 self.find_all_references_task_sources.insert(i, head_anchor);
8579 }
8580 }
8581
8582 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
8583 let replica_id = self.replica_id(cx);
8584 let workspace = self.workspace()?;
8585 let project = workspace.read(cx).project().clone();
8586 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
8587 Some(cx.spawn(|editor, mut cx| async move {
8588 let _cleanup = defer({
8589 let mut cx = cx.clone();
8590 move || {
8591 let _ = editor.update(&mut cx, |editor, _| {
8592 if let Ok(i) =
8593 editor
8594 .find_all_references_task_sources
8595 .binary_search_by(|anchor| {
8596 anchor.cmp(&head_anchor, &multi_buffer_snapshot)
8597 })
8598 {
8599 editor.find_all_references_task_sources.remove(i);
8600 }
8601 });
8602 }
8603 });
8604
8605 let locations = references.await?;
8606 if locations.is_empty() {
8607 return anyhow::Ok(());
8608 }
8609
8610 workspace.update(&mut cx, |workspace, cx| {
8611 let title = locations
8612 .first()
8613 .as_ref()
8614 .map(|location| {
8615 let buffer = location.buffer.read(cx);
8616 format!(
8617 "References to `{}`",
8618 buffer
8619 .text_for_range(location.range.clone())
8620 .collect::<String>()
8621 )
8622 })
8623 .unwrap();
8624 Self::open_locations_in_multibuffer(
8625 workspace, locations, replica_id, title, false, cx,
8626 );
8627 })
8628 }))
8629 }
8630
8631 /// Opens a multibuffer with the given project locations in it
8632 pub fn open_locations_in_multibuffer(
8633 workspace: &mut Workspace,
8634 mut locations: Vec<Location>,
8635 replica_id: ReplicaId,
8636 title: String,
8637 split: bool,
8638 cx: &mut ViewContext<Workspace>,
8639 ) {
8640 // If there are multiple definitions, open them in a multibuffer
8641 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
8642 let mut locations = locations.into_iter().peekable();
8643 let mut ranges_to_highlight = Vec::new();
8644 let capability = workspace.project().read(cx).capability();
8645
8646 let excerpt_buffer = cx.new_model(|cx| {
8647 let mut multibuffer = MultiBuffer::new(replica_id, capability);
8648 while let Some(location) = locations.next() {
8649 let buffer = location.buffer.read(cx);
8650 let mut ranges_for_buffer = Vec::new();
8651 let range = location.range.to_offset(buffer);
8652 ranges_for_buffer.push(range.clone());
8653
8654 while let Some(next_location) = locations.peek() {
8655 if next_location.buffer == location.buffer {
8656 ranges_for_buffer.push(next_location.range.to_offset(buffer));
8657 locations.next();
8658 } else {
8659 break;
8660 }
8661 }
8662
8663 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
8664 ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
8665 location.buffer.clone(),
8666 ranges_for_buffer,
8667 DEFAULT_MULTIBUFFER_CONTEXT,
8668 cx,
8669 ))
8670 }
8671
8672 multibuffer.with_title(title)
8673 });
8674
8675 let editor = cx.new_view(|cx| {
8676 Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx)
8677 });
8678 editor.update(cx, |editor, cx| {
8679 editor.highlight_background::<Self>(
8680 &ranges_to_highlight,
8681 |theme| theme.editor_highlighted_line_background,
8682 cx,
8683 );
8684 });
8685
8686 let item = Box::new(editor);
8687 let item_id = item.item_id();
8688
8689 if split {
8690 workspace.split_item(SplitDirection::Right, item.clone(), cx);
8691 } else {
8692 let destination_index = workspace.active_pane().update(cx, |pane, cx| {
8693 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
8694 pane.close_current_preview_item(cx)
8695 } else {
8696 None
8697 }
8698 });
8699 workspace.add_item_to_active_pane(item.clone(), destination_index, cx);
8700 }
8701 workspace.active_pane().update(cx, |pane, cx| {
8702 pane.set_preview_item_id(Some(item_id), cx);
8703 });
8704 }
8705
8706 pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
8707 use language::ToOffset as _;
8708
8709 let project = self.project.clone()?;
8710 let selection = self.selections.newest_anchor().clone();
8711 let (cursor_buffer, cursor_buffer_position) = self
8712 .buffer
8713 .read(cx)
8714 .text_anchor_for_position(selection.head(), cx)?;
8715 let (tail_buffer, cursor_buffer_position_end) = self
8716 .buffer
8717 .read(cx)
8718 .text_anchor_for_position(selection.tail(), cx)?;
8719 if tail_buffer != cursor_buffer {
8720 return None;
8721 }
8722
8723 let snapshot = cursor_buffer.read(cx).snapshot();
8724 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
8725 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
8726 let prepare_rename = project.update(cx, |project, cx| {
8727 project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
8728 });
8729 drop(snapshot);
8730
8731 Some(cx.spawn(|this, mut cx| async move {
8732 let rename_range = if let Some(range) = prepare_rename.await? {
8733 Some(range)
8734 } else {
8735 this.update(&mut cx, |this, cx| {
8736 let buffer = this.buffer.read(cx).snapshot(cx);
8737 let mut buffer_highlights = this
8738 .document_highlights_for_position(selection.head(), &buffer)
8739 .filter(|highlight| {
8740 highlight.start.excerpt_id == selection.head().excerpt_id
8741 && highlight.end.excerpt_id == selection.head().excerpt_id
8742 });
8743 buffer_highlights
8744 .next()
8745 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
8746 })?
8747 };
8748 if let Some(rename_range) = rename_range {
8749 this.update(&mut cx, |this, cx| {
8750 let snapshot = cursor_buffer.read(cx).snapshot();
8751 let rename_buffer_range = rename_range.to_offset(&snapshot);
8752 let cursor_offset_in_rename_range =
8753 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
8754 let cursor_offset_in_rename_range_end =
8755 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
8756
8757 this.take_rename(false, cx);
8758 let buffer = this.buffer.read(cx).read(cx);
8759 let cursor_offset = selection.head().to_offset(&buffer);
8760 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
8761 let rename_end = rename_start + rename_buffer_range.len();
8762 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
8763 let mut old_highlight_id = None;
8764 let old_name: Arc<str> = buffer
8765 .chunks(rename_start..rename_end, true)
8766 .map(|chunk| {
8767 if old_highlight_id.is_none() {
8768 old_highlight_id = chunk.syntax_highlight_id;
8769 }
8770 chunk.text
8771 })
8772 .collect::<String>()
8773 .into();
8774
8775 drop(buffer);
8776
8777 // Position the selection in the rename editor so that it matches the current selection.
8778 this.show_local_selections = false;
8779 let rename_editor = cx.new_view(|cx| {
8780 let mut editor = Editor::single_line(cx);
8781 editor.buffer.update(cx, |buffer, cx| {
8782 buffer.edit([(0..0, old_name.clone())], None, cx)
8783 });
8784 let rename_selection_range = match cursor_offset_in_rename_range
8785 .cmp(&cursor_offset_in_rename_range_end)
8786 {
8787 Ordering::Equal => {
8788 editor.select_all(&SelectAll, cx);
8789 return editor;
8790 }
8791 Ordering::Less => {
8792 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
8793 }
8794 Ordering::Greater => {
8795 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
8796 }
8797 };
8798 if rename_selection_range.end > old_name.len() {
8799 editor.select_all(&SelectAll, cx);
8800 } else {
8801 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
8802 s.select_ranges([rename_selection_range]);
8803 });
8804 }
8805 editor
8806 });
8807
8808 let write_highlights =
8809 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
8810 let read_highlights =
8811 this.clear_background_highlights::<DocumentHighlightRead>(cx);
8812 let ranges = write_highlights
8813 .iter()
8814 .flat_map(|(_, ranges)| ranges.iter())
8815 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
8816 .cloned()
8817 .collect();
8818
8819 this.highlight_text::<Rename>(
8820 ranges,
8821 HighlightStyle {
8822 fade_out: Some(0.6),
8823 ..Default::default()
8824 },
8825 cx,
8826 );
8827 let rename_focus_handle = rename_editor.focus_handle(cx);
8828 cx.focus(&rename_focus_handle);
8829 let block_id = this.insert_blocks(
8830 [BlockProperties {
8831 style: BlockStyle::Flex,
8832 position: range.start,
8833 height: 1,
8834 render: Box::new({
8835 let rename_editor = rename_editor.clone();
8836 move |cx: &mut BlockContext| {
8837 let mut text_style = cx.editor_style.text.clone();
8838 if let Some(highlight_style) = old_highlight_id
8839 .and_then(|h| h.style(&cx.editor_style.syntax))
8840 {
8841 text_style = text_style.highlight(highlight_style);
8842 }
8843 div()
8844 .pl(cx.anchor_x)
8845 .child(EditorElement::new(
8846 &rename_editor,
8847 EditorStyle {
8848 background: cx.theme().system().transparent,
8849 local_player: cx.editor_style.local_player,
8850 text: text_style,
8851 scrollbar_width: cx.editor_style.scrollbar_width,
8852 syntax: cx.editor_style.syntax.clone(),
8853 status: cx.editor_style.status.clone(),
8854 inlay_hints_style: HighlightStyle {
8855 color: Some(cx.theme().status().hint),
8856 font_weight: Some(FontWeight::BOLD),
8857 ..HighlightStyle::default()
8858 },
8859 suggestions_style: HighlightStyle {
8860 color: Some(cx.theme().status().predictive),
8861 ..HighlightStyle::default()
8862 },
8863 },
8864 ))
8865 .into_any_element()
8866 }
8867 }),
8868 disposition: BlockDisposition::Below,
8869 }],
8870 Some(Autoscroll::fit()),
8871 cx,
8872 )[0];
8873 this.pending_rename = Some(RenameState {
8874 range,
8875 old_name,
8876 editor: rename_editor,
8877 block_id,
8878 });
8879 })?;
8880 }
8881
8882 Ok(())
8883 }))
8884 }
8885
8886 pub fn confirm_rename(
8887 &mut self,
8888 _: &ConfirmRename,
8889 cx: &mut ViewContext<Self>,
8890 ) -> Option<Task<Result<()>>> {
8891 let rename = self.take_rename(false, cx)?;
8892 let workspace = self.workspace()?;
8893 let (start_buffer, start) = self
8894 .buffer
8895 .read(cx)
8896 .text_anchor_for_position(rename.range.start, cx)?;
8897 let (end_buffer, end) = self
8898 .buffer
8899 .read(cx)
8900 .text_anchor_for_position(rename.range.end, cx)?;
8901 if start_buffer != end_buffer {
8902 return None;
8903 }
8904
8905 let buffer = start_buffer;
8906 let range = start..end;
8907 let old_name = rename.old_name;
8908 let new_name = rename.editor.read(cx).text(cx);
8909
8910 let rename = workspace
8911 .read(cx)
8912 .project()
8913 .clone()
8914 .update(cx, |project, cx| {
8915 project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
8916 });
8917 let workspace = workspace.downgrade();
8918
8919 Some(cx.spawn(|editor, mut cx| async move {
8920 let project_transaction = rename.await?;
8921 Self::open_project_transaction(
8922 &editor,
8923 workspace,
8924 project_transaction,
8925 format!("Rename: {} → {}", old_name, new_name),
8926 cx.clone(),
8927 )
8928 .await?;
8929
8930 editor.update(&mut cx, |editor, cx| {
8931 editor.refresh_document_highlights(cx);
8932 })?;
8933 Ok(())
8934 }))
8935 }
8936
8937 fn take_rename(
8938 &mut self,
8939 moving_cursor: bool,
8940 cx: &mut ViewContext<Self>,
8941 ) -> Option<RenameState> {
8942 let rename = self.pending_rename.take()?;
8943 if rename.editor.focus_handle(cx).is_focused(cx) {
8944 cx.focus(&self.focus_handle);
8945 }
8946
8947 self.remove_blocks(
8948 [rename.block_id].into_iter().collect(),
8949 Some(Autoscroll::fit()),
8950 cx,
8951 );
8952 self.clear_highlights::<Rename>(cx);
8953 self.show_local_selections = true;
8954
8955 if moving_cursor {
8956 let rename_editor = rename.editor.read(cx);
8957 let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
8958
8959 // Update the selection to match the position of the selection inside
8960 // the rename editor.
8961 let snapshot = self.buffer.read(cx).read(cx);
8962 let rename_range = rename.range.to_offset(&snapshot);
8963 let cursor_in_editor = snapshot
8964 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
8965 .min(rename_range.end);
8966 drop(snapshot);
8967
8968 self.change_selections(None, cx, |s| {
8969 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
8970 });
8971 } else {
8972 self.refresh_document_highlights(cx);
8973 }
8974
8975 Some(rename)
8976 }
8977
8978 pub fn pending_rename(&self) -> Option<&RenameState> {
8979 self.pending_rename.as_ref()
8980 }
8981
8982 fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
8983 let project = match &self.project {
8984 Some(project) => project.clone(),
8985 None => return None,
8986 };
8987
8988 Some(self.perform_format(project, FormatTrigger::Manual, cx))
8989 }
8990
8991 fn perform_format(
8992 &mut self,
8993 project: Model<Project>,
8994 trigger: FormatTrigger,
8995 cx: &mut ViewContext<Self>,
8996 ) -> Task<Result<()>> {
8997 let buffer = self.buffer().clone();
8998 let mut buffers = buffer.read(cx).all_buffers();
8999 if trigger == FormatTrigger::Save {
9000 buffers.retain(|buffer| buffer.read(cx).is_dirty());
9001 }
9002
9003 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
9004 let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
9005
9006 cx.spawn(|_, mut cx| async move {
9007 let transaction = futures::select_biased! {
9008 () = timeout => {
9009 log::warn!("timed out waiting for formatting");
9010 None
9011 }
9012 transaction = format.log_err().fuse() => transaction,
9013 };
9014
9015 buffer
9016 .update(&mut cx, |buffer, cx| {
9017 if let Some(transaction) = transaction {
9018 if !buffer.is_singleton() {
9019 buffer.push_transaction(&transaction.0, cx);
9020 }
9021 }
9022
9023 cx.notify();
9024 })
9025 .ok();
9026
9027 Ok(())
9028 })
9029 }
9030
9031 fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
9032 if let Some(project) = self.project.clone() {
9033 self.buffer.update(cx, |multi_buffer, cx| {
9034 project.update(cx, |project, cx| {
9035 project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
9036 });
9037 })
9038 }
9039 }
9040
9041 fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
9042 cx.show_character_palette();
9043 }
9044
9045 fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
9046 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
9047 let buffer = self.buffer.read(cx).snapshot(cx);
9048 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
9049 let is_valid = buffer
9050 .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
9051 .any(|entry| {
9052 entry.diagnostic.is_primary
9053 && !entry.range.is_empty()
9054 && entry.range.start == primary_range_start
9055 && entry.diagnostic.message == active_diagnostics.primary_message
9056 });
9057
9058 if is_valid != active_diagnostics.is_valid {
9059 active_diagnostics.is_valid = is_valid;
9060 let mut new_styles = HashMap::default();
9061 for (block_id, diagnostic) in &active_diagnostics.blocks {
9062 new_styles.insert(
9063 *block_id,
9064 diagnostic_block_renderer(diagnostic.clone(), is_valid),
9065 );
9066 }
9067 self.display_map
9068 .update(cx, |display_map, _| display_map.replace_blocks(new_styles));
9069 }
9070 }
9071 }
9072
9073 fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
9074 self.dismiss_diagnostics(cx);
9075 let snapshot = self.snapshot(cx);
9076 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
9077 let buffer = self.buffer.read(cx).snapshot(cx);
9078
9079 let mut primary_range = None;
9080 let mut primary_message = None;
9081 let mut group_end = Point::zero();
9082 let diagnostic_group = buffer
9083 .diagnostic_group::<MultiBufferPoint>(group_id)
9084 .filter_map(|entry| {
9085 if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
9086 && (entry.range.start.row == entry.range.end.row
9087 || snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
9088 {
9089 return None;
9090 }
9091 if entry.range.end > group_end {
9092 group_end = entry.range.end;
9093 }
9094 if entry.diagnostic.is_primary {
9095 primary_range = Some(entry.range.clone());
9096 primary_message = Some(entry.diagnostic.message.clone());
9097 }
9098 Some(entry)
9099 })
9100 .collect::<Vec<_>>();
9101 let primary_range = primary_range?;
9102 let primary_message = primary_message?;
9103 let primary_range =
9104 buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
9105
9106 let blocks = display_map
9107 .insert_blocks(
9108 diagnostic_group.iter().map(|entry| {
9109 let diagnostic = entry.diagnostic.clone();
9110 let message_height = diagnostic.message.matches('\n').count() as u8 + 1;
9111 BlockProperties {
9112 style: BlockStyle::Fixed,
9113 position: buffer.anchor_after(entry.range.start),
9114 height: message_height,
9115 render: diagnostic_block_renderer(diagnostic, true),
9116 disposition: BlockDisposition::Below,
9117 }
9118 }),
9119 cx,
9120 )
9121 .into_iter()
9122 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
9123 .collect();
9124
9125 Some(ActiveDiagnosticGroup {
9126 primary_range,
9127 primary_message,
9128 group_id,
9129 blocks,
9130 is_valid: true,
9131 })
9132 });
9133 self.active_diagnostics.is_some()
9134 }
9135
9136 fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
9137 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
9138 self.display_map.update(cx, |display_map, cx| {
9139 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
9140 });
9141 cx.notify();
9142 }
9143 }
9144
9145 pub fn set_selections_from_remote(
9146 &mut self,
9147 selections: Vec<Selection<Anchor>>,
9148 pending_selection: Option<Selection<Anchor>>,
9149 cx: &mut ViewContext<Self>,
9150 ) {
9151 let old_cursor_position = self.selections.newest_anchor().head();
9152 self.selections.change_with(cx, |s| {
9153 s.select_anchors(selections);
9154 if let Some(pending_selection) = pending_selection {
9155 s.set_pending(pending_selection, SelectMode::Character);
9156 } else {
9157 s.clear_pending();
9158 }
9159 });
9160 self.selections_did_change(false, &old_cursor_position, cx);
9161 }
9162
9163 fn push_to_selection_history(&mut self) {
9164 self.selection_history.push(SelectionHistoryEntry {
9165 selections: self.selections.disjoint_anchors(),
9166 select_next_state: self.select_next_state.clone(),
9167 select_prev_state: self.select_prev_state.clone(),
9168 add_selections_state: self.add_selections_state.clone(),
9169 });
9170 }
9171
9172 pub fn transact(
9173 &mut self,
9174 cx: &mut ViewContext<Self>,
9175 update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
9176 ) -> Option<TransactionId> {
9177 self.start_transaction_at(Instant::now(), cx);
9178 update(self, cx);
9179 self.end_transaction_at(Instant::now(), cx)
9180 }
9181
9182 fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
9183 self.end_selection(cx);
9184 if let Some(tx_id) = self
9185 .buffer
9186 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
9187 {
9188 self.selection_history
9189 .insert_transaction(tx_id, self.selections.disjoint_anchors());
9190 cx.emit(EditorEvent::TransactionBegun {
9191 transaction_id: tx_id,
9192 })
9193 }
9194 }
9195
9196 fn end_transaction_at(
9197 &mut self,
9198 now: Instant,
9199 cx: &mut ViewContext<Self>,
9200 ) -> Option<TransactionId> {
9201 if let Some(tx_id) = self
9202 .buffer
9203 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
9204 {
9205 if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
9206 *end_selections = Some(self.selections.disjoint_anchors());
9207 } else {
9208 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
9209 }
9210
9211 cx.emit(EditorEvent::Edited);
9212 Some(tx_id)
9213 } else {
9214 None
9215 }
9216 }
9217
9218 pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
9219 let mut fold_ranges = Vec::new();
9220
9221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9222
9223 let selections = self.selections.all_adjusted(cx);
9224 for selection in selections {
9225 let range = selection.range().sorted();
9226 let buffer_start_row = range.start.row;
9227
9228 for row in (0..=range.end.row).rev() {
9229 let fold_range = display_map.foldable_range(MultiBufferRow(row));
9230
9231 if let Some(fold_range) = fold_range {
9232 if fold_range.end.row >= buffer_start_row {
9233 fold_ranges.push(fold_range);
9234 if row <= range.start.row {
9235 break;
9236 }
9237 }
9238 }
9239 }
9240 }
9241
9242 self.fold_ranges(fold_ranges, true, cx);
9243 }
9244
9245 pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
9246 let buffer_row = fold_at.buffer_row;
9247 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9248
9249 if let Some(fold_range) = display_map.foldable_range(buffer_row) {
9250 let autoscroll = self
9251 .selections
9252 .all::<Point>(cx)
9253 .iter()
9254 .any(|selection| fold_range.overlaps(&selection.range()));
9255
9256 self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
9257 }
9258 }
9259
9260 pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
9261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9262 let buffer = &display_map.buffer_snapshot;
9263 let selections = self.selections.all::<Point>(cx);
9264 let ranges = selections
9265 .iter()
9266 .map(|s| {
9267 let range = s.display_range(&display_map).sorted();
9268 let mut start = range.start.to_point(&display_map);
9269 let mut end = range.end.to_point(&display_map);
9270 start.column = 0;
9271 end.column = buffer.line_len(MultiBufferRow(end.row));
9272 start..end
9273 })
9274 .collect::<Vec<_>>();
9275
9276 self.unfold_ranges(ranges, true, true, cx);
9277 }
9278
9279 pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
9280 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9281
9282 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
9283 ..Point::new(
9284 unfold_at.buffer_row.0,
9285 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
9286 );
9287
9288 let autoscroll = self
9289 .selections
9290 .all::<Point>(cx)
9291 .iter()
9292 .any(|selection| selection.range().overlaps(&intersection_range));
9293
9294 self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
9295 }
9296
9297 pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
9298 let selections = self.selections.all::<Point>(cx);
9299 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9300 let line_mode = self.selections.line_mode;
9301 let ranges = selections.into_iter().map(|s| {
9302 if line_mode {
9303 let start = Point::new(s.start.row, 0);
9304 let end = Point::new(
9305 s.end.row,
9306 display_map
9307 .buffer_snapshot
9308 .line_len(MultiBufferRow(s.end.row)),
9309 );
9310 start..end
9311 } else {
9312 s.start..s.end
9313 }
9314 });
9315 self.fold_ranges(ranges, true, cx);
9316 }
9317
9318 pub fn fold_ranges<T: ToOffset + Clone>(
9319 &mut self,
9320 ranges: impl IntoIterator<Item = Range<T>>,
9321 auto_scroll: bool,
9322 cx: &mut ViewContext<Self>,
9323 ) {
9324 let mut fold_ranges = Vec::new();
9325 let mut buffers_affected = HashMap::default();
9326 let multi_buffer = self.buffer().read(cx);
9327 for range in ranges {
9328 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
9329 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
9330 };
9331 fold_ranges.push(range);
9332 }
9333
9334 let mut ranges = fold_ranges.into_iter().peekable();
9335 if ranges.peek().is_some() {
9336 self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
9337
9338 if auto_scroll {
9339 self.request_autoscroll(Autoscroll::fit(), cx);
9340 }
9341
9342 for buffer in buffers_affected.into_values() {
9343 self.sync_expanded_diff_hunks(buffer, cx);
9344 }
9345
9346 cx.notify();
9347
9348 if let Some(active_diagnostics) = self.active_diagnostics.take() {
9349 // Clear diagnostics block when folding a range that contains it.
9350 let snapshot = self.snapshot(cx);
9351 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
9352 drop(snapshot);
9353 self.active_diagnostics = Some(active_diagnostics);
9354 self.dismiss_diagnostics(cx);
9355 } else {
9356 self.active_diagnostics = Some(active_diagnostics);
9357 }
9358 }
9359
9360 self.scrollbar_marker_state.dirty = true;
9361 }
9362 }
9363
9364 pub fn unfold_ranges<T: ToOffset + Clone>(
9365 &mut self,
9366 ranges: impl IntoIterator<Item = Range<T>>,
9367 inclusive: bool,
9368 auto_scroll: bool,
9369 cx: &mut ViewContext<Self>,
9370 ) {
9371 let mut unfold_ranges = Vec::new();
9372 let mut buffers_affected = HashMap::default();
9373 let multi_buffer = self.buffer().read(cx);
9374 for range in ranges {
9375 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
9376 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
9377 };
9378 unfold_ranges.push(range);
9379 }
9380
9381 let mut ranges = unfold_ranges.into_iter().peekable();
9382 if ranges.peek().is_some() {
9383 self.display_map
9384 .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
9385 if auto_scroll {
9386 self.request_autoscroll(Autoscroll::fit(), cx);
9387 }
9388
9389 for buffer in buffers_affected.into_values() {
9390 self.sync_expanded_diff_hunks(buffer, cx);
9391 }
9392
9393 cx.notify();
9394 self.scrollbar_marker_state.dirty = true;
9395 }
9396 }
9397
9398 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
9399 if hovered != self.gutter_hovered {
9400 self.gutter_hovered = hovered;
9401 cx.notify();
9402 }
9403 }
9404
9405 pub fn insert_blocks(
9406 &mut self,
9407 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
9408 autoscroll: Option<Autoscroll>,
9409 cx: &mut ViewContext<Self>,
9410 ) -> Vec<BlockId> {
9411 let blocks = self
9412 .display_map
9413 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
9414 if let Some(autoscroll) = autoscroll {
9415 self.request_autoscroll(autoscroll, cx);
9416 }
9417 blocks
9418 }
9419
9420 pub fn replace_blocks(
9421 &mut self,
9422 blocks: HashMap<BlockId, RenderBlock>,
9423 autoscroll: Option<Autoscroll>,
9424 cx: &mut ViewContext<Self>,
9425 ) {
9426 self.display_map
9427 .update(cx, |display_map, _| display_map.replace_blocks(blocks));
9428 if let Some(autoscroll) = autoscroll {
9429 self.request_autoscroll(autoscroll, cx);
9430 }
9431 }
9432
9433 pub fn remove_blocks(
9434 &mut self,
9435 block_ids: HashSet<BlockId>,
9436 autoscroll: Option<Autoscroll>,
9437 cx: &mut ViewContext<Self>,
9438 ) {
9439 self.display_map.update(cx, |display_map, cx| {
9440 display_map.remove_blocks(block_ids, cx)
9441 });
9442 if let Some(autoscroll) = autoscroll {
9443 self.request_autoscroll(autoscroll, cx);
9444 }
9445 }
9446
9447 pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
9448 self.display_map
9449 .update(cx, |map, cx| map.snapshot(cx))
9450 .longest_row()
9451 }
9452
9453 pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
9454 self.display_map
9455 .update(cx, |map, cx| map.snapshot(cx))
9456 .max_point()
9457 }
9458
9459 pub fn text(&self, cx: &AppContext) -> String {
9460 self.buffer.read(cx).read(cx).text()
9461 }
9462
9463 pub fn text_option(&self, cx: &AppContext) -> Option<String> {
9464 let text = self.text(cx);
9465 let text = text.trim();
9466
9467 if text.is_empty() {
9468 return None;
9469 }
9470
9471 Some(text.to_string())
9472 }
9473
9474 pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
9475 self.transact(cx, |this, cx| {
9476 this.buffer
9477 .read(cx)
9478 .as_singleton()
9479 .expect("you can only call set_text on editors for singleton buffers")
9480 .update(cx, |buffer, cx| buffer.set_text(text, cx));
9481 });
9482 }
9483
9484 pub fn display_text(&self, cx: &mut AppContext) -> String {
9485 self.display_map
9486 .update(cx, |map, cx| map.snapshot(cx))
9487 .text()
9488 }
9489
9490 pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
9491 let mut wrap_guides = smallvec::smallvec![];
9492
9493 if self.show_wrap_guides == Some(false) {
9494 return wrap_guides;
9495 }
9496
9497 let settings = self.buffer.read(cx).settings_at(0, cx);
9498 if settings.show_wrap_guides {
9499 if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
9500 wrap_guides.push((soft_wrap as usize, true));
9501 }
9502 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
9503 }
9504
9505 wrap_guides
9506 }
9507
9508 pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
9509 let settings = self.buffer.read(cx).settings_at(0, cx);
9510 let mode = self
9511 .soft_wrap_mode_override
9512 .unwrap_or_else(|| settings.soft_wrap);
9513 match mode {
9514 language_settings::SoftWrap::None => SoftWrap::None,
9515 language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine,
9516 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
9517 language_settings::SoftWrap::PreferredLineLength => {
9518 SoftWrap::Column(settings.preferred_line_length)
9519 }
9520 }
9521 }
9522
9523 pub fn set_soft_wrap_mode(
9524 &mut self,
9525 mode: language_settings::SoftWrap,
9526 cx: &mut ViewContext<Self>,
9527 ) {
9528 self.soft_wrap_mode_override = Some(mode);
9529 cx.notify();
9530 }
9531
9532 pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
9533 let rem_size = cx.rem_size();
9534 self.display_map.update(cx, |map, cx| {
9535 map.set_font(
9536 style.text.font(),
9537 style.text.font_size.to_pixels(rem_size),
9538 cx,
9539 )
9540 });
9541 self.style = Some(style);
9542 }
9543
9544 pub fn style(&self) -> Option<&EditorStyle> {
9545 self.style.as_ref()
9546 }
9547
9548 // Called by the element. This method is not designed to be called outside of the editor
9549 // element's layout code because it does not notify when rewrapping is computed synchronously.
9550 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
9551 self.display_map
9552 .update(cx, |map, cx| map.set_wrap_width(width, cx))
9553 }
9554
9555 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
9556 if self.soft_wrap_mode_override.is_some() {
9557 self.soft_wrap_mode_override.take();
9558 } else {
9559 let soft_wrap = match self.soft_wrap_mode(cx) {
9560 SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
9561 SoftWrap::EditorWidth | SoftWrap::Column(_) => {
9562 language_settings::SoftWrap::PreferLine
9563 }
9564 };
9565 self.soft_wrap_mode_override = Some(soft_wrap);
9566 }
9567 cx.notify();
9568 }
9569
9570 pub fn toggle_line_numbers(&mut self, _: &ToggleLineNumbers, cx: &mut ViewContext<Self>) {
9571 let mut editor_settings = EditorSettings::get_global(cx).clone();
9572 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
9573 EditorSettings::override_global(editor_settings, cx);
9574 }
9575
9576 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
9577 self.show_gutter = show_gutter;
9578 cx.notify();
9579 }
9580
9581 pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
9582 self.show_wrap_guides = Some(show_gutter);
9583 cx.notify();
9584 }
9585
9586 pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
9587 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
9588 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
9589 cx.reveal_path(&file.abs_path(cx));
9590 }
9591 }
9592 }
9593
9594 pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
9595 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
9596 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
9597 if let Some(path) = file.abs_path(cx).to_str() {
9598 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
9599 }
9600 }
9601 }
9602 }
9603
9604 pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
9605 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
9606 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
9607 if let Some(path) = file.path().to_str() {
9608 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
9609 }
9610 }
9611 }
9612 }
9613
9614 pub fn toggle_git_blame(&mut self, _: &ToggleGitBlame, cx: &mut ViewContext<Self>) {
9615 self.show_git_blame_gutter = !self.show_git_blame_gutter;
9616
9617 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
9618 self.start_git_blame(true, cx);
9619 }
9620
9621 cx.notify();
9622 }
9623
9624 pub fn toggle_git_blame_inline(
9625 &mut self,
9626 _: &ToggleGitBlameInline,
9627 cx: &mut ViewContext<Self>,
9628 ) {
9629 self.toggle_git_blame_inline_internal(true, cx);
9630 cx.notify();
9631 }
9632
9633 pub fn git_blame_inline_enabled(&self) -> bool {
9634 self.git_blame_inline_enabled
9635 }
9636
9637 fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
9638 if let Some(project) = self.project.as_ref() {
9639 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
9640 return;
9641 };
9642
9643 if buffer.read(cx).file().is_none() {
9644 return;
9645 }
9646
9647 let focused = self.focus_handle(cx).contains_focused(cx);
9648
9649 let project = project.clone();
9650 let blame =
9651 cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
9652 self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
9653 self.blame = Some(blame);
9654 }
9655 }
9656
9657 fn toggle_git_blame_inline_internal(
9658 &mut self,
9659 user_triggered: bool,
9660 cx: &mut ViewContext<Self>,
9661 ) {
9662 if self.git_blame_inline_enabled {
9663 self.git_blame_inline_enabled = false;
9664 self.show_git_blame_inline = false;
9665 self.show_git_blame_inline_delay_task.take();
9666 } else {
9667 self.git_blame_inline_enabled = true;
9668 self.start_git_blame_inline(user_triggered, cx);
9669 }
9670
9671 cx.notify();
9672 }
9673
9674 fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
9675 self.start_git_blame(user_triggered, cx);
9676
9677 if ProjectSettings::get_global(cx)
9678 .git
9679 .inline_blame_delay()
9680 .is_some()
9681 {
9682 self.start_inline_blame_timer(cx);
9683 } else {
9684 self.show_git_blame_inline = true
9685 }
9686 }
9687
9688 pub fn blame(&self) -> Option<&Model<GitBlame>> {
9689 self.blame.as_ref()
9690 }
9691
9692 pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
9693 self.show_git_blame_gutter && self.has_blame_entries(cx)
9694 }
9695
9696 pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
9697 self.show_git_blame_inline
9698 && self.focus_handle.is_focused(cx)
9699 && !self.newest_selection_head_on_empty_line(cx)
9700 && self.has_blame_entries(cx)
9701 }
9702
9703 fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
9704 self.blame()
9705 .map_or(false, |blame| blame.read(cx).has_generated_entries())
9706 }
9707
9708 fn newest_selection_head_on_empty_line(&mut self, cx: &mut WindowContext) -> bool {
9709 let cursor_anchor = self.selections.newest_anchor().head();
9710
9711 let snapshot = self.buffer.read(cx).snapshot(cx);
9712 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
9713
9714 snapshot.line_len(buffer_row) == 0
9715 }
9716
9717 fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Result<url::Url> {
9718 let (path, repo) = maybe!({
9719 let project_handle = self.project.as_ref()?.clone();
9720 let project = project_handle.read(cx);
9721 let buffer = self.buffer().read(cx).as_singleton()?;
9722 let path = buffer
9723 .read(cx)
9724 .file()?
9725 .as_local()?
9726 .path()
9727 .to_str()?
9728 .to_string();
9729 let repo = project.get_repo(&buffer.read(cx).project_path(cx)?, cx)?;
9730 Some((path, repo))
9731 })
9732 .ok_or_else(|| anyhow!("unable to open git repository"))?;
9733
9734 const REMOTE_NAME: &str = "origin";
9735 let origin_url = repo
9736 .lock()
9737 .remote_url(REMOTE_NAME)
9738 .ok_or_else(|| anyhow!("remote \"{REMOTE_NAME}\" not found"))?;
9739 let sha = repo
9740 .lock()
9741 .head_sha()
9742 .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
9743 let selections = self.selections.all::<Point>(cx);
9744 let selection = selections.iter().peekable().next();
9745
9746 let (provider, remote) =
9747 parse_git_remote_url(GitHostingProviderRegistry::default_global(cx), &origin_url)
9748 .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
9749
9750 Ok(provider.build_permalink(
9751 remote,
9752 BuildPermalinkParams {
9753 sha: &sha,
9754 path: &path,
9755 selection: selection.map(|selection| {
9756 let range = selection.range();
9757 let start = range.start.row;
9758 let end = range.end.row;
9759 start..end
9760 }),
9761 },
9762 ))
9763 }
9764
9765 pub fn copy_permalink_to_line(&mut self, _: &CopyPermalinkToLine, cx: &mut ViewContext<Self>) {
9766 let permalink = self.get_permalink_to_line(cx);
9767
9768 match permalink {
9769 Ok(permalink) => {
9770 cx.write_to_clipboard(ClipboardItem::new(permalink.to_string()));
9771 }
9772 Err(err) => {
9773 let message = format!("Failed to copy permalink: {err}");
9774
9775 Err::<(), anyhow::Error>(err).log_err();
9776
9777 if let Some(workspace) = self.workspace() {
9778 workspace.update(cx, |workspace, cx| {
9779 struct CopyPermalinkToLine;
9780
9781 workspace.show_toast(
9782 Toast::new(NotificationId::unique::<CopyPermalinkToLine>(), message),
9783 cx,
9784 )
9785 })
9786 }
9787 }
9788 }
9789 }
9790
9791 pub fn open_permalink_to_line(&mut self, _: &OpenPermalinkToLine, cx: &mut ViewContext<Self>) {
9792 let permalink = self.get_permalink_to_line(cx);
9793
9794 match permalink {
9795 Ok(permalink) => {
9796 cx.open_url(permalink.as_ref());
9797 }
9798 Err(err) => {
9799 let message = format!("Failed to open permalink: {err}");
9800
9801 Err::<(), anyhow::Error>(err).log_err();
9802
9803 if let Some(workspace) = self.workspace() {
9804 workspace.update(cx, |workspace, cx| {
9805 struct OpenPermalinkToLine;
9806
9807 workspace.show_toast(
9808 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
9809 cx,
9810 )
9811 })
9812 }
9813 }
9814 }
9815 }
9816
9817 /// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
9818 /// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
9819 /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
9820 pub fn highlight_rows<T: 'static>(
9821 &mut self,
9822 rows: RangeInclusive<Anchor>,
9823 color: Option<Hsla>,
9824 should_autoscroll: bool,
9825 cx: &mut ViewContext<Self>,
9826 ) {
9827 let snapshot = self.buffer().read(cx).snapshot(cx);
9828 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
9829 let existing_highlight_index = row_highlights.binary_search_by(|highlight| {
9830 highlight
9831 .range
9832 .start()
9833 .cmp(&rows.start(), &snapshot)
9834 .then(highlight.range.end().cmp(&rows.end(), &snapshot))
9835 });
9836 match (color, existing_highlight_index) {
9837 (Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert(
9838 ix,
9839 RowHighlight {
9840 index: post_inc(&mut self.highlight_order),
9841 range: rows,
9842 should_autoscroll,
9843 color,
9844 },
9845 ),
9846 (None, Ok(i)) => {
9847 row_highlights.remove(i);
9848 }
9849 }
9850 }
9851
9852 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
9853 pub fn clear_row_highlights<T: 'static>(&mut self) {
9854 self.highlighted_rows.remove(&TypeId::of::<T>());
9855 }
9856
9857 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
9858 pub fn highlighted_rows<T: 'static>(
9859 &self,
9860 ) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
9861 Some(
9862 self.highlighted_rows
9863 .get(&TypeId::of::<T>())?
9864 .iter()
9865 .map(|highlight| (&highlight.range, highlight.color.as_ref())),
9866 )
9867 }
9868
9869 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
9870 /// Rerturns a map of display rows that are highlighted and their corresponding highlight color.
9871 /// Allows to ignore certain kinds of highlights.
9872 pub fn highlighted_display_rows(
9873 &mut self,
9874 cx: &mut WindowContext,
9875 ) -> BTreeMap<DisplayRow, Hsla> {
9876 let snapshot = self.snapshot(cx);
9877 let mut used_highlight_orders = HashMap::default();
9878 self.highlighted_rows
9879 .iter()
9880 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
9881 .fold(
9882 BTreeMap::<DisplayRow, Hsla>::new(),
9883 |mut unique_rows, highlight| {
9884 let start_row = highlight.range.start().to_display_point(&snapshot).row();
9885 let end_row = highlight.range.end().to_display_point(&snapshot).row();
9886 for row in start_row.0..=end_row.0 {
9887 let used_index =
9888 used_highlight_orders.entry(row).or_insert(highlight.index);
9889 if highlight.index >= *used_index {
9890 *used_index = highlight.index;
9891 match highlight.color {
9892 Some(hsla) => unique_rows.insert(DisplayRow(row), hsla),
9893 None => unique_rows.remove(&DisplayRow(row)),
9894 };
9895 }
9896 }
9897 unique_rows
9898 },
9899 )
9900 }
9901
9902 pub fn highlighted_display_row_for_autoscroll(
9903 &self,
9904 snapshot: &DisplaySnapshot,
9905 ) -> Option<DisplayRow> {
9906 self.highlighted_rows
9907 .values()
9908 .flat_map(|highlighted_rows| highlighted_rows.iter())
9909 .filter_map(|highlight| {
9910 if highlight.color.is_none() || !highlight.should_autoscroll {
9911 return None;
9912 }
9913 Some(highlight.range.start().to_display_point(&snapshot).row())
9914 })
9915 .min()
9916 }
9917
9918 pub fn set_search_within_ranges(
9919 &mut self,
9920 ranges: &[Range<Anchor>],
9921 cx: &mut ViewContext<Self>,
9922 ) {
9923 self.highlight_background::<SearchWithinRange>(
9924 ranges,
9925 |colors| colors.editor_document_highlight_read_background,
9926 cx,
9927 )
9928 }
9929
9930 pub fn highlight_background<T: 'static>(
9931 &mut self,
9932 ranges: &[Range<Anchor>],
9933 color_fetcher: fn(&ThemeColors) -> Hsla,
9934 cx: &mut ViewContext<Self>,
9935 ) {
9936 let snapshot = self.snapshot(cx);
9937 // this is to try and catch a panic sooner
9938 for range in ranges {
9939 snapshot
9940 .buffer_snapshot
9941 .summary_for_anchor::<usize>(&range.start);
9942 snapshot
9943 .buffer_snapshot
9944 .summary_for_anchor::<usize>(&range.end);
9945 }
9946
9947 self.background_highlights
9948 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
9949 self.scrollbar_marker_state.dirty = true;
9950 cx.notify();
9951 }
9952
9953 pub fn clear_background_highlights<T: 'static>(
9954 &mut self,
9955 cx: &mut ViewContext<Self>,
9956 ) -> Option<BackgroundHighlight> {
9957 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
9958 if !text_highlights.1.is_empty() {
9959 self.scrollbar_marker_state.dirty = true;
9960 cx.notify();
9961 }
9962 Some(text_highlights)
9963 }
9964
9965 #[cfg(feature = "test-support")]
9966 pub fn all_text_background_highlights(
9967 &mut self,
9968 cx: &mut ViewContext<Self>,
9969 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
9970 let snapshot = self.snapshot(cx);
9971 let buffer = &snapshot.buffer_snapshot;
9972 let start = buffer.anchor_before(0);
9973 let end = buffer.anchor_after(buffer.len());
9974 let theme = cx.theme().colors();
9975 self.background_highlights_in_range(start..end, &snapshot, theme)
9976 }
9977
9978 fn document_highlights_for_position<'a>(
9979 &'a self,
9980 position: Anchor,
9981 buffer: &'a MultiBufferSnapshot,
9982 ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
9983 let read_highlights = self
9984 .background_highlights
9985 .get(&TypeId::of::<DocumentHighlightRead>())
9986 .map(|h| &h.1);
9987 let write_highlights = self
9988 .background_highlights
9989 .get(&TypeId::of::<DocumentHighlightWrite>())
9990 .map(|h| &h.1);
9991 let left_position = position.bias_left(buffer);
9992 let right_position = position.bias_right(buffer);
9993 read_highlights
9994 .into_iter()
9995 .chain(write_highlights)
9996 .flat_map(move |ranges| {
9997 let start_ix = match ranges.binary_search_by(|probe| {
9998 let cmp = probe.end.cmp(&left_position, buffer);
9999 if cmp.is_ge() {
10000 Ordering::Greater
10001 } else {
10002 Ordering::Less
10003 }
10004 }) {
10005 Ok(i) | Err(i) => i,
10006 };
10007
10008 ranges[start_ix..]
10009 .iter()
10010 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
10011 })
10012 }
10013
10014 pub fn has_background_highlights<T: 'static>(&self) -> bool {
10015 self.background_highlights
10016 .get(&TypeId::of::<T>())
10017 .map_or(false, |(_, highlights)| !highlights.is_empty())
10018 }
10019
10020 pub fn background_highlights_in_range(
10021 &self,
10022 search_range: Range<Anchor>,
10023 display_snapshot: &DisplaySnapshot,
10024 theme: &ThemeColors,
10025 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
10026 let mut results = Vec::new();
10027 for (color_fetcher, ranges) in self.background_highlights.values() {
10028 let color = color_fetcher(theme);
10029 let start_ix = match ranges.binary_search_by(|probe| {
10030 let cmp = probe
10031 .end
10032 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
10033 if cmp.is_gt() {
10034 Ordering::Greater
10035 } else {
10036 Ordering::Less
10037 }
10038 }) {
10039 Ok(i) | Err(i) => i,
10040 };
10041 for range in &ranges[start_ix..] {
10042 if range
10043 .start
10044 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
10045 .is_ge()
10046 {
10047 break;
10048 }
10049
10050 let start = range.start.to_display_point(&display_snapshot);
10051 let end = range.end.to_display_point(&display_snapshot);
10052 results.push((start..end, color))
10053 }
10054 }
10055 results
10056 }
10057
10058 pub fn background_highlight_row_ranges<T: 'static>(
10059 &self,
10060 search_range: Range<Anchor>,
10061 display_snapshot: &DisplaySnapshot,
10062 count: usize,
10063 ) -> Vec<RangeInclusive<DisplayPoint>> {
10064 let mut results = Vec::new();
10065 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
10066 return vec![];
10067 };
10068
10069 let start_ix = match ranges.binary_search_by(|probe| {
10070 let cmp = probe
10071 .end
10072 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
10073 if cmp.is_gt() {
10074 Ordering::Greater
10075 } else {
10076 Ordering::Less
10077 }
10078 }) {
10079 Ok(i) | Err(i) => i,
10080 };
10081 let mut push_region = |start: Option<Point>, end: Option<Point>| {
10082 if let (Some(start_display), Some(end_display)) = (start, end) {
10083 results.push(
10084 start_display.to_display_point(display_snapshot)
10085 ..=end_display.to_display_point(display_snapshot),
10086 );
10087 }
10088 };
10089 let mut start_row: Option<Point> = None;
10090 let mut end_row: Option<Point> = None;
10091 if ranges.len() > count {
10092 return Vec::new();
10093 }
10094 for range in &ranges[start_ix..] {
10095 if range
10096 .start
10097 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
10098 .is_ge()
10099 {
10100 break;
10101 }
10102 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
10103 if let Some(current_row) = &end_row {
10104 if end.row == current_row.row {
10105 continue;
10106 }
10107 }
10108 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
10109 if start_row.is_none() {
10110 assert_eq!(end_row, None);
10111 start_row = Some(start);
10112 end_row = Some(end);
10113 continue;
10114 }
10115 if let Some(current_end) = end_row.as_mut() {
10116 if start.row > current_end.row + 1 {
10117 push_region(start_row, end_row);
10118 start_row = Some(start);
10119 end_row = Some(end);
10120 } else {
10121 // Merge two hunks.
10122 *current_end = end;
10123 }
10124 } else {
10125 unreachable!();
10126 }
10127 }
10128 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
10129 push_region(start_row, end_row);
10130 results
10131 }
10132
10133 /// Get the text ranges corresponding to the redaction query
10134 pub fn redacted_ranges(
10135 &self,
10136 search_range: Range<Anchor>,
10137 display_snapshot: &DisplaySnapshot,
10138 cx: &WindowContext,
10139 ) -> Vec<Range<DisplayPoint>> {
10140 display_snapshot
10141 .buffer_snapshot
10142 .redacted_ranges(search_range, |file| {
10143 if let Some(file) = file {
10144 file.is_private()
10145 && EditorSettings::get(Some(file.as_ref().into()), cx).redact_private_values
10146 } else {
10147 false
10148 }
10149 })
10150 .map(|range| {
10151 range.start.to_display_point(display_snapshot)
10152 ..range.end.to_display_point(display_snapshot)
10153 })
10154 .collect()
10155 }
10156
10157 pub fn highlight_text<T: 'static>(
10158 &mut self,
10159 ranges: Vec<Range<Anchor>>,
10160 style: HighlightStyle,
10161 cx: &mut ViewContext<Self>,
10162 ) {
10163 self.display_map.update(cx, |map, _| {
10164 map.highlight_text(TypeId::of::<T>(), ranges, style)
10165 });
10166 cx.notify();
10167 }
10168
10169 pub(crate) fn highlight_inlays<T: 'static>(
10170 &mut self,
10171 highlights: Vec<InlayHighlight>,
10172 style: HighlightStyle,
10173 cx: &mut ViewContext<Self>,
10174 ) {
10175 self.display_map.update(cx, |map, _| {
10176 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
10177 });
10178 cx.notify();
10179 }
10180
10181 pub fn text_highlights<'a, T: 'static>(
10182 &'a self,
10183 cx: &'a AppContext,
10184 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
10185 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
10186 }
10187
10188 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
10189 let cleared = self
10190 .display_map
10191 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
10192 if cleared {
10193 cx.notify();
10194 }
10195 }
10196
10197 pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
10198 (self.read_only(cx) || self.blink_manager.read(cx).visible())
10199 && self.focus_handle.is_focused(cx)
10200 }
10201
10202 fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
10203 cx.notify();
10204 }
10205
10206 fn on_buffer_event(
10207 &mut self,
10208 multibuffer: Model<MultiBuffer>,
10209 event: &multi_buffer::Event,
10210 cx: &mut ViewContext<Self>,
10211 ) {
10212 match event {
10213 multi_buffer::Event::Edited {
10214 singleton_buffer_edited,
10215 } => {
10216 self.scrollbar_marker_state.dirty = true;
10217 self.refresh_active_diagnostics(cx);
10218 self.refresh_code_actions(cx);
10219 if self.has_active_inline_completion(cx) {
10220 self.update_visible_inline_completion(cx);
10221 }
10222 cx.emit(EditorEvent::BufferEdited);
10223 cx.emit(SearchEvent::MatchesInvalidated);
10224
10225 if *singleton_buffer_edited {
10226 if let Some(project) = &self.project {
10227 let project = project.read(cx);
10228 let languages_affected = multibuffer
10229 .read(cx)
10230 .all_buffers()
10231 .into_iter()
10232 .filter_map(|buffer| {
10233 let buffer = buffer.read(cx);
10234 let language = buffer.language()?;
10235 if project.is_local()
10236 && project.language_servers_for_buffer(buffer, cx).count() == 0
10237 {
10238 None
10239 } else {
10240 Some(language)
10241 }
10242 })
10243 .cloned()
10244 .collect::<HashSet<_>>();
10245 if !languages_affected.is_empty() {
10246 self.refresh_inlay_hints(
10247 InlayHintRefreshReason::BufferEdited(languages_affected),
10248 cx,
10249 );
10250 }
10251 }
10252 }
10253
10254 let Some(project) = &self.project else { return };
10255 let telemetry = project.read(cx).client().telemetry().clone();
10256 telemetry.log_edit_event("editor");
10257 }
10258 multi_buffer::Event::ExcerptsAdded {
10259 buffer,
10260 predecessor,
10261 excerpts,
10262 } => {
10263 self.tasks_update_task = Some(self.refresh_runnables(cx));
10264 cx.emit(EditorEvent::ExcerptsAdded {
10265 buffer: buffer.clone(),
10266 predecessor: *predecessor,
10267 excerpts: excerpts.clone(),
10268 });
10269 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
10270 }
10271 multi_buffer::Event::ExcerptsRemoved { ids } => {
10272 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
10273 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
10274 }
10275 multi_buffer::Event::Reparsed => {
10276 self.tasks_update_task = Some(self.refresh_runnables(cx));
10277
10278 cx.emit(EditorEvent::Reparsed);
10279 }
10280 multi_buffer::Event::LanguageChanged => {
10281 cx.emit(EditorEvent::Reparsed);
10282 cx.notify();
10283 }
10284 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
10285 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
10286 multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
10287 cx.emit(EditorEvent::TitleChanged)
10288 }
10289 multi_buffer::Event::DiffBaseChanged => {
10290 self.scrollbar_marker_state.dirty = true;
10291 cx.emit(EditorEvent::DiffBaseChanged);
10292 cx.notify();
10293 }
10294 multi_buffer::Event::DiffUpdated { buffer } => {
10295 self.sync_expanded_diff_hunks(buffer.clone(), cx);
10296 cx.notify();
10297 }
10298 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
10299 multi_buffer::Event::DiagnosticsUpdated => {
10300 self.refresh_active_diagnostics(cx);
10301 self.scrollbar_marker_state.dirty = true;
10302 cx.notify();
10303 }
10304 _ => {}
10305 };
10306 }
10307
10308 fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
10309 cx.notify();
10310 }
10311
10312 fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
10313 self.refresh_inline_completion(true, cx);
10314 self.refresh_inlay_hints(
10315 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
10316 self.selections.newest_anchor().head(),
10317 &self.buffer.read(cx).snapshot(cx),
10318 cx,
10319 )),
10320 cx,
10321 );
10322 let editor_settings = EditorSettings::get_global(cx);
10323 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
10324 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
10325 self.current_line_highlight = editor_settings.current_line_highlight;
10326
10327 if self.mode == EditorMode::Full {
10328 let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();
10329 if self.git_blame_inline_enabled != inline_blame_enabled {
10330 self.toggle_git_blame_inline_internal(false, cx);
10331 }
10332 }
10333
10334 cx.notify();
10335 }
10336
10337 pub fn set_searchable(&mut self, searchable: bool) {
10338 self.searchable = searchable;
10339 }
10340
10341 pub fn searchable(&self) -> bool {
10342 self.searchable
10343 }
10344
10345 fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
10346 self.open_excerpts_common(true, cx)
10347 }
10348
10349 fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
10350 self.open_excerpts_common(false, cx)
10351 }
10352
10353 fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
10354 let buffer = self.buffer.read(cx);
10355 if buffer.is_singleton() {
10356 cx.propagate();
10357 return;
10358 }
10359
10360 let Some(workspace) = self.workspace() else {
10361 cx.propagate();
10362 return;
10363 };
10364
10365 let mut new_selections_by_buffer = HashMap::default();
10366 for selection in self.selections.all::<usize>(cx) {
10367 for (buffer, mut range, _) in
10368 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
10369 {
10370 if selection.reversed {
10371 mem::swap(&mut range.start, &mut range.end);
10372 }
10373 new_selections_by_buffer
10374 .entry(buffer)
10375 .or_insert(Vec::new())
10376 .push(range)
10377 }
10378 }
10379
10380 // We defer the pane interaction because we ourselves are a workspace item
10381 // and activating a new item causes the pane to call a method on us reentrantly,
10382 // which panics if we're on the stack.
10383 cx.window_context().defer(move |cx| {
10384 workspace.update(cx, |workspace, cx| {
10385 let pane = if split {
10386 workspace.adjacent_pane(cx)
10387 } else {
10388 workspace.active_pane().clone()
10389 };
10390
10391 for (buffer, ranges) in new_selections_by_buffer {
10392 let editor = workspace.open_project_item::<Self>(pane.clone(), buffer, cx);
10393 editor.update(cx, |editor, cx| {
10394 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
10395 s.select_ranges(ranges);
10396 });
10397 });
10398 }
10399 })
10400 });
10401 }
10402
10403 fn jump(
10404 &mut self,
10405 path: ProjectPath,
10406 position: Point,
10407 anchor: language::Anchor,
10408 offset_from_top: u32,
10409 cx: &mut ViewContext<Self>,
10410 ) {
10411 let workspace = self.workspace();
10412 cx.spawn(|_, mut cx| async move {
10413 let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
10414 let editor = workspace.update(&mut cx, |workspace, cx| {
10415 // Reset the preview item id before opening the new item
10416 workspace.active_pane().update(cx, |pane, cx| {
10417 pane.set_preview_item_id(None, cx);
10418 });
10419 workspace.open_path_preview(path, None, true, true, cx)
10420 })?;
10421 let editor = editor
10422 .await?
10423 .downcast::<Editor>()
10424 .ok_or_else(|| anyhow!("opened item was not an editor"))?
10425 .downgrade();
10426 editor.update(&mut cx, |editor, cx| {
10427 let buffer = editor
10428 .buffer()
10429 .read(cx)
10430 .as_singleton()
10431 .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
10432 let buffer = buffer.read(cx);
10433 let cursor = if buffer.can_resolve(&anchor) {
10434 language::ToPoint::to_point(&anchor, buffer)
10435 } else {
10436 buffer.clip_point(position, Bias::Left)
10437 };
10438
10439 let nav_history = editor.nav_history.take();
10440 editor.change_selections(
10441 Some(Autoscroll::top_relative(offset_from_top as usize)),
10442 cx,
10443 |s| {
10444 s.select_ranges([cursor..cursor]);
10445 },
10446 );
10447 editor.nav_history = nav_history;
10448
10449 anyhow::Ok(())
10450 })??;
10451
10452 anyhow::Ok(())
10453 })
10454 .detach_and_log_err(cx);
10455 }
10456
10457 fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
10458 let snapshot = self.buffer.read(cx).read(cx);
10459 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
10460 Some(
10461 ranges
10462 .iter()
10463 .map(move |range| {
10464 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
10465 })
10466 .collect(),
10467 )
10468 }
10469
10470 fn selection_replacement_ranges(
10471 &self,
10472 range: Range<OffsetUtf16>,
10473 cx: &AppContext,
10474 ) -> Vec<Range<OffsetUtf16>> {
10475 let selections = self.selections.all::<OffsetUtf16>(cx);
10476 let newest_selection = selections
10477 .iter()
10478 .max_by_key(|selection| selection.id)
10479 .unwrap();
10480 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
10481 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
10482 let snapshot = self.buffer.read(cx).read(cx);
10483 selections
10484 .into_iter()
10485 .map(|mut selection| {
10486 selection.start.0 =
10487 (selection.start.0 as isize).saturating_add(start_delta) as usize;
10488 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
10489 snapshot.clip_offset_utf16(selection.start, Bias::Left)
10490 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
10491 })
10492 .collect()
10493 }
10494
10495 fn report_editor_event(
10496 &self,
10497 operation: &'static str,
10498 file_extension: Option<String>,
10499 cx: &AppContext,
10500 ) {
10501 if cfg!(any(test, feature = "test-support")) {
10502 return;
10503 }
10504
10505 let Some(project) = &self.project else { return };
10506
10507 // If None, we are in a file without an extension
10508 let file = self
10509 .buffer
10510 .read(cx)
10511 .as_singleton()
10512 .and_then(|b| b.read(cx).file());
10513 let file_extension = file_extension.or(file
10514 .as_ref()
10515 .and_then(|file| Path::new(file.file_name(cx)).extension())
10516 .and_then(|e| e.to_str())
10517 .map(|a| a.to_string()));
10518
10519 let vim_mode = cx
10520 .global::<SettingsStore>()
10521 .raw_user_settings()
10522 .get("vim_mode")
10523 == Some(&serde_json::Value::Bool(true));
10524
10525 let copilot_enabled = all_language_settings(file, cx).inline_completions.provider
10526 == language::language_settings::InlineCompletionProvider::Copilot;
10527 let copilot_enabled_for_language = self
10528 .buffer
10529 .read(cx)
10530 .settings_at(0, cx)
10531 .show_inline_completions;
10532
10533 let telemetry = project.read(cx).client().telemetry().clone();
10534 telemetry.report_editor_event(
10535 file_extension,
10536 vim_mode,
10537 operation,
10538 copilot_enabled,
10539 copilot_enabled_for_language,
10540 )
10541 }
10542
10543 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
10544 /// with each line being an array of {text, highlight} objects.
10545 fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
10546 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
10547 return;
10548 };
10549
10550 #[derive(Serialize)]
10551 struct Chunk<'a> {
10552 text: String,
10553 highlight: Option<&'a str>,
10554 }
10555
10556 let snapshot = buffer.read(cx).snapshot();
10557 let range = self
10558 .selected_text_range(cx)
10559 .and_then(|selected_range| {
10560 if selected_range.is_empty() {
10561 None
10562 } else {
10563 Some(selected_range)
10564 }
10565 })
10566 .unwrap_or_else(|| 0..snapshot.len());
10567
10568 let chunks = snapshot.chunks(range, true);
10569 let mut lines = Vec::new();
10570 let mut line: VecDeque<Chunk> = VecDeque::new();
10571
10572 let Some(style) = self.style.as_ref() else {
10573 return;
10574 };
10575
10576 for chunk in chunks {
10577 let highlight = chunk
10578 .syntax_highlight_id
10579 .and_then(|id| id.name(&style.syntax));
10580 let mut chunk_lines = chunk.text.split('\n').peekable();
10581 while let Some(text) = chunk_lines.next() {
10582 let mut merged_with_last_token = false;
10583 if let Some(last_token) = line.back_mut() {
10584 if last_token.highlight == highlight {
10585 last_token.text.push_str(text);
10586 merged_with_last_token = true;
10587 }
10588 }
10589
10590 if !merged_with_last_token {
10591 line.push_back(Chunk {
10592 text: text.into(),
10593 highlight,
10594 });
10595 }
10596
10597 if chunk_lines.peek().is_some() {
10598 if line.len() > 1 && line.front().unwrap().text.is_empty() {
10599 line.pop_front();
10600 }
10601 if line.len() > 1 && line.back().unwrap().text.is_empty() {
10602 line.pop_back();
10603 }
10604
10605 lines.push(mem::take(&mut line));
10606 }
10607 }
10608 }
10609
10610 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
10611 return;
10612 };
10613 cx.write_to_clipboard(ClipboardItem::new(lines));
10614 }
10615
10616 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
10617 &self.inlay_hint_cache
10618 }
10619
10620 pub fn replay_insert_event(
10621 &mut self,
10622 text: &str,
10623 relative_utf16_range: Option<Range<isize>>,
10624 cx: &mut ViewContext<Self>,
10625 ) {
10626 if !self.input_enabled {
10627 cx.emit(EditorEvent::InputIgnored { text: text.into() });
10628 return;
10629 }
10630 if let Some(relative_utf16_range) = relative_utf16_range {
10631 let selections = self.selections.all::<OffsetUtf16>(cx);
10632 self.change_selections(None, cx, |s| {
10633 let new_ranges = selections.into_iter().map(|range| {
10634 let start = OffsetUtf16(
10635 range
10636 .head()
10637 .0
10638 .saturating_add_signed(relative_utf16_range.start),
10639 );
10640 let end = OffsetUtf16(
10641 range
10642 .head()
10643 .0
10644 .saturating_add_signed(relative_utf16_range.end),
10645 );
10646 start..end
10647 });
10648 s.select_ranges(new_ranges);
10649 });
10650 }
10651
10652 self.handle_input(text, cx);
10653 }
10654
10655 pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
10656 let Some(project) = self.project.as_ref() else {
10657 return false;
10658 };
10659 let project = project.read(cx);
10660
10661 let mut supports = false;
10662 self.buffer().read(cx).for_each_buffer(|buffer| {
10663 if !supports {
10664 supports = project
10665 .language_servers_for_buffer(buffer.read(cx), cx)
10666 .any(
10667 |(_, server)| match server.capabilities().inlay_hint_provider {
10668 Some(lsp::OneOf::Left(enabled)) => enabled,
10669 Some(lsp::OneOf::Right(_)) => true,
10670 None => false,
10671 },
10672 )
10673 }
10674 });
10675 supports
10676 }
10677
10678 pub fn focus(&self, cx: &mut WindowContext) {
10679 cx.focus(&self.focus_handle)
10680 }
10681
10682 pub fn is_focused(&self, cx: &WindowContext) -> bool {
10683 self.focus_handle.is_focused(cx)
10684 }
10685
10686 fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
10687 cx.emit(EditorEvent::Focused);
10688
10689 if let Some(rename) = self.pending_rename.as_ref() {
10690 let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
10691 cx.focus(&rename_editor_focus_handle);
10692 } else {
10693 if let Some(blame) = self.blame.as_ref() {
10694 blame.update(cx, GitBlame::focus)
10695 }
10696
10697 self.blink_manager.update(cx, BlinkManager::enable);
10698 self.show_cursor_names(cx);
10699 self.buffer.update(cx, |buffer, cx| {
10700 buffer.finalize_last_transaction(cx);
10701 if self.leader_peer_id.is_none() {
10702 buffer.set_active_selections(
10703 &self.selections.disjoint_anchors(),
10704 self.selections.line_mode,
10705 self.cursor_shape,
10706 cx,
10707 );
10708 }
10709 });
10710 }
10711 }
10712
10713 pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
10714 self.blink_manager.update(cx, BlinkManager::disable);
10715 self.buffer
10716 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
10717
10718 if let Some(blame) = self.blame.as_ref() {
10719 blame.update(cx, GitBlame::blur)
10720 }
10721 self.hide_context_menu(cx);
10722 hide_hover(self, cx);
10723 cx.emit(EditorEvent::Blurred);
10724 cx.notify();
10725 }
10726
10727 pub fn register_action<A: Action>(
10728 &mut self,
10729 listener: impl Fn(&A, &mut WindowContext) + 'static,
10730 ) -> &mut Self {
10731 let listener = Arc::new(listener);
10732
10733 self.editor_actions.push(Box::new(move |cx| {
10734 let _view = cx.view().clone();
10735 let cx = cx.window_context();
10736 let listener = listener.clone();
10737 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
10738 let action = action.downcast_ref().unwrap();
10739 if phase == DispatchPhase::Bubble {
10740 listener(action, cx)
10741 }
10742 })
10743 }));
10744 self
10745 }
10746}
10747
10748fn hunks_for_selections(
10749 multi_buffer_snapshot: &MultiBufferSnapshot,
10750 selections: &[Selection<Anchor>],
10751) -> Vec<DiffHunk<MultiBufferRow>> {
10752 let mut hunks = Vec::with_capacity(selections.len());
10753 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
10754 HashMap::default();
10755 let buffer_rows_for_selections = selections.iter().map(|selection| {
10756 let head = selection.head();
10757 let tail = selection.tail();
10758 let start = MultiBufferRow(tail.to_point(&multi_buffer_snapshot).row);
10759 let end = MultiBufferRow(head.to_point(&multi_buffer_snapshot).row);
10760 if start > end {
10761 end..start
10762 } else {
10763 start..end
10764 }
10765 });
10766
10767 for selected_multi_buffer_rows in buffer_rows_for_selections {
10768 let query_rows =
10769 selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
10770 for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
10771 // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
10772 // when the caret is just above or just below the deleted hunk.
10773 let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
10774 let related_to_selection = if allow_adjacent {
10775 hunk.associated_range.overlaps(&query_rows)
10776 || hunk.associated_range.start == query_rows.end
10777 || hunk.associated_range.end == query_rows.start
10778 } else {
10779 // `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
10780 // `hunk.associated_range` is exclusive (e.g. [2..3] means 2nd row is selected)
10781 hunk.associated_range.overlaps(&selected_multi_buffer_rows)
10782 || selected_multi_buffer_rows.end == hunk.associated_range.start
10783 };
10784 if related_to_selection {
10785 if !processed_buffer_rows
10786 .entry(hunk.buffer_id)
10787 .or_default()
10788 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
10789 {
10790 continue;
10791 }
10792 hunks.push(hunk);
10793 }
10794 }
10795 }
10796
10797 hunks
10798}
10799
10800pub trait CollaborationHub {
10801 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
10802 fn user_participant_indices<'a>(
10803 &self,
10804 cx: &'a AppContext,
10805 ) -> &'a HashMap<u64, ParticipantIndex>;
10806 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
10807}
10808
10809impl CollaborationHub for Model<Project> {
10810 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
10811 self.read(cx).collaborators()
10812 }
10813
10814 fn user_participant_indices<'a>(
10815 &self,
10816 cx: &'a AppContext,
10817 ) -> &'a HashMap<u64, ParticipantIndex> {
10818 self.read(cx).user_store().read(cx).participant_indices()
10819 }
10820
10821 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
10822 let this = self.read(cx);
10823 let user_ids = this.collaborators().values().map(|c| c.user_id);
10824 this.user_store().read_with(cx, |user_store, cx| {
10825 user_store.participant_names(user_ids, cx)
10826 })
10827 }
10828}
10829
10830pub trait CompletionProvider {
10831 fn completions(
10832 &self,
10833 buffer: &Model<Buffer>,
10834 buffer_position: text::Anchor,
10835 cx: &mut ViewContext<Editor>,
10836 ) -> Task<Result<Vec<Completion>>>;
10837
10838 fn resolve_completions(
10839 &self,
10840 buffer: Model<Buffer>,
10841 completion_indices: Vec<usize>,
10842 completions: Arc<RwLock<Box<[Completion]>>>,
10843 cx: &mut ViewContext<Editor>,
10844 ) -> Task<Result<bool>>;
10845
10846 fn apply_additional_edits_for_completion(
10847 &self,
10848 buffer: Model<Buffer>,
10849 completion: Completion,
10850 push_to_history: bool,
10851 cx: &mut ViewContext<Editor>,
10852 ) -> Task<Result<Option<language::Transaction>>>;
10853}
10854
10855impl CompletionProvider for Model<Project> {
10856 fn completions(
10857 &self,
10858 buffer: &Model<Buffer>,
10859 buffer_position: text::Anchor,
10860 cx: &mut ViewContext<Editor>,
10861 ) -> Task<Result<Vec<Completion>>> {
10862 self.update(cx, |project, cx| {
10863 project.completions(&buffer, buffer_position, cx)
10864 })
10865 }
10866
10867 fn resolve_completions(
10868 &self,
10869 buffer: Model<Buffer>,
10870 completion_indices: Vec<usize>,
10871 completions: Arc<RwLock<Box<[Completion]>>>,
10872 cx: &mut ViewContext<Editor>,
10873 ) -> Task<Result<bool>> {
10874 self.update(cx, |project, cx| {
10875 project.resolve_completions(buffer, completion_indices, completions, cx)
10876 })
10877 }
10878
10879 fn apply_additional_edits_for_completion(
10880 &self,
10881 buffer: Model<Buffer>,
10882 completion: Completion,
10883 push_to_history: bool,
10884 cx: &mut ViewContext<Editor>,
10885 ) -> Task<Result<Option<language::Transaction>>> {
10886 self.update(cx, |project, cx| {
10887 project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
10888 })
10889 }
10890}
10891
10892fn inlay_hint_settings(
10893 location: Anchor,
10894 snapshot: &MultiBufferSnapshot,
10895 cx: &mut ViewContext<'_, Editor>,
10896) -> InlayHintSettings {
10897 let file = snapshot.file_at(location);
10898 let language = snapshot.language_at(location);
10899 let settings = all_language_settings(file, cx);
10900 settings
10901 .language(language.map(|l| l.name()).as_deref())
10902 .inlay_hints
10903}
10904
10905fn consume_contiguous_rows(
10906 contiguous_row_selections: &mut Vec<Selection<Point>>,
10907 selection: &Selection<Point>,
10908 display_map: &DisplaySnapshot,
10909 selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
10910) -> (MultiBufferRow, MultiBufferRow) {
10911 contiguous_row_selections.push(selection.clone());
10912 let start_row = MultiBufferRow(selection.start.row);
10913 let mut end_row = ending_row(selection, display_map);
10914
10915 while let Some(next_selection) = selections.peek() {
10916 if next_selection.start.row <= end_row.0 {
10917 end_row = ending_row(next_selection, display_map);
10918 contiguous_row_selections.push(selections.next().unwrap().clone());
10919 } else {
10920 break;
10921 }
10922 }
10923 (start_row, end_row)
10924}
10925
10926fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
10927 if next_selection.end.column > 0 || next_selection.is_empty() {
10928 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
10929 } else {
10930 MultiBufferRow(next_selection.end.row)
10931 }
10932}
10933
10934impl EditorSnapshot {
10935 pub fn remote_selections_in_range<'a>(
10936 &'a self,
10937 range: &'a Range<Anchor>,
10938 collaboration_hub: &dyn CollaborationHub,
10939 cx: &'a AppContext,
10940 ) -> impl 'a + Iterator<Item = RemoteSelection> {
10941 let participant_names = collaboration_hub.user_names(cx);
10942 let participant_indices = collaboration_hub.user_participant_indices(cx);
10943 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
10944 let collaborators_by_replica_id = collaborators_by_peer_id
10945 .iter()
10946 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
10947 .collect::<HashMap<_, _>>();
10948 self.buffer_snapshot
10949 .remote_selections_in_range(range)
10950 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
10951 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
10952 let participant_index = participant_indices.get(&collaborator.user_id).copied();
10953 let user_name = participant_names.get(&collaborator.user_id).cloned();
10954 Some(RemoteSelection {
10955 replica_id,
10956 selection,
10957 cursor_shape,
10958 line_mode,
10959 participant_index,
10960 peer_id: collaborator.peer_id,
10961 user_name,
10962 })
10963 })
10964 }
10965
10966 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
10967 self.display_snapshot.buffer_snapshot.language_at(position)
10968 }
10969
10970 pub fn is_focused(&self) -> bool {
10971 self.is_focused
10972 }
10973
10974 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
10975 self.placeholder_text.as_ref()
10976 }
10977
10978 pub fn scroll_position(&self) -> gpui::Point<f32> {
10979 self.scroll_anchor.scroll_position(&self.display_snapshot)
10980 }
10981
10982 pub fn gutter_dimensions(
10983 &self,
10984 font_id: FontId,
10985 font_size: Pixels,
10986 em_width: Pixels,
10987 max_line_number_width: Pixels,
10988 cx: &AppContext,
10989 ) -> GutterDimensions {
10990 if !self.show_gutter {
10991 return GutterDimensions::default();
10992 }
10993 let descent = cx.text_system().descent(font_id, font_size);
10994
10995 let show_git_gutter = matches!(
10996 ProjectSettings::get_global(cx).git.git_gutter,
10997 Some(GitGutterSetting::TrackedFiles)
10998 );
10999 let gutter_settings = EditorSettings::get_global(cx).gutter;
11000 let gutter_lines_enabled = gutter_settings.line_numbers;
11001 let line_gutter_width = if gutter_lines_enabled {
11002 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
11003 let min_width_for_number_on_gutter = em_width * 4.0;
11004 max_line_number_width.max(min_width_for_number_on_gutter)
11005 } else {
11006 0.0.into()
11007 };
11008
11009 let git_blame_entries_width = self
11010 .render_git_blame_gutter
11011 .then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
11012
11013 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
11014 left_padding += if gutter_settings.code_actions {
11015 em_width * 3.0
11016 } else if show_git_gutter && gutter_lines_enabled {
11017 em_width * 2.0
11018 } else if show_git_gutter || gutter_lines_enabled {
11019 em_width
11020 } else {
11021 px(0.)
11022 };
11023
11024 let right_padding = if gutter_settings.folds && gutter_lines_enabled {
11025 em_width * 4.0
11026 } else if gutter_settings.folds {
11027 em_width * 3.0
11028 } else if gutter_lines_enabled {
11029 em_width
11030 } else {
11031 px(0.)
11032 };
11033
11034 GutterDimensions {
11035 left_padding,
11036 right_padding,
11037 width: line_gutter_width + left_padding + right_padding,
11038 margin: -descent,
11039 git_blame_entries_width,
11040 }
11041 }
11042}
11043
11044impl Deref for EditorSnapshot {
11045 type Target = DisplaySnapshot;
11046
11047 fn deref(&self) -> &Self::Target {
11048 &self.display_snapshot
11049 }
11050}
11051
11052#[derive(Clone, Debug, PartialEq, Eq)]
11053pub enum EditorEvent {
11054 InputIgnored {
11055 text: Arc<str>,
11056 },
11057 InputHandled {
11058 utf16_range_to_replace: Option<Range<isize>>,
11059 text: Arc<str>,
11060 },
11061 ExcerptsAdded {
11062 buffer: Model<Buffer>,
11063 predecessor: ExcerptId,
11064 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
11065 },
11066 ExcerptsRemoved {
11067 ids: Vec<ExcerptId>,
11068 },
11069 BufferEdited,
11070 Edited,
11071 Reparsed,
11072 Focused,
11073 Blurred,
11074 DirtyChanged,
11075 Saved,
11076 TitleChanged,
11077 DiffBaseChanged,
11078 SelectionsChanged {
11079 local: bool,
11080 },
11081 ScrollPositionChanged {
11082 local: bool,
11083 autoscroll: bool,
11084 },
11085 Closed,
11086 TransactionUndone {
11087 transaction_id: clock::Lamport,
11088 },
11089 TransactionBegun {
11090 transaction_id: clock::Lamport,
11091 },
11092}
11093
11094impl EventEmitter<EditorEvent> for Editor {}
11095
11096impl FocusableView for Editor {
11097 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
11098 self.focus_handle.clone()
11099 }
11100}
11101
11102impl Render for Editor {
11103 fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
11104 let settings = ThemeSettings::get_global(cx);
11105
11106 let text_style = match self.mode {
11107 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
11108 color: cx.theme().colors().editor_foreground,
11109 font_family: settings.ui_font.family.clone(),
11110 font_features: settings.ui_font.features.clone(),
11111 font_size: rems(0.875).into(),
11112 font_weight: FontWeight::NORMAL,
11113 font_style: FontStyle::Normal,
11114 line_height: relative(settings.buffer_line_height.value()),
11115 background_color: None,
11116 underline: None,
11117 strikethrough: None,
11118 white_space: WhiteSpace::Normal,
11119 },
11120 EditorMode::Full => TextStyle {
11121 color: cx.theme().colors().editor_foreground,
11122 font_family: settings.buffer_font.family.clone(),
11123 font_features: settings.buffer_font.features.clone(),
11124 font_size: settings.buffer_font_size(cx).into(),
11125 font_weight: FontWeight::NORMAL,
11126 font_style: FontStyle::Normal,
11127 line_height: relative(settings.buffer_line_height.value()),
11128 background_color: None,
11129 underline: None,
11130 strikethrough: None,
11131 white_space: WhiteSpace::Normal,
11132 },
11133 };
11134
11135 let background = match self.mode {
11136 EditorMode::SingleLine => cx.theme().system().transparent,
11137 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
11138 EditorMode::Full => cx.theme().colors().editor_background,
11139 };
11140
11141 EditorElement::new(
11142 cx.view(),
11143 EditorStyle {
11144 background,
11145 local_player: cx.theme().players().local(),
11146 text: text_style,
11147 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
11148 syntax: cx.theme().syntax().clone(),
11149 status: cx.theme().status().clone(),
11150 inlay_hints_style: HighlightStyle {
11151 color: Some(cx.theme().status().hint),
11152 ..HighlightStyle::default()
11153 },
11154 suggestions_style: HighlightStyle {
11155 color: Some(cx.theme().status().predictive),
11156 ..HighlightStyle::default()
11157 },
11158 },
11159 )
11160 }
11161}
11162
11163impl ViewInputHandler for Editor {
11164 fn text_for_range(
11165 &mut self,
11166 range_utf16: Range<usize>,
11167 cx: &mut ViewContext<Self>,
11168 ) -> Option<String> {
11169 Some(
11170 self.buffer
11171 .read(cx)
11172 .read(cx)
11173 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
11174 .collect(),
11175 )
11176 }
11177
11178 fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
11179 // Prevent the IME menu from appearing when holding down an alphabetic key
11180 // while input is disabled.
11181 if !self.input_enabled {
11182 return None;
11183 }
11184
11185 let range = self.selections.newest::<OffsetUtf16>(cx).range();
11186 Some(range.start.0..range.end.0)
11187 }
11188
11189 fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
11190 let snapshot = self.buffer.read(cx).read(cx);
11191 let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
11192 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
11193 }
11194
11195 fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
11196 self.clear_highlights::<InputComposition>(cx);
11197 self.ime_transaction.take();
11198 }
11199
11200 fn replace_text_in_range(
11201 &mut self,
11202 range_utf16: Option<Range<usize>>,
11203 text: &str,
11204 cx: &mut ViewContext<Self>,
11205 ) {
11206 if !self.input_enabled {
11207 cx.emit(EditorEvent::InputIgnored { text: text.into() });
11208 return;
11209 }
11210
11211 self.transact(cx, |this, cx| {
11212 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
11213 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
11214 Some(this.selection_replacement_ranges(range_utf16, cx))
11215 } else {
11216 this.marked_text_ranges(cx)
11217 };
11218
11219 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
11220 let newest_selection_id = this.selections.newest_anchor().id;
11221 this.selections
11222 .all::<OffsetUtf16>(cx)
11223 .iter()
11224 .zip(ranges_to_replace.iter())
11225 .find_map(|(selection, range)| {
11226 if selection.id == newest_selection_id {
11227 Some(
11228 (range.start.0 as isize - selection.head().0 as isize)
11229 ..(range.end.0 as isize - selection.head().0 as isize),
11230 )
11231 } else {
11232 None
11233 }
11234 })
11235 });
11236
11237 cx.emit(EditorEvent::InputHandled {
11238 utf16_range_to_replace: range_to_replace,
11239 text: text.into(),
11240 });
11241
11242 if let Some(new_selected_ranges) = new_selected_ranges {
11243 this.change_selections(None, cx, |selections| {
11244 selections.select_ranges(new_selected_ranges)
11245 });
11246 this.backspace(&Default::default(), cx);
11247 }
11248
11249 this.handle_input(text, cx);
11250 });
11251
11252 if let Some(transaction) = self.ime_transaction {
11253 self.buffer.update(cx, |buffer, cx| {
11254 buffer.group_until_transaction(transaction, cx);
11255 });
11256 }
11257
11258 self.unmark_text(cx);
11259 }
11260
11261 fn replace_and_mark_text_in_range(
11262 &mut self,
11263 range_utf16: Option<Range<usize>>,
11264 text: &str,
11265 new_selected_range_utf16: Option<Range<usize>>,
11266 cx: &mut ViewContext<Self>,
11267 ) {
11268 if !self.input_enabled {
11269 cx.emit(EditorEvent::InputIgnored { text: text.into() });
11270 return;
11271 }
11272
11273 let transaction = self.transact(cx, |this, cx| {
11274 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
11275 let snapshot = this.buffer.read(cx).read(cx);
11276 if let Some(relative_range_utf16) = range_utf16.as_ref() {
11277 for marked_range in &mut marked_ranges {
11278 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
11279 marked_range.start.0 += relative_range_utf16.start;
11280 marked_range.start =
11281 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
11282 marked_range.end =
11283 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
11284 }
11285 }
11286 Some(marked_ranges)
11287 } else if let Some(range_utf16) = range_utf16 {
11288 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
11289 Some(this.selection_replacement_ranges(range_utf16, cx))
11290 } else {
11291 None
11292 };
11293
11294 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
11295 let newest_selection_id = this.selections.newest_anchor().id;
11296 this.selections
11297 .all::<OffsetUtf16>(cx)
11298 .iter()
11299 .zip(ranges_to_replace.iter())
11300 .find_map(|(selection, range)| {
11301 if selection.id == newest_selection_id {
11302 Some(
11303 (range.start.0 as isize - selection.head().0 as isize)
11304 ..(range.end.0 as isize - selection.head().0 as isize),
11305 )
11306 } else {
11307 None
11308 }
11309 })
11310 });
11311
11312 cx.emit(EditorEvent::InputHandled {
11313 utf16_range_to_replace: range_to_replace,
11314 text: text.into(),
11315 });
11316
11317 if let Some(ranges) = ranges_to_replace {
11318 this.change_selections(None, cx, |s| s.select_ranges(ranges));
11319 }
11320
11321 let marked_ranges = {
11322 let snapshot = this.buffer.read(cx).read(cx);
11323 this.selections
11324 .disjoint_anchors()
11325 .iter()
11326 .map(|selection| {
11327 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
11328 })
11329 .collect::<Vec<_>>()
11330 };
11331
11332 if text.is_empty() {
11333 this.unmark_text(cx);
11334 } else {
11335 this.highlight_text::<InputComposition>(
11336 marked_ranges.clone(),
11337 HighlightStyle {
11338 underline: Some(UnderlineStyle {
11339 thickness: px(1.),
11340 color: None,
11341 wavy: false,
11342 }),
11343 ..Default::default()
11344 },
11345 cx,
11346 );
11347 }
11348
11349 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
11350 let use_autoclose = this.use_autoclose;
11351 this.set_use_autoclose(false);
11352 this.handle_input(text, cx);
11353 this.set_use_autoclose(use_autoclose);
11354
11355 if let Some(new_selected_range) = new_selected_range_utf16 {
11356 let snapshot = this.buffer.read(cx).read(cx);
11357 let new_selected_ranges = marked_ranges
11358 .into_iter()
11359 .map(|marked_range| {
11360 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
11361 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
11362 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
11363 snapshot.clip_offset_utf16(new_start, Bias::Left)
11364 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
11365 })
11366 .collect::<Vec<_>>();
11367
11368 drop(snapshot);
11369 this.change_selections(None, cx, |selections| {
11370 selections.select_ranges(new_selected_ranges)
11371 });
11372 }
11373 });
11374
11375 self.ime_transaction = self.ime_transaction.or(transaction);
11376 if let Some(transaction) = self.ime_transaction {
11377 self.buffer.update(cx, |buffer, cx| {
11378 buffer.group_until_transaction(transaction, cx);
11379 });
11380 }
11381
11382 if self.text_highlights::<InputComposition>(cx).is_none() {
11383 self.ime_transaction.take();
11384 }
11385 }
11386
11387 fn bounds_for_range(
11388 &mut self,
11389 range_utf16: Range<usize>,
11390 element_bounds: gpui::Bounds<Pixels>,
11391 cx: &mut ViewContext<Self>,
11392 ) -> Option<gpui::Bounds<Pixels>> {
11393 let text_layout_details = self.text_layout_details(cx);
11394 let style = &text_layout_details.editor_style;
11395 let font_id = cx.text_system().resolve_font(&style.text.font());
11396 let font_size = style.text.font_size.to_pixels(cx.rem_size());
11397 let line_height = style.text.line_height_in_pixels(cx.rem_size());
11398 let em_width = cx
11399 .text_system()
11400 .typographic_bounds(font_id, font_size, 'm')
11401 .unwrap()
11402 .size
11403 .width;
11404
11405 let snapshot = self.snapshot(cx);
11406 let scroll_position = snapshot.scroll_position();
11407 let scroll_left = scroll_position.x * em_width;
11408
11409 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
11410 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
11411 + self.gutter_dimensions.width;
11412 let y = line_height * (start.row().as_f32() - scroll_position.y);
11413
11414 Some(Bounds {
11415 origin: element_bounds.origin + point(x, y),
11416 size: size(em_width, line_height),
11417 })
11418 }
11419}
11420
11421trait SelectionExt {
11422 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
11423 fn spanned_rows(
11424 &self,
11425 include_end_if_at_line_start: bool,
11426 map: &DisplaySnapshot,
11427 ) -> Range<MultiBufferRow>;
11428}
11429
11430impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
11431 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
11432 let start = self
11433 .start
11434 .to_point(&map.buffer_snapshot)
11435 .to_display_point(map);
11436 let end = self
11437 .end
11438 .to_point(&map.buffer_snapshot)
11439 .to_display_point(map);
11440 if self.reversed {
11441 end..start
11442 } else {
11443 start..end
11444 }
11445 }
11446
11447 fn spanned_rows(
11448 &self,
11449 include_end_if_at_line_start: bool,
11450 map: &DisplaySnapshot,
11451 ) -> Range<MultiBufferRow> {
11452 let start = self.start.to_point(&map.buffer_snapshot);
11453 let mut end = self.end.to_point(&map.buffer_snapshot);
11454 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
11455 end.row -= 1;
11456 }
11457
11458 let buffer_start = map.prev_line_boundary(start).0;
11459 let buffer_end = map.next_line_boundary(end).0;
11460 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
11461 }
11462}
11463
11464impl<T: InvalidationRegion> InvalidationStack<T> {
11465 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
11466 where
11467 S: Clone + ToOffset,
11468 {
11469 while let Some(region) = self.last() {
11470 let all_selections_inside_invalidation_ranges =
11471 if selections.len() == region.ranges().len() {
11472 selections
11473 .iter()
11474 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
11475 .all(|(selection, invalidation_range)| {
11476 let head = selection.head().to_offset(buffer);
11477 invalidation_range.start <= head && invalidation_range.end >= head
11478 })
11479 } else {
11480 false
11481 };
11482
11483 if all_selections_inside_invalidation_ranges {
11484 break;
11485 } else {
11486 self.pop();
11487 }
11488 }
11489 }
11490}
11491
11492impl<T> Default for InvalidationStack<T> {
11493 fn default() -> Self {
11494 Self(Default::default())
11495 }
11496}
11497
11498impl<T> Deref for InvalidationStack<T> {
11499 type Target = Vec<T>;
11500
11501 fn deref(&self) -> &Self::Target {
11502 &self.0
11503 }
11504}
11505
11506impl<T> DerefMut for InvalidationStack<T> {
11507 fn deref_mut(&mut self) -> &mut Self::Target {
11508 &mut self.0
11509 }
11510}
11511
11512impl InvalidationRegion for SnippetState {
11513 fn ranges(&self) -> &[Range<Anchor>] {
11514 &self.ranges[self.active_index]
11515 }
11516}
11517
11518pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock {
11519 let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
11520
11521 Box::new(move |cx: &mut BlockContext| {
11522 let group_id: SharedString = cx.block_id.to_string().into();
11523
11524 let mut text_style = cx.text_style().clone();
11525 text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
11526 let theme_settings = ThemeSettings::get_global(cx);
11527 text_style.font_family = theme_settings.buffer_font.family.clone();
11528 text_style.font_style = theme_settings.buffer_font.style;
11529 text_style.font_features = theme_settings.buffer_font.features.clone();
11530 text_style.font_weight = theme_settings.buffer_font.weight;
11531
11532 let multi_line_diagnostic = diagnostic.message.contains('\n');
11533
11534 let buttons = |diagnostic: &Diagnostic, block_id: usize| {
11535 if multi_line_diagnostic {
11536 v_flex()
11537 } else {
11538 h_flex()
11539 }
11540 .children(diagnostic.is_primary.then(|| {
11541 IconButton::new(("close-block", block_id), IconName::XCircle)
11542 .icon_color(Color::Muted)
11543 .size(ButtonSize::Compact)
11544 .style(ButtonStyle::Transparent)
11545 .visible_on_hover(group_id.clone())
11546 .on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
11547 .tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
11548 }))
11549 .child(
11550 IconButton::new(("copy-block", block_id), IconName::Copy)
11551 .icon_color(Color::Muted)
11552 .size(ButtonSize::Compact)
11553 .style(ButtonStyle::Transparent)
11554 .visible_on_hover(group_id.clone())
11555 .on_click({
11556 let message = diagnostic.message.clone();
11557 move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
11558 })
11559 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
11560 )
11561 };
11562
11563 let icon_size = buttons(&diagnostic, cx.block_id)
11564 .into_any_element()
11565 .layout_as_root(AvailableSpace::min_size(), cx);
11566
11567 h_flex()
11568 .id(cx.block_id)
11569 .group(group_id.clone())
11570 .relative()
11571 .size_full()
11572 .pl(cx.gutter_dimensions.width)
11573 .w(cx.max_width + cx.gutter_dimensions.width)
11574 .child(
11575 div()
11576 .flex()
11577 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
11578 .flex_shrink(),
11579 )
11580 .child(buttons(&diagnostic, cx.block_id))
11581 .child(div().flex().flex_shrink_0().child(
11582 StyledText::new(text_without_backticks.clone()).with_highlights(
11583 &text_style,
11584 code_ranges.iter().map(|range| {
11585 (
11586 range.clone(),
11587 HighlightStyle {
11588 font_weight: Some(FontWeight::BOLD),
11589 ..Default::default()
11590 },
11591 )
11592 }),
11593 ),
11594 ))
11595 .into_any_element()
11596 })
11597}
11598
11599pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
11600 let mut text_without_backticks = String::new();
11601 let mut code_ranges = Vec::new();
11602
11603 if let Some(source) = &diagnostic.source {
11604 text_without_backticks.push_str(&source);
11605 code_ranges.push(0..source.len());
11606 text_without_backticks.push_str(": ");
11607 }
11608
11609 let mut prev_offset = 0;
11610 let mut in_code_block = false;
11611 for (ix, _) in diagnostic
11612 .message
11613 .match_indices('`')
11614 .chain([(diagnostic.message.len(), "")])
11615 {
11616 let prev_len = text_without_backticks.len();
11617 text_without_backticks.push_str(&diagnostic.message[prev_offset..ix]);
11618 prev_offset = ix + 1;
11619 if in_code_block {
11620 code_ranges.push(prev_len..text_without_backticks.len());
11621 in_code_block = false;
11622 } else {
11623 in_code_block = true;
11624 }
11625 }
11626
11627 (text_without_backticks.into(), code_ranges)
11628}
11629
11630fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
11631 match (severity, valid) {
11632 (DiagnosticSeverity::ERROR, true) => colors.error,
11633 (DiagnosticSeverity::ERROR, false) => colors.error,
11634 (DiagnosticSeverity::WARNING, true) => colors.warning,
11635 (DiagnosticSeverity::WARNING, false) => colors.warning,
11636 (DiagnosticSeverity::INFORMATION, true) => colors.info,
11637 (DiagnosticSeverity::INFORMATION, false) => colors.info,
11638 (DiagnosticSeverity::HINT, true) => colors.info,
11639 (DiagnosticSeverity::HINT, false) => colors.info,
11640 _ => colors.ignored,
11641 }
11642}
11643
11644pub fn styled_runs_for_code_label<'a>(
11645 label: &'a CodeLabel,
11646 syntax_theme: &'a theme::SyntaxTheme,
11647) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
11648 let fade_out = HighlightStyle {
11649 fade_out: Some(0.35),
11650 ..Default::default()
11651 };
11652
11653 let mut prev_end = label.filter_range.end;
11654 label
11655 .runs
11656 .iter()
11657 .enumerate()
11658 .flat_map(move |(ix, (range, highlight_id))| {
11659 let style = if let Some(style) = highlight_id.style(syntax_theme) {
11660 style
11661 } else {
11662 return Default::default();
11663 };
11664 let mut muted_style = style;
11665 muted_style.highlight(fade_out);
11666
11667 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
11668 if range.start >= label.filter_range.end {
11669 if range.start > prev_end {
11670 runs.push((prev_end..range.start, fade_out));
11671 }
11672 runs.push((range.clone(), muted_style));
11673 } else if range.end <= label.filter_range.end {
11674 runs.push((range.clone(), style));
11675 } else {
11676 runs.push((range.start..label.filter_range.end, style));
11677 runs.push((label.filter_range.end..range.end, muted_style));
11678 }
11679 prev_end = cmp::max(prev_end, range.end);
11680
11681 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
11682 runs.push((prev_end..label.text.len(), fade_out));
11683 }
11684
11685 runs
11686 })
11687}
11688
11689pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
11690 let mut prev_index = 0;
11691 let mut prev_codepoint: Option<char> = None;
11692 text.char_indices()
11693 .chain([(text.len(), '\0')])
11694 .filter_map(move |(index, codepoint)| {
11695 let prev_codepoint = prev_codepoint.replace(codepoint)?;
11696 let is_boundary = index == text.len()
11697 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
11698 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
11699 if is_boundary {
11700 let chunk = &text[prev_index..index];
11701 prev_index = index;
11702 Some(chunk)
11703 } else {
11704 None
11705 }
11706 })
11707}
11708
11709trait RangeToAnchorExt {
11710 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
11711}
11712
11713impl<T: ToOffset> RangeToAnchorExt for Range<T> {
11714 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
11715 let start_offset = self.start.to_offset(snapshot);
11716 let end_offset = self.end.to_offset(snapshot);
11717 if start_offset == end_offset {
11718 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
11719 } else {
11720 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
11721 }
11722 }
11723}
11724
11725pub trait RowExt {
11726 fn as_f32(&self) -> f32;
11727
11728 fn next_row(&self) -> Self;
11729
11730 fn previous_row(&self) -> Self;
11731
11732 fn minus(&self, other: Self) -> u32;
11733}
11734
11735impl RowExt for DisplayRow {
11736 fn as_f32(&self) -> f32 {
11737 self.0 as f32
11738 }
11739
11740 fn next_row(&self) -> Self {
11741 Self(self.0 + 1)
11742 }
11743
11744 fn previous_row(&self) -> Self {
11745 Self(self.0.saturating_sub(1))
11746 }
11747
11748 fn minus(&self, other: Self) -> u32 {
11749 self.0 - other.0
11750 }
11751}
11752
11753impl RowExt for MultiBufferRow {
11754 fn as_f32(&self) -> f32 {
11755 self.0 as f32
11756 }
11757
11758 fn next_row(&self) -> Self {
11759 Self(self.0 + 1)
11760 }
11761
11762 fn previous_row(&self) -> Self {
11763 Self(self.0.saturating_sub(1))
11764 }
11765
11766 fn minus(&self, other: Self) -> u32 {
11767 self.0 - other.0
11768 }
11769}
11770
11771trait RowRangeExt {
11772 type Row;
11773
11774 fn len(&self) -> usize;
11775
11776 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
11777}
11778
11779impl RowRangeExt for Range<MultiBufferRow> {
11780 type Row = MultiBufferRow;
11781
11782 fn len(&self) -> usize {
11783 (self.end.0 - self.start.0) as usize
11784 }
11785
11786 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
11787 (self.start.0..self.end.0).map(MultiBufferRow)
11788 }
11789}
11790
11791impl RowRangeExt for Range<DisplayRow> {
11792 type Row = DisplayRow;
11793
11794 fn len(&self) -> usize {
11795 (self.end.0 - self.start.0) as usize
11796 }
11797
11798 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
11799 (self.start.0..self.end.0).map(DisplayRow)
11800 }
11801}
11802
11803fn hunk_status(hunk: &DiffHunk<MultiBufferRow>) -> DiffHunkStatus {
11804 if hunk.diff_base_byte_range.is_empty() {
11805 DiffHunkStatus::Added
11806 } else if hunk.associated_range.is_empty() {
11807 DiffHunkStatus::Removed
11808 } else {
11809 DiffHunkStatus::Modified
11810 }
11811}