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