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;
18mod debounced_delay;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25mod hover_popover;
26mod hunk_diff;
27mod indent_guides;
28mod inlay_hint_cache;
29mod inline_completion_provider;
30pub mod items;
31mod linked_editing_ranges;
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;
42mod signature_help;
43#[cfg(any(test, feature = "test-support"))]
44pub mod test;
45
46use ::git::diff::{DiffHunk, DiffHunkStatus};
47use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
48pub(crate) use actions::*;
49use aho_corasick::AhoCorasick;
50use anyhow::{anyhow, Context as _, Result};
51use blink_manager::BlinkManager;
52use client::{Collaborator, ParticipantIndex};
53use clock::ReplicaId;
54use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
55use convert_case::{Case, Casing};
56use debounced_delay::DebouncedDelay;
57use display_map::*;
58pub use display_map::{DisplayPoint, FoldPlaceholder};
59pub use editor_settings::{CurrentLineHighlight, EditorSettings};
60use element::LineWithInvisibles;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64use futures::FutureExt;
65use fuzzy::{StringMatch, StringMatchCandidate};
66use git::blame::GitBlame;
67use git::diff_hunk_to_display;
68use gpui::{
69 div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
70 AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
71 Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle, FocusOutEvent,
72 FocusableView, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText,
73 KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
74 SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
75 UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext,
76 WeakFocusHandle, WeakView, WhiteSpace, WindowContext,
77};
78use highlight_matching_bracket::refresh_matching_bracket_highlights;
79use hover_popover::{hide_hover, HoverState};
80use hunk_diff::ExpandedHunks;
81pub(crate) use hunk_diff::HunkToExpand;
82use indent_guides::ActiveIndentGuidesState;
83use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
84pub use inline_completion_provider::*;
85pub use items::MAX_TAB_TITLE_LEN;
86use itertools::Itertools;
87use language::{
88 char_kind,
89 language_settings::{self, all_language_settings, InlayHintSettings},
90 markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
91 CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
92 Point, Selection, SelectionGoal, TransactionId,
93};
94use language::{point_to_lsp, BufferRow, Runnable, RunnableRange};
95use linked_editing_ranges::refresh_linked_ranges;
96use task::{ResolvedTask, TaskTemplate, TaskVariables};
97
98use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
99pub use lsp::CompletionContext;
100use lsp::{
101 CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
102 LanguageServerId,
103};
104use mouse_context_menu::MouseContextMenu;
105use movement::TextLayoutDetails;
106pub use multi_buffer::{
107 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
108 ToPoint,
109};
110use multi_buffer::{ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16};
111use ordered_float::OrderedFloat;
112use parking_lot::{Mutex, RwLock};
113use project::project_settings::{GitGutterSetting, ProjectSettings};
114use project::{
115 CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath,
116 ProjectTransaction, TaskSourceKind, WorktreeId,
117};
118use rand::prelude::*;
119use rpc::{proto::*, ErrorExt};
120use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
121use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
122use serde::{Deserialize, Serialize};
123use settings::{update_settings_file, Settings, SettingsStore};
124use smallvec::SmallVec;
125use snippet::Snippet;
126use std::{
127 any::TypeId,
128 borrow::Cow,
129 cell::RefCell,
130 cmp::{self, Ordering, Reverse},
131 mem,
132 num::NonZeroU32,
133 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
134 path::{Path, PathBuf},
135 rc::Rc,
136 sync::Arc,
137 time::{Duration, Instant},
138};
139pub use sum_tree::Bias;
140use sum_tree::TreeMap;
141use text::{BufferId, OffsetUtf16, Rope};
142use theme::{
143 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
144 ThemeColors, ThemeSettings,
145};
146use ui::{
147 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize,
148 ListItem, Popover, Tooltip,
149};
150use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
151use workspace::item::{ItemHandle, PreviewTabsSettings};
152use workspace::notifications::{DetachAndPromptErr, NotificationId};
153use workspace::{
154 searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
155};
156use workspace::{OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
157
158use crate::hover_links::find_url;
159use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
160
161pub const FILE_HEADER_HEIGHT: u8 = 1;
162pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u8 = 1;
163pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u8 = 1;
164pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
165const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
166const MAX_LINE_LEN: usize = 1024;
167const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
168const MAX_SELECTION_HISTORY_LEN: usize = 1024;
169pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
170#[doc(hidden)]
171pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
172#[doc(hidden)]
173pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
174
175pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
176
177pub fn render_parsed_markdown(
178 element_id: impl Into<ElementId>,
179 parsed: &language::ParsedMarkdown,
180 editor_style: &EditorStyle,
181 workspace: Option<WeakView<Workspace>>,
182 cx: &mut WindowContext,
183) -> InteractiveText {
184 let code_span_background_color = cx
185 .theme()
186 .colors()
187 .editor_document_highlight_read_background;
188
189 let highlights = gpui::combine_highlights(
190 parsed.highlights.iter().filter_map(|(range, highlight)| {
191 let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
192 Some((range.clone(), highlight))
193 }),
194 parsed
195 .regions
196 .iter()
197 .zip(&parsed.region_ranges)
198 .filter_map(|(region, range)| {
199 if region.code {
200 Some((
201 range.clone(),
202 HighlightStyle {
203 background_color: Some(code_span_background_color),
204 ..Default::default()
205 },
206 ))
207 } else {
208 None
209 }
210 }),
211 );
212
213 let mut links = Vec::new();
214 let mut link_ranges = Vec::new();
215 for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
216 if let Some(link) = region.link.clone() {
217 links.push(link);
218 link_ranges.push(range.clone());
219 }
220 }
221
222 InteractiveText::new(
223 element_id,
224 StyledText::new(parsed.text.clone()).with_highlights(&editor_style.text, highlights),
225 )
226 .on_click(link_ranges, move |clicked_range_ix, cx| {
227 match &links[clicked_range_ix] {
228 markdown::Link::Web { url } => cx.open_url(url),
229 markdown::Link::Path { path } => {
230 if let Some(workspace) = &workspace {
231 _ = workspace.update(cx, |workspace, cx| {
232 workspace.open_abs_path(path.clone(), false, cx).detach();
233 });
234 }
235 }
236 }
237 })
238}
239
240#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
241pub(crate) enum InlayId {
242 Suggestion(usize),
243 Hint(usize),
244}
245
246impl InlayId {
247 fn id(&self) -> usize {
248 match self {
249 Self::Suggestion(id) => *id,
250 Self::Hint(id) => *id,
251 }
252 }
253}
254
255enum DiffRowHighlight {}
256enum DocumentHighlightRead {}
257enum DocumentHighlightWrite {}
258enum InputComposition {}
259
260#[derive(Copy, Clone, PartialEq, Eq)]
261pub enum Direction {
262 Prev,
263 Next,
264}
265
266pub fn init_settings(cx: &mut AppContext) {
267 EditorSettings::register(cx);
268}
269
270pub fn init(cx: &mut AppContext) {
271 init_settings(cx);
272
273 workspace::register_project_item::<Editor>(cx);
274 workspace::FollowableViewRegistry::register::<Editor>(cx);
275 workspace::register_serializable_item::<Editor>(cx);
276
277 cx.observe_new_views(
278 |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
279 workspace.register_action(Editor::new_file);
280 workspace.register_action(Editor::new_file_in_direction);
281 },
282 )
283 .detach();
284
285 cx.on_action(move |_: &workspace::NewFile, cx| {
286 let app_state = workspace::AppState::global(cx);
287 if let Some(app_state) = app_state.upgrade() {
288 workspace::open_new(app_state, cx, |workspace, cx| {
289 Editor::new_file(workspace, &Default::default(), cx)
290 })
291 .detach();
292 }
293 });
294 cx.on_action(move |_: &workspace::NewWindow, cx| {
295 let app_state = workspace::AppState::global(cx);
296 if let Some(app_state) = app_state.upgrade() {
297 workspace::open_new(app_state, cx, |workspace, cx| {
298 Editor::new_file(workspace, &Default::default(), cx)
299 })
300 .detach();
301 }
302 });
303}
304
305pub struct SearchWithinRange;
306
307trait InvalidationRegion {
308 fn ranges(&self) -> &[Range<Anchor>];
309}
310
311#[derive(Clone, Debug, PartialEq)]
312pub enum SelectPhase {
313 Begin {
314 position: DisplayPoint,
315 add: bool,
316 click_count: usize,
317 },
318 BeginColumnar {
319 position: DisplayPoint,
320 reset: bool,
321 goal_column: u32,
322 },
323 Extend {
324 position: DisplayPoint,
325 click_count: usize,
326 },
327 Update {
328 position: DisplayPoint,
329 goal_column: u32,
330 scroll_delta: gpui::Point<f32>,
331 },
332 End,
333}
334
335#[derive(Clone, Debug)]
336pub enum SelectMode {
337 Character,
338 Word(Range<Anchor>),
339 Line(Range<Anchor>),
340 All,
341}
342
343#[derive(Copy, Clone, PartialEq, Eq, Debug)]
344pub enum EditorMode {
345 SingleLine { auto_width: bool },
346 AutoHeight { max_lines: usize },
347 Full,
348}
349
350#[derive(Clone, Debug)]
351pub enum SoftWrap {
352 None,
353 PreferLine,
354 EditorWidth,
355 Column(u32),
356}
357
358#[derive(Clone)]
359pub struct EditorStyle {
360 pub background: Hsla,
361 pub local_player: PlayerColor,
362 pub text: TextStyle,
363 pub scrollbar_width: Pixels,
364 pub syntax: Arc<SyntaxTheme>,
365 pub status: StatusColors,
366 pub inlay_hints_style: HighlightStyle,
367 pub suggestions_style: HighlightStyle,
368}
369
370impl Default for EditorStyle {
371 fn default() -> Self {
372 Self {
373 background: Hsla::default(),
374 local_player: PlayerColor::default(),
375 text: TextStyle::default(),
376 scrollbar_width: Pixels::default(),
377 syntax: Default::default(),
378 // HACK: Status colors don't have a real default.
379 // We should look into removing the status colors from the editor
380 // style and retrieve them directly from the theme.
381 status: StatusColors::dark(),
382 inlay_hints_style: HighlightStyle::default(),
383 suggestions_style: HighlightStyle::default(),
384 }
385 }
386}
387
388type CompletionId = usize;
389
390#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
391struct EditorActionId(usize);
392
393impl EditorActionId {
394 pub fn post_inc(&mut self) -> Self {
395 let answer = self.0;
396
397 *self = Self(answer + 1);
398
399 Self(answer)
400 }
401}
402
403// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
404// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
405
406type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
407type GutterHighlight = (fn(&AppContext) -> Hsla, Arc<[Range<Anchor>]>);
408
409struct ScrollbarMarkerState {
410 scrollbar_size: Size<Pixels>,
411 dirty: bool,
412 markers: Arc<[PaintQuad]>,
413 pending_refresh: Option<Task<Result<()>>>,
414}
415
416impl ScrollbarMarkerState {
417 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
418 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
419 }
420}
421
422impl Default for ScrollbarMarkerState {
423 fn default() -> Self {
424 Self {
425 scrollbar_size: Size::default(),
426 dirty: false,
427 markers: Arc::from([]),
428 pending_refresh: None,
429 }
430 }
431}
432
433#[derive(Clone, Debug)]
434struct RunnableTasks {
435 templates: Vec<(TaskSourceKind, TaskTemplate)>,
436 offset: MultiBufferOffset,
437 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
438 column: u32,
439 // Values of all named captures, including those starting with '_'
440 extra_variables: HashMap<String, String>,
441 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
442 context_range: Range<BufferOffset>,
443}
444
445#[derive(Clone)]
446struct ResolvedTasks {
447 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
448 position: Anchor,
449}
450#[derive(Copy, Clone, Debug)]
451struct MultiBufferOffset(usize);
452#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
453struct BufferOffset(usize);
454/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
455///
456/// See the [module level documentation](self) for more information.
457pub struct Editor {
458 focus_handle: FocusHandle,
459 last_focused_descendant: Option<WeakFocusHandle>,
460 /// The text buffer being edited
461 buffer: Model<MultiBuffer>,
462 /// Map of how text in the buffer should be displayed.
463 /// Handles soft wraps, folds, fake inlay text insertions, etc.
464 pub display_map: Model<DisplayMap>,
465 pub selections: SelectionsCollection,
466 pub scroll_manager: ScrollManager,
467 /// When inline assist editors are linked, they all render cursors because
468 /// typing enters text into each of them, even the ones that aren't focused.
469 pub(crate) show_cursor_when_unfocused: bool,
470 columnar_selection_tail: Option<Anchor>,
471 add_selections_state: Option<AddSelectionsState>,
472 select_next_state: Option<SelectNextState>,
473 select_prev_state: Option<SelectNextState>,
474 selection_history: SelectionHistory,
475 autoclose_regions: Vec<AutocloseRegion>,
476 snippet_stack: InvalidationStack<SnippetState>,
477 select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
478 ime_transaction: Option<TransactionId>,
479 active_diagnostics: Option<ActiveDiagnosticGroup>,
480 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
481 project: Option<Model<Project>>,
482 completion_provider: Option<Box<dyn CompletionProvider>>,
483 collaboration_hub: Option<Box<dyn CollaborationHub>>,
484 blink_manager: Model<BlinkManager>,
485 show_cursor_names: bool,
486 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
487 pub show_local_selections: bool,
488 mode: EditorMode,
489 show_breadcrumbs: bool,
490 show_gutter: bool,
491 show_line_numbers: Option<bool>,
492 show_git_diff_gutter: Option<bool>,
493 show_code_actions: Option<bool>,
494 show_runnables: Option<bool>,
495 show_wrap_guides: Option<bool>,
496 show_indent_guides: Option<bool>,
497 placeholder_text: Option<Arc<str>>,
498 highlight_order: usize,
499 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
500 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
501 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
502 scrollbar_marker_state: ScrollbarMarkerState,
503 active_indent_guides_state: ActiveIndentGuidesState,
504 nav_history: Option<ItemNavHistory>,
505 context_menu: RwLock<Option<ContextMenu>>,
506 mouse_context_menu: Option<MouseContextMenu>,
507 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
508 signature_help_state: SignatureHelpState,
509 auto_signature_help: Option<bool>,
510 find_all_references_task_sources: Vec<Anchor>,
511 next_completion_id: CompletionId,
512 completion_documentation_pre_resolve_debounce: DebouncedDelay,
513 available_code_actions: Option<(Location, Arc<[CodeAction]>)>,
514 code_actions_task: Option<Task<()>>,
515 document_highlights_task: Option<Task<()>>,
516 linked_editing_range_task: Option<Task<Option<()>>>,
517 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
518 pending_rename: Option<RenameState>,
519 searchable: bool,
520 cursor_shape: CursorShape,
521 current_line_highlight: Option<CurrentLineHighlight>,
522 collapse_matches: bool,
523 autoindent_mode: Option<AutoindentMode>,
524 workspace: Option<(WeakView<Workspace>, Option<WorkspaceId>)>,
525 keymap_context_layers: BTreeMap<TypeId, KeyContext>,
526 input_enabled: bool,
527 use_modal_editing: bool,
528 read_only: bool,
529 leader_peer_id: Option<PeerId>,
530 remote_id: Option<ViewId>,
531 hover_state: HoverState,
532 gutter_hovered: bool,
533 hovered_link_state: Option<HoveredLinkState>,
534 inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
535 active_inline_completion: Option<Inlay>,
536 show_inline_completions: bool,
537 inlay_hint_cache: InlayHintCache,
538 expanded_hunks: ExpandedHunks,
539 next_inlay_id: usize,
540 _subscriptions: Vec<Subscription>,
541 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
542 gutter_dimensions: GutterDimensions,
543 pub vim_replace_map: HashMap<Range<usize>, String>,
544 style: Option<EditorStyle>,
545 next_editor_action_id: EditorActionId,
546 editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
547 use_autoclose: bool,
548 use_auto_surround: bool,
549 auto_replace_emoji_shortcode: bool,
550 show_git_blame_gutter: bool,
551 show_git_blame_inline: bool,
552 show_git_blame_inline_delay_task: Option<Task<()>>,
553 git_blame_inline_enabled: bool,
554 serialize_dirty_buffers: bool,
555 show_selection_menu: Option<bool>,
556 blame: Option<Model<GitBlame>>,
557 blame_subscription: Option<Subscription>,
558 custom_context_menu: Option<
559 Box<
560 dyn 'static
561 + Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
562 >,
563 >,
564 last_bounds: Option<Bounds<Pixels>>,
565 expect_bounds_change: Option<Bounds<Pixels>>,
566 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
567 tasks_update_task: Option<Task<()>>,
568 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
569 file_header_size: u8,
570 breadcrumb_header: Option<String>,
571}
572
573#[derive(Clone)]
574pub struct EditorSnapshot {
575 pub mode: EditorMode,
576 show_gutter: bool,
577 show_line_numbers: Option<bool>,
578 show_git_diff_gutter: Option<bool>,
579 show_code_actions: Option<bool>,
580 show_runnables: Option<bool>,
581 render_git_blame_gutter: bool,
582 pub display_snapshot: DisplaySnapshot,
583 pub placeholder_text: Option<Arc<str>>,
584 is_focused: bool,
585 scroll_anchor: ScrollAnchor,
586 ongoing_scroll: OngoingScroll,
587 current_line_highlight: CurrentLineHighlight,
588 gutter_hovered: bool,
589}
590
591const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
592
593#[derive(Debug, Clone, Copy)]
594pub struct GutterDimensions {
595 pub left_padding: Pixels,
596 pub right_padding: Pixels,
597 pub width: Pixels,
598 pub margin: Pixels,
599 pub git_blame_entries_width: Option<Pixels>,
600}
601
602impl GutterDimensions {
603 /// The full width of the space taken up by the gutter.
604 pub fn full_width(&self) -> Pixels {
605 self.margin + self.width
606 }
607
608 /// The width of the space reserved for the fold indicators,
609 /// use alongside 'justify_end' and `gutter_width` to
610 /// right align content with the line numbers
611 pub fn fold_area_width(&self) -> Pixels {
612 self.margin + self.right_padding
613 }
614}
615
616impl Default for GutterDimensions {
617 fn default() -> Self {
618 Self {
619 left_padding: Pixels::ZERO,
620 right_padding: Pixels::ZERO,
621 width: Pixels::ZERO,
622 margin: Pixels::ZERO,
623 git_blame_entries_width: None,
624 }
625 }
626}
627
628#[derive(Debug)]
629pub struct RemoteSelection {
630 pub replica_id: ReplicaId,
631 pub selection: Selection<Anchor>,
632 pub cursor_shape: CursorShape,
633 pub peer_id: PeerId,
634 pub line_mode: bool,
635 pub participant_index: Option<ParticipantIndex>,
636 pub user_name: Option<SharedString>,
637}
638
639#[derive(Clone, Debug)]
640struct SelectionHistoryEntry {
641 selections: Arc<[Selection<Anchor>]>,
642 select_next_state: Option<SelectNextState>,
643 select_prev_state: Option<SelectNextState>,
644 add_selections_state: Option<AddSelectionsState>,
645}
646
647enum SelectionHistoryMode {
648 Normal,
649 Undoing,
650 Redoing,
651}
652
653#[derive(Clone, PartialEq, Eq, Hash)]
654struct HoveredCursor {
655 replica_id: u16,
656 selection_id: usize,
657}
658
659impl Default for SelectionHistoryMode {
660 fn default() -> Self {
661 Self::Normal
662 }
663}
664
665#[derive(Default)]
666struct SelectionHistory {
667 #[allow(clippy::type_complexity)]
668 selections_by_transaction:
669 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
670 mode: SelectionHistoryMode,
671 undo_stack: VecDeque<SelectionHistoryEntry>,
672 redo_stack: VecDeque<SelectionHistoryEntry>,
673}
674
675impl SelectionHistory {
676 fn insert_transaction(
677 &mut self,
678 transaction_id: TransactionId,
679 selections: Arc<[Selection<Anchor>]>,
680 ) {
681 self.selections_by_transaction
682 .insert(transaction_id, (selections, None));
683 }
684
685 #[allow(clippy::type_complexity)]
686 fn transaction(
687 &self,
688 transaction_id: TransactionId,
689 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
690 self.selections_by_transaction.get(&transaction_id)
691 }
692
693 #[allow(clippy::type_complexity)]
694 fn transaction_mut(
695 &mut self,
696 transaction_id: TransactionId,
697 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
698 self.selections_by_transaction.get_mut(&transaction_id)
699 }
700
701 fn push(&mut self, entry: SelectionHistoryEntry) {
702 if !entry.selections.is_empty() {
703 match self.mode {
704 SelectionHistoryMode::Normal => {
705 self.push_undo(entry);
706 self.redo_stack.clear();
707 }
708 SelectionHistoryMode::Undoing => self.push_redo(entry),
709 SelectionHistoryMode::Redoing => self.push_undo(entry),
710 }
711 }
712 }
713
714 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
715 if self
716 .undo_stack
717 .back()
718 .map_or(true, |e| e.selections != entry.selections)
719 {
720 self.undo_stack.push_back(entry);
721 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
722 self.undo_stack.pop_front();
723 }
724 }
725 }
726
727 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
728 if self
729 .redo_stack
730 .back()
731 .map_or(true, |e| e.selections != entry.selections)
732 {
733 self.redo_stack.push_back(entry);
734 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
735 self.redo_stack.pop_front();
736 }
737 }
738 }
739}
740
741struct RowHighlight {
742 index: usize,
743 range: RangeInclusive<Anchor>,
744 color: Option<Hsla>,
745 should_autoscroll: bool,
746}
747
748#[derive(Clone, Debug)]
749struct AddSelectionsState {
750 above: bool,
751 stack: Vec<usize>,
752}
753
754#[derive(Clone)]
755struct SelectNextState {
756 query: AhoCorasick,
757 wordwise: bool,
758 done: bool,
759}
760
761impl std::fmt::Debug for SelectNextState {
762 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
763 f.debug_struct(std::any::type_name::<Self>())
764 .field("wordwise", &self.wordwise)
765 .field("done", &self.done)
766 .finish()
767 }
768}
769
770#[derive(Debug)]
771struct AutocloseRegion {
772 selection_id: usize,
773 range: Range<Anchor>,
774 pair: BracketPair,
775}
776
777#[derive(Debug)]
778struct SnippetState {
779 ranges: Vec<Vec<Range<Anchor>>>,
780 active_index: usize,
781}
782
783#[doc(hidden)]
784pub struct RenameState {
785 pub range: Range<Anchor>,
786 pub old_name: Arc<str>,
787 pub editor: View<Editor>,
788 block_id: BlockId,
789}
790
791struct InvalidationStack<T>(Vec<T>);
792
793struct RegisteredInlineCompletionProvider {
794 provider: Arc<dyn InlineCompletionProviderHandle>,
795 _subscription: Subscription,
796}
797
798enum ContextMenu {
799 Completions(CompletionsMenu),
800 CodeActions(CodeActionsMenu),
801}
802
803impl ContextMenu {
804 fn select_first(
805 &mut self,
806 project: Option<&Model<Project>>,
807 cx: &mut ViewContext<Editor>,
808 ) -> bool {
809 if self.visible() {
810 match self {
811 ContextMenu::Completions(menu) => menu.select_first(project, cx),
812 ContextMenu::CodeActions(menu) => menu.select_first(cx),
813 }
814 true
815 } else {
816 false
817 }
818 }
819
820 fn select_prev(
821 &mut self,
822 project: Option<&Model<Project>>,
823 cx: &mut ViewContext<Editor>,
824 ) -> bool {
825 if self.visible() {
826 match self {
827 ContextMenu::Completions(menu) => menu.select_prev(project, cx),
828 ContextMenu::CodeActions(menu) => menu.select_prev(cx),
829 }
830 true
831 } else {
832 false
833 }
834 }
835
836 fn select_next(
837 &mut self,
838 project: Option<&Model<Project>>,
839 cx: &mut ViewContext<Editor>,
840 ) -> bool {
841 if self.visible() {
842 match self {
843 ContextMenu::Completions(menu) => menu.select_next(project, cx),
844 ContextMenu::CodeActions(menu) => menu.select_next(cx),
845 }
846 true
847 } else {
848 false
849 }
850 }
851
852 fn select_last(
853 &mut self,
854 project: Option<&Model<Project>>,
855 cx: &mut ViewContext<Editor>,
856 ) -> bool {
857 if self.visible() {
858 match self {
859 ContextMenu::Completions(menu) => menu.select_last(project, cx),
860 ContextMenu::CodeActions(menu) => menu.select_last(cx),
861 }
862 true
863 } else {
864 false
865 }
866 }
867
868 fn visible(&self) -> bool {
869 match self {
870 ContextMenu::Completions(menu) => menu.visible(),
871 ContextMenu::CodeActions(menu) => menu.visible(),
872 }
873 }
874
875 fn render(
876 &self,
877 cursor_position: DisplayPoint,
878 style: &EditorStyle,
879 max_height: Pixels,
880 workspace: Option<WeakView<Workspace>>,
881 cx: &mut ViewContext<Editor>,
882 ) -> (ContextMenuOrigin, AnyElement) {
883 match self {
884 ContextMenu::Completions(menu) => (
885 ContextMenuOrigin::EditorPoint(cursor_position),
886 menu.render(style, max_height, workspace, cx),
887 ),
888 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
889 }
890 }
891}
892
893enum ContextMenuOrigin {
894 EditorPoint(DisplayPoint),
895 GutterIndicator(DisplayRow),
896}
897
898#[derive(Clone)]
899struct CompletionsMenu {
900 id: CompletionId,
901 initial_position: Anchor,
902 buffer: Model<Buffer>,
903 completions: Arc<RwLock<Box<[Completion]>>>,
904 match_candidates: Arc<[StringMatchCandidate]>,
905 matches: Arc<[StringMatch]>,
906 selected_item: usize,
907 scroll_handle: UniformListScrollHandle,
908 selected_completion_documentation_resolve_debounce: Arc<Mutex<DebouncedDelay>>,
909}
910
911impl CompletionsMenu {
912 fn select_first(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
913 self.selected_item = 0;
914 self.scroll_handle.scroll_to_item(self.selected_item);
915 self.attempt_resolve_selected_completion_documentation(project, cx);
916 cx.notify();
917 }
918
919 fn select_prev(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
920 if self.selected_item > 0 {
921 self.selected_item -= 1;
922 } else {
923 self.selected_item = self.matches.len() - 1;
924 }
925 self.scroll_handle.scroll_to_item(self.selected_item);
926 self.attempt_resolve_selected_completion_documentation(project, cx);
927 cx.notify();
928 }
929
930 fn select_next(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
931 if self.selected_item + 1 < self.matches.len() {
932 self.selected_item += 1;
933 } else {
934 self.selected_item = 0;
935 }
936 self.scroll_handle.scroll_to_item(self.selected_item);
937 self.attempt_resolve_selected_completion_documentation(project, cx);
938 cx.notify();
939 }
940
941 fn select_last(&mut self, project: Option<&Model<Project>>, cx: &mut ViewContext<Editor>) {
942 self.selected_item = self.matches.len() - 1;
943 self.scroll_handle.scroll_to_item(self.selected_item);
944 self.attempt_resolve_selected_completion_documentation(project, cx);
945 cx.notify();
946 }
947
948 fn pre_resolve_completion_documentation(
949 buffer: Model<Buffer>,
950 completions: Arc<RwLock<Box<[Completion]>>>,
951 matches: Arc<[StringMatch]>,
952 editor: &Editor,
953 cx: &mut ViewContext<Editor>,
954 ) -> Task<()> {
955 let settings = EditorSettings::get_global(cx);
956 if !settings.show_completion_documentation {
957 return Task::ready(());
958 }
959
960 let Some(provider) = editor.completion_provider.as_ref() else {
961 return Task::ready(());
962 };
963
964 let resolve_task = provider.resolve_completions(
965 buffer,
966 matches.iter().map(|m| m.candidate_id).collect(),
967 completions.clone(),
968 cx,
969 );
970
971 return cx.spawn(move |this, mut cx| async move {
972 if let Some(true) = resolve_task.await.log_err() {
973 this.update(&mut cx, |_, cx| cx.notify()).ok();
974 }
975 });
976 }
977
978 fn attempt_resolve_selected_completion_documentation(
979 &mut self,
980 project: Option<&Model<Project>>,
981 cx: &mut ViewContext<Editor>,
982 ) {
983 let settings = EditorSettings::get_global(cx);
984 if !settings.show_completion_documentation {
985 return;
986 }
987
988 let completion_index = self.matches[self.selected_item].candidate_id;
989 let Some(project) = project else {
990 return;
991 };
992
993 let resolve_task = project.update(cx, |project, cx| {
994 project.resolve_completions(
995 self.buffer.clone(),
996 vec![completion_index],
997 self.completions.clone(),
998 cx,
999 )
1000 });
1001
1002 let delay_ms =
1003 EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
1004 let delay = Duration::from_millis(delay_ms);
1005
1006 self.selected_completion_documentation_resolve_debounce
1007 .lock()
1008 .fire_new(delay, cx, |_, cx| {
1009 cx.spawn(move |this, mut cx| async move {
1010 if let Some(true) = resolve_task.await.log_err() {
1011 this.update(&mut cx, |_, cx| cx.notify()).ok();
1012 }
1013 })
1014 });
1015 }
1016
1017 fn visible(&self) -> bool {
1018 !self.matches.is_empty()
1019 }
1020
1021 fn render(
1022 &self,
1023 style: &EditorStyle,
1024 max_height: Pixels,
1025 workspace: Option<WeakView<Workspace>>,
1026 cx: &mut ViewContext<Editor>,
1027 ) -> AnyElement {
1028 let settings = EditorSettings::get_global(cx);
1029 let show_completion_documentation = settings.show_completion_documentation;
1030
1031 let widest_completion_ix = self
1032 .matches
1033 .iter()
1034 .enumerate()
1035 .max_by_key(|(_, mat)| {
1036 let completions = self.completions.read();
1037 let completion = &completions[mat.candidate_id];
1038 let documentation = &completion.documentation;
1039
1040 let mut len = completion.label.text.chars().count();
1041 if let Some(Documentation::SingleLine(text)) = documentation {
1042 if show_completion_documentation {
1043 len += text.chars().count();
1044 }
1045 }
1046
1047 len
1048 })
1049 .map(|(ix, _)| ix);
1050
1051 let completions = self.completions.clone();
1052 let matches = self.matches.clone();
1053 let selected_item = self.selected_item;
1054 let style = style.clone();
1055
1056 let multiline_docs = if show_completion_documentation {
1057 let mat = &self.matches[selected_item];
1058 let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
1059 Some(Documentation::MultiLinePlainText(text)) => {
1060 Some(div().child(SharedString::from(text.clone())))
1061 }
1062 Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
1063 Some(div().child(render_parsed_markdown(
1064 "completions_markdown",
1065 parsed,
1066 &style,
1067 workspace,
1068 cx,
1069 )))
1070 }
1071 _ => None,
1072 };
1073 multiline_docs.map(|div| {
1074 div.id("multiline_docs")
1075 .max_h(max_height)
1076 .flex_1()
1077 .px_1p5()
1078 .py_1()
1079 .min_w(px(260.))
1080 .max_w(px(640.))
1081 .w(px(500.))
1082 .overflow_y_scroll()
1083 .occlude()
1084 })
1085 } else {
1086 None
1087 };
1088
1089 let list = uniform_list(
1090 cx.view().clone(),
1091 "completions",
1092 matches.len(),
1093 move |_editor, range, cx| {
1094 let start_ix = range.start;
1095 let completions_guard = completions.read();
1096
1097 matches[range]
1098 .iter()
1099 .enumerate()
1100 .map(|(ix, mat)| {
1101 let item_ix = start_ix + ix;
1102 let candidate_id = mat.candidate_id;
1103 let completion = &completions_guard[candidate_id];
1104
1105 let documentation = if show_completion_documentation {
1106 &completion.documentation
1107 } else {
1108 &None
1109 };
1110
1111 let highlights = gpui::combine_highlights(
1112 mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
1113 styled_runs_for_code_label(&completion.label, &style.syntax).map(
1114 |(range, mut highlight)| {
1115 // Ignore font weight for syntax highlighting, as we'll use it
1116 // for fuzzy matches.
1117 highlight.font_weight = None;
1118
1119 if completion.lsp_completion.deprecated.unwrap_or(false) {
1120 highlight.strikethrough = Some(StrikethroughStyle {
1121 thickness: 1.0.into(),
1122 ..Default::default()
1123 });
1124 highlight.color = Some(cx.theme().colors().text_muted);
1125 }
1126
1127 (range, highlight)
1128 },
1129 ),
1130 );
1131 let completion_label = StyledText::new(completion.label.text.clone())
1132 .with_highlights(&style.text, highlights);
1133 let documentation_label =
1134 if let Some(Documentation::SingleLine(text)) = documentation {
1135 if text.trim().is_empty() {
1136 None
1137 } else {
1138 Some(
1139 Label::new(text.clone())
1140 .ml_4()
1141 .size(LabelSize::Small)
1142 .color(Color::Muted),
1143 )
1144 }
1145 } else {
1146 None
1147 };
1148
1149 div().min_w(px(220.)).max_w(px(540.)).child(
1150 ListItem::new(mat.candidate_id)
1151 .inset(true)
1152 .selected(item_ix == selected_item)
1153 .on_click(cx.listener(move |editor, _event, cx| {
1154 cx.stop_propagation();
1155 if let Some(task) = editor.confirm_completion(
1156 &ConfirmCompletion {
1157 item_ix: Some(item_ix),
1158 },
1159 cx,
1160 ) {
1161 task.detach_and_log_err(cx)
1162 }
1163 }))
1164 .child(h_flex().overflow_hidden().child(completion_label))
1165 .end_slot::<Label>(documentation_label),
1166 )
1167 })
1168 .collect()
1169 },
1170 )
1171 .occlude()
1172 .max_h(max_height)
1173 .track_scroll(self.scroll_handle.clone())
1174 .with_width_from_item(widest_completion_ix)
1175 .with_sizing_behavior(ListSizingBehavior::Infer);
1176
1177 Popover::new()
1178 .child(list)
1179 .when_some(multiline_docs, |popover, multiline_docs| {
1180 popover.aside(multiline_docs)
1181 })
1182 .into_any_element()
1183 }
1184
1185 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
1186 let mut matches = if let Some(query) = query {
1187 fuzzy::match_strings(
1188 &self.match_candidates,
1189 query,
1190 query.chars().any(|c| c.is_uppercase()),
1191 100,
1192 &Default::default(),
1193 executor,
1194 )
1195 .await
1196 } else {
1197 self.match_candidates
1198 .iter()
1199 .enumerate()
1200 .map(|(candidate_id, candidate)| StringMatch {
1201 candidate_id,
1202 score: Default::default(),
1203 positions: Default::default(),
1204 string: candidate.string.clone(),
1205 })
1206 .collect()
1207 };
1208
1209 // Remove all candidates where the query's start does not match the start of any word in the candidate
1210 if let Some(query) = query {
1211 if let Some(query_start) = query.chars().next() {
1212 matches.retain(|string_match| {
1213 split_words(&string_match.string).any(|word| {
1214 // Check that the first codepoint of the word as lowercase matches the first
1215 // codepoint of the query as lowercase
1216 word.chars()
1217 .flat_map(|codepoint| codepoint.to_lowercase())
1218 .zip(query_start.to_lowercase())
1219 .all(|(word_cp, query_cp)| word_cp == query_cp)
1220 })
1221 });
1222 }
1223 }
1224
1225 let completions = self.completions.read();
1226 matches.sort_unstable_by_key(|mat| {
1227 // We do want to strike a balance here between what the language server tells us
1228 // to sort by (the sort_text) and what are "obvious" good matches (i.e. when you type
1229 // `Creat` and there is a local variable called `CreateComponent`).
1230 // So what we do is: we bucket all matches into two buckets
1231 // - Strong matches
1232 // - Weak matches
1233 // Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches)
1234 // and the Weak matches are the rest.
1235 //
1236 // For the strong matches, we sort by the language-servers score first and for the weak
1237 // matches, we prefer our fuzzy finder first.
1238 //
1239 // The thinking behind that: it's useless to take the sort_text the language-server gives
1240 // us into account when it's obviously a bad match.
1241
1242 #[derive(PartialEq, Eq, PartialOrd, Ord)]
1243 enum MatchScore<'a> {
1244 Strong {
1245 sort_text: Option<&'a str>,
1246 score: Reverse<OrderedFloat<f64>>,
1247 sort_key: (usize, &'a str),
1248 },
1249 Weak {
1250 score: Reverse<OrderedFloat<f64>>,
1251 sort_text: Option<&'a str>,
1252 sort_key: (usize, &'a str),
1253 },
1254 }
1255
1256 let completion = &completions[mat.candidate_id];
1257 let sort_key = completion.sort_key();
1258 let sort_text = completion.lsp_completion.sort_text.as_deref();
1259 let score = Reverse(OrderedFloat(mat.score));
1260
1261 if mat.score >= 0.2 {
1262 MatchScore::Strong {
1263 sort_text,
1264 score,
1265 sort_key,
1266 }
1267 } else {
1268 MatchScore::Weak {
1269 score,
1270 sort_text,
1271 sort_key,
1272 }
1273 }
1274 });
1275
1276 for mat in &mut matches {
1277 let completion = &completions[mat.candidate_id];
1278 mat.string.clone_from(&completion.label.text);
1279 for position in &mut mat.positions {
1280 *position += completion.label.filter_range.start;
1281 }
1282 }
1283 drop(completions);
1284
1285 self.matches = matches.into();
1286 self.selected_item = 0;
1287 }
1288}
1289
1290#[derive(Clone)]
1291struct CodeActionContents {
1292 tasks: Option<Arc<ResolvedTasks>>,
1293 actions: Option<Arc<[CodeAction]>>,
1294}
1295
1296impl CodeActionContents {
1297 fn len(&self) -> usize {
1298 match (&self.tasks, &self.actions) {
1299 (Some(tasks), Some(actions)) => actions.len() + tasks.templates.len(),
1300 (Some(tasks), None) => tasks.templates.len(),
1301 (None, Some(actions)) => actions.len(),
1302 (None, None) => 0,
1303 }
1304 }
1305
1306 fn is_empty(&self) -> bool {
1307 match (&self.tasks, &self.actions) {
1308 (Some(tasks), Some(actions)) => actions.is_empty() && tasks.templates.is_empty(),
1309 (Some(tasks), None) => tasks.templates.is_empty(),
1310 (None, Some(actions)) => actions.is_empty(),
1311 (None, None) => true,
1312 }
1313 }
1314
1315 fn iter(&self) -> impl Iterator<Item = CodeActionsItem> + '_ {
1316 self.tasks
1317 .iter()
1318 .flat_map(|tasks| {
1319 tasks
1320 .templates
1321 .iter()
1322 .map(|(kind, task)| CodeActionsItem::Task(kind.clone(), task.clone()))
1323 })
1324 .chain(self.actions.iter().flat_map(|actions| {
1325 actions
1326 .iter()
1327 .map(|action| CodeActionsItem::CodeAction(action.clone()))
1328 }))
1329 }
1330 fn get(&self, index: usize) -> Option<CodeActionsItem> {
1331 match (&self.tasks, &self.actions) {
1332 (Some(tasks), Some(actions)) => {
1333 if index < tasks.templates.len() {
1334 tasks
1335 .templates
1336 .get(index)
1337 .cloned()
1338 .map(|(kind, task)| CodeActionsItem::Task(kind, task))
1339 } else {
1340 actions
1341 .get(index - tasks.templates.len())
1342 .cloned()
1343 .map(CodeActionsItem::CodeAction)
1344 }
1345 }
1346 (Some(tasks), None) => tasks
1347 .templates
1348 .get(index)
1349 .cloned()
1350 .map(|(kind, task)| CodeActionsItem::Task(kind, task)),
1351 (None, Some(actions)) => actions.get(index).cloned().map(CodeActionsItem::CodeAction),
1352 (None, None) => None,
1353 }
1354 }
1355}
1356
1357#[allow(clippy::large_enum_variant)]
1358#[derive(Clone)]
1359enum CodeActionsItem {
1360 Task(TaskSourceKind, ResolvedTask),
1361 CodeAction(CodeAction),
1362}
1363
1364impl CodeActionsItem {
1365 fn as_task(&self) -> Option<&ResolvedTask> {
1366 let Self::Task(_, task) = self else {
1367 return None;
1368 };
1369 Some(task)
1370 }
1371 fn as_code_action(&self) -> Option<&CodeAction> {
1372 let Self::CodeAction(action) = self else {
1373 return None;
1374 };
1375 Some(action)
1376 }
1377 fn label(&self) -> String {
1378 match self {
1379 Self::CodeAction(action) => action.lsp_action.title.clone(),
1380 Self::Task(_, task) => task.resolved_label.clone(),
1381 }
1382 }
1383}
1384
1385struct CodeActionsMenu {
1386 actions: CodeActionContents,
1387 buffer: Model<Buffer>,
1388 selected_item: usize,
1389 scroll_handle: UniformListScrollHandle,
1390 deployed_from_indicator: Option<DisplayRow>,
1391}
1392
1393impl CodeActionsMenu {
1394 fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
1395 self.selected_item = 0;
1396 self.scroll_handle.scroll_to_item(self.selected_item);
1397 cx.notify()
1398 }
1399
1400 fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
1401 if self.selected_item > 0 {
1402 self.selected_item -= 1;
1403 } else {
1404 self.selected_item = self.actions.len() - 1;
1405 }
1406 self.scroll_handle.scroll_to_item(self.selected_item);
1407 cx.notify();
1408 }
1409
1410 fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
1411 if self.selected_item + 1 < self.actions.len() {
1412 self.selected_item += 1;
1413 } else {
1414 self.selected_item = 0;
1415 }
1416 self.scroll_handle.scroll_to_item(self.selected_item);
1417 cx.notify();
1418 }
1419
1420 fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
1421 self.selected_item = self.actions.len() - 1;
1422 self.scroll_handle.scroll_to_item(self.selected_item);
1423 cx.notify()
1424 }
1425
1426 fn visible(&self) -> bool {
1427 !self.actions.is_empty()
1428 }
1429
1430 fn render(
1431 &self,
1432 cursor_position: DisplayPoint,
1433 _style: &EditorStyle,
1434 max_height: Pixels,
1435 cx: &mut ViewContext<Editor>,
1436 ) -> (ContextMenuOrigin, AnyElement) {
1437 let actions = self.actions.clone();
1438 let selected_item = self.selected_item;
1439 let element = uniform_list(
1440 cx.view().clone(),
1441 "code_actions_menu",
1442 self.actions.len(),
1443 move |_this, range, cx| {
1444 actions
1445 .iter()
1446 .skip(range.start)
1447 .take(range.end - range.start)
1448 .enumerate()
1449 .map(|(ix, action)| {
1450 let item_ix = range.start + ix;
1451 let selected = selected_item == item_ix;
1452 let colors = cx.theme().colors();
1453 div()
1454 .px_2()
1455 .text_color(colors.text)
1456 .when(selected, |style| {
1457 style
1458 .bg(colors.element_active)
1459 .text_color(colors.text_accent)
1460 })
1461 .hover(|style| {
1462 style
1463 .bg(colors.element_hover)
1464 .text_color(colors.text_accent)
1465 })
1466 .whitespace_nowrap()
1467 .when_some(action.as_code_action(), |this, action| {
1468 this.on_mouse_down(
1469 MouseButton::Left,
1470 cx.listener(move |editor, _, cx| {
1471 cx.stop_propagation();
1472 if let Some(task) = editor.confirm_code_action(
1473 &ConfirmCodeAction {
1474 item_ix: Some(item_ix),
1475 },
1476 cx,
1477 ) {
1478 task.detach_and_log_err(cx)
1479 }
1480 }),
1481 )
1482 // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
1483 .child(SharedString::from(action.lsp_action.title.clone()))
1484 })
1485 .when_some(action.as_task(), |this, task| {
1486 this.on_mouse_down(
1487 MouseButton::Left,
1488 cx.listener(move |editor, _, cx| {
1489 cx.stop_propagation();
1490 if let Some(task) = editor.confirm_code_action(
1491 &ConfirmCodeAction {
1492 item_ix: Some(item_ix),
1493 },
1494 cx,
1495 ) {
1496 task.detach_and_log_err(cx)
1497 }
1498 }),
1499 )
1500 .child(SharedString::from(task.resolved_label.clone()))
1501 })
1502 })
1503 .collect()
1504 },
1505 )
1506 .elevation_1(cx)
1507 .px_2()
1508 .py_1()
1509 .max_h(max_height)
1510 .occlude()
1511 .track_scroll(self.scroll_handle.clone())
1512 .with_width_from_item(
1513 self.actions
1514 .iter()
1515 .enumerate()
1516 .max_by_key(|(_, action)| match action {
1517 CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(),
1518 CodeActionsItem::CodeAction(action) => action.lsp_action.title.chars().count(),
1519 })
1520 .map(|(ix, _)| ix),
1521 )
1522 .with_sizing_behavior(ListSizingBehavior::Infer)
1523 .into_any_element();
1524
1525 let cursor_position = if let Some(row) = self.deployed_from_indicator {
1526 ContextMenuOrigin::GutterIndicator(row)
1527 } else {
1528 ContextMenuOrigin::EditorPoint(cursor_position)
1529 };
1530
1531 (cursor_position, element)
1532 }
1533}
1534
1535#[derive(Debug)]
1536struct ActiveDiagnosticGroup {
1537 primary_range: Range<Anchor>,
1538 primary_message: String,
1539 group_id: usize,
1540 blocks: HashMap<BlockId, Diagnostic>,
1541 is_valid: bool,
1542}
1543
1544#[derive(Serialize, Deserialize, Clone, Debug)]
1545pub struct ClipboardSelection {
1546 pub len: usize,
1547 pub is_entire_line: bool,
1548 pub first_line_indent: u32,
1549}
1550
1551#[derive(Debug)]
1552pub(crate) struct NavigationData {
1553 cursor_anchor: Anchor,
1554 cursor_position: Point,
1555 scroll_anchor: ScrollAnchor,
1556 scroll_top_row: u32,
1557}
1558
1559enum GotoDefinitionKind {
1560 Symbol,
1561 Type,
1562 Implementation,
1563}
1564
1565#[derive(Debug, Clone)]
1566enum InlayHintRefreshReason {
1567 Toggle(bool),
1568 SettingsChange(InlayHintSettings),
1569 NewLinesShown,
1570 BufferEdited(HashSet<Arc<Language>>),
1571 RefreshRequested,
1572 ExcerptsRemoved(Vec<ExcerptId>),
1573}
1574
1575impl InlayHintRefreshReason {
1576 fn description(&self) -> &'static str {
1577 match self {
1578 Self::Toggle(_) => "toggle",
1579 Self::SettingsChange(_) => "settings change",
1580 Self::NewLinesShown => "new lines shown",
1581 Self::BufferEdited(_) => "buffer edited",
1582 Self::RefreshRequested => "refresh requested",
1583 Self::ExcerptsRemoved(_) => "excerpts removed",
1584 }
1585 }
1586}
1587
1588impl Editor {
1589 pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
1590 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1591 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1592 Self::new(
1593 EditorMode::SingleLine { auto_width: false },
1594 buffer,
1595 None,
1596 false,
1597 cx,
1598 )
1599 }
1600
1601 pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
1602 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1603 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1604 Self::new(EditorMode::Full, buffer, None, false, cx)
1605 }
1606
1607 pub fn auto_width(cx: &mut ViewContext<Self>) -> Self {
1608 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1609 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1610 Self::new(
1611 EditorMode::SingleLine { auto_width: true },
1612 buffer,
1613 None,
1614 false,
1615 cx,
1616 )
1617 }
1618
1619 pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
1620 let buffer = cx.new_model(|cx| Buffer::local("", cx));
1621 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1622 Self::new(
1623 EditorMode::AutoHeight { max_lines },
1624 buffer,
1625 None,
1626 false,
1627 cx,
1628 )
1629 }
1630
1631 pub fn for_buffer(
1632 buffer: Model<Buffer>,
1633 project: Option<Model<Project>>,
1634 cx: &mut ViewContext<Self>,
1635 ) -> Self {
1636 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1637 Self::new(EditorMode::Full, buffer, project, false, cx)
1638 }
1639
1640 pub fn for_multibuffer(
1641 buffer: Model<MultiBuffer>,
1642 project: Option<Model<Project>>,
1643 show_excerpt_controls: bool,
1644 cx: &mut ViewContext<Self>,
1645 ) -> Self {
1646 Self::new(EditorMode::Full, buffer, project, show_excerpt_controls, cx)
1647 }
1648
1649 pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
1650 let show_excerpt_controls = self.display_map.read(cx).show_excerpt_controls();
1651 let mut clone = Self::new(
1652 self.mode,
1653 self.buffer.clone(),
1654 self.project.clone(),
1655 show_excerpt_controls,
1656 cx,
1657 );
1658 self.display_map.update(cx, |display_map, cx| {
1659 let snapshot = display_map.snapshot(cx);
1660 clone.display_map.update(cx, |display_map, cx| {
1661 display_map.set_state(&snapshot, cx);
1662 });
1663 });
1664 clone.selections.clone_state(&self.selections);
1665 clone.scroll_manager.clone_state(&self.scroll_manager);
1666 clone.searchable = self.searchable;
1667 clone
1668 }
1669
1670 pub fn new(
1671 mode: EditorMode,
1672 buffer: Model<MultiBuffer>,
1673 project: Option<Model<Project>>,
1674 show_excerpt_controls: bool,
1675 cx: &mut ViewContext<Self>,
1676 ) -> Self {
1677 let style = cx.text_style();
1678 let font_size = style.font_size.to_pixels(cx.rem_size());
1679 let editor = cx.view().downgrade();
1680 let fold_placeholder = FoldPlaceholder {
1681 constrain_width: true,
1682 render: Arc::new(move |fold_id, fold_range, cx| {
1683 let editor = editor.clone();
1684 div()
1685 .id(fold_id)
1686 .bg(cx.theme().colors().ghost_element_background)
1687 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1688 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1689 .rounded_sm()
1690 .size_full()
1691 .cursor_pointer()
1692 .child("⋯")
1693 .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
1694 .on_click(move |_, cx| {
1695 editor
1696 .update(cx, |editor, cx| {
1697 editor.unfold_ranges(
1698 [fold_range.start..fold_range.end],
1699 true,
1700 false,
1701 cx,
1702 );
1703 cx.stop_propagation();
1704 })
1705 .ok();
1706 })
1707 .into_any()
1708 }),
1709 merge_adjacent: true,
1710 };
1711 let file_header_size = if show_excerpt_controls { 3 } else { 2 };
1712 let display_map = cx.new_model(|cx| {
1713 DisplayMap::new(
1714 buffer.clone(),
1715 style.font(),
1716 font_size,
1717 None,
1718 show_excerpt_controls,
1719 file_header_size,
1720 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1721 MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
1722 fold_placeholder,
1723 cx,
1724 )
1725 });
1726
1727 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1728
1729 let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1730
1731 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1732 .then(|| language_settings::SoftWrap::PreferLine);
1733
1734 let mut project_subscriptions = Vec::new();
1735 if mode == EditorMode::Full {
1736 if let Some(project) = project.as_ref() {
1737 if buffer.read(cx).is_singleton() {
1738 project_subscriptions.push(cx.observe(project, |_, _, cx| {
1739 cx.emit(EditorEvent::TitleChanged);
1740 }));
1741 }
1742 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
1743 if let project::Event::RefreshInlayHints = event {
1744 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1745 } else if let project::Event::SnippetEdit(id, snippet_edits) = event {
1746 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1747 let focus_handle = editor.focus_handle(cx);
1748 if focus_handle.is_focused(cx) {
1749 let snapshot = buffer.read(cx).snapshot();
1750 for (range, snippet) in snippet_edits {
1751 let editor_range =
1752 language::range_from_lsp(*range).to_offset(&snapshot);
1753 editor
1754 .insert_snippet(&[editor_range], snippet.clone(), cx)
1755 .ok();
1756 }
1757 }
1758 }
1759 }
1760 }));
1761 let task_inventory = project.read(cx).task_inventory().clone();
1762 project_subscriptions.push(cx.observe(&task_inventory, |editor, _, cx| {
1763 editor.tasks_update_task = Some(editor.refresh_runnables(cx));
1764 }));
1765 }
1766 }
1767
1768 let inlay_hint_settings = inlay_hint_settings(
1769 selections.newest_anchor().head(),
1770 &buffer.read(cx).snapshot(cx),
1771 cx,
1772 );
1773 let focus_handle = cx.focus_handle();
1774 cx.on_focus(&focus_handle, Self::handle_focus).detach();
1775 cx.on_focus_in(&focus_handle, Self::handle_focus_in)
1776 .detach();
1777 cx.on_focus_out(&focus_handle, Self::handle_focus_out)
1778 .detach();
1779 cx.on_blur(&focus_handle, Self::handle_blur).detach();
1780
1781 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1782 Some(false)
1783 } else {
1784 None
1785 };
1786
1787 let mut this = Self {
1788 focus_handle,
1789 show_cursor_when_unfocused: false,
1790 last_focused_descendant: None,
1791 buffer: buffer.clone(),
1792 display_map: display_map.clone(),
1793 selections,
1794 scroll_manager: ScrollManager::new(cx),
1795 columnar_selection_tail: None,
1796 add_selections_state: None,
1797 select_next_state: None,
1798 select_prev_state: None,
1799 selection_history: Default::default(),
1800 autoclose_regions: Default::default(),
1801 snippet_stack: Default::default(),
1802 select_larger_syntax_node_stack: Vec::new(),
1803 ime_transaction: Default::default(),
1804 active_diagnostics: None,
1805 soft_wrap_mode_override,
1806 completion_provider: project.clone().map(|project| Box::new(project) as _),
1807 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1808 project,
1809 blink_manager: blink_manager.clone(),
1810 show_local_selections: true,
1811 mode,
1812 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1813 show_gutter: mode == EditorMode::Full,
1814 show_line_numbers: None,
1815 show_git_diff_gutter: None,
1816 show_code_actions: None,
1817 show_runnables: None,
1818 show_wrap_guides: None,
1819 show_indent_guides,
1820 placeholder_text: None,
1821 highlight_order: 0,
1822 highlighted_rows: HashMap::default(),
1823 background_highlights: Default::default(),
1824 gutter_highlights: TreeMap::default(),
1825 scrollbar_marker_state: ScrollbarMarkerState::default(),
1826 active_indent_guides_state: ActiveIndentGuidesState::default(),
1827 nav_history: None,
1828 context_menu: RwLock::new(None),
1829 mouse_context_menu: None,
1830 completion_tasks: Default::default(),
1831 signature_help_state: SignatureHelpState::default(),
1832 auto_signature_help: None,
1833 find_all_references_task_sources: Vec::new(),
1834 next_completion_id: 0,
1835 completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
1836 next_inlay_id: 0,
1837 available_code_actions: Default::default(),
1838 code_actions_task: Default::default(),
1839 document_highlights_task: Default::default(),
1840 linked_editing_range_task: Default::default(),
1841 pending_rename: Default::default(),
1842 searchable: true,
1843 cursor_shape: Default::default(),
1844 current_line_highlight: None,
1845 autoindent_mode: Some(AutoindentMode::EachLine),
1846 collapse_matches: false,
1847 workspace: None,
1848 keymap_context_layers: Default::default(),
1849 input_enabled: true,
1850 use_modal_editing: mode == EditorMode::Full,
1851 read_only: false,
1852 use_autoclose: true,
1853 use_auto_surround: true,
1854 auto_replace_emoji_shortcode: false,
1855 leader_peer_id: None,
1856 remote_id: None,
1857 hover_state: Default::default(),
1858 hovered_link_state: Default::default(),
1859 inline_completion_provider: None,
1860 active_inline_completion: None,
1861 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1862 expanded_hunks: ExpandedHunks::default(),
1863 gutter_hovered: false,
1864 pixel_position_of_newest_cursor: None,
1865 last_bounds: None,
1866 expect_bounds_change: None,
1867 gutter_dimensions: GutterDimensions::default(),
1868 style: None,
1869 show_cursor_names: false,
1870 hovered_cursors: Default::default(),
1871 next_editor_action_id: EditorActionId::default(),
1872 editor_actions: Rc::default(),
1873 vim_replace_map: Default::default(),
1874 show_inline_completions: mode == EditorMode::Full,
1875 custom_context_menu: None,
1876 show_git_blame_gutter: false,
1877 show_git_blame_inline: false,
1878 show_selection_menu: None,
1879 show_git_blame_inline_delay_task: None,
1880 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1881 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1882 .session
1883 .restore_unsaved_buffers,
1884 blame: None,
1885 blame_subscription: None,
1886 file_header_size,
1887 tasks: Default::default(),
1888 _subscriptions: vec![
1889 cx.observe(&buffer, Self::on_buffer_changed),
1890 cx.subscribe(&buffer, Self::on_buffer_event),
1891 cx.observe(&display_map, Self::on_display_map_changed),
1892 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1893 cx.observe_global::<SettingsStore>(Self::settings_changed),
1894 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1895 cx.observe_window_activation(|editor, cx| {
1896 let active = cx.is_window_active();
1897 editor.blink_manager.update(cx, |blink_manager, cx| {
1898 if active {
1899 blink_manager.enable(cx);
1900 } else {
1901 blink_manager.show_cursor(cx);
1902 blink_manager.disable(cx);
1903 }
1904 });
1905 }),
1906 ],
1907 tasks_update_task: None,
1908 linked_edit_ranges: Default::default(),
1909 previous_search_ranges: None,
1910 breadcrumb_header: None,
1911 };
1912 this.tasks_update_task = Some(this.refresh_runnables(cx));
1913 this._subscriptions.extend(project_subscriptions);
1914
1915 this.end_selection(cx);
1916 this.scroll_manager.show_scrollbar(cx);
1917
1918 if mode == EditorMode::Full {
1919 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1920 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1921
1922 if this.git_blame_inline_enabled {
1923 this.git_blame_inline_enabled = true;
1924 this.start_git_blame_inline(false, cx);
1925 }
1926 }
1927
1928 this.report_editor_event("open", None, cx);
1929 this
1930 }
1931
1932 pub fn mouse_menu_is_focused(&self, cx: &mut WindowContext) -> bool {
1933 self.mouse_context_menu
1934 .as_ref()
1935 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(cx))
1936 }
1937
1938 fn key_context(&self, cx: &AppContext) -> KeyContext {
1939 let mut key_context = KeyContext::new_with_defaults();
1940 key_context.add("Editor");
1941 let mode = match self.mode {
1942 EditorMode::SingleLine { .. } => "single_line",
1943 EditorMode::AutoHeight { .. } => "auto_height",
1944 EditorMode::Full => "full",
1945 };
1946
1947 if EditorSettings::get_global(cx).jupyter.enabled {
1948 key_context.add("jupyter");
1949 }
1950
1951 key_context.set("mode", mode);
1952 if self.pending_rename.is_some() {
1953 key_context.add("renaming");
1954 }
1955 if self.context_menu_visible() {
1956 match self.context_menu.read().as_ref() {
1957 Some(ContextMenu::Completions(_)) => {
1958 key_context.add("menu");
1959 key_context.add("showing_completions")
1960 }
1961 Some(ContextMenu::CodeActions(_)) => {
1962 key_context.add("menu");
1963 key_context.add("showing_code_actions")
1964 }
1965 None => {}
1966 }
1967 }
1968
1969 for layer in self.keymap_context_layers.values() {
1970 key_context.extend(layer);
1971 }
1972
1973 if let Some(extension) = self
1974 .buffer
1975 .read(cx)
1976 .as_singleton()
1977 .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
1978 {
1979 key_context.set("extension", extension.to_string());
1980 }
1981
1982 if self.has_active_inline_completion(cx) {
1983 key_context.add("copilot_suggestion");
1984 key_context.add("inline_completion");
1985 }
1986
1987 key_context
1988 }
1989
1990 pub fn new_file(
1991 workspace: &mut Workspace,
1992 _: &workspace::NewFile,
1993 cx: &mut ViewContext<Workspace>,
1994 ) {
1995 let project = workspace.project().clone();
1996 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1997
1998 cx.spawn(|workspace, mut cx| async move {
1999 let buffer = create.await?;
2000 workspace.update(&mut cx, |workspace, cx| {
2001 workspace.add_item_to_active_pane(
2002 Box::new(
2003 cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
2004 ),
2005 None,
2006 cx,
2007 )
2008 })
2009 })
2010 .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
2011 ErrorCode::RemoteUpgradeRequired => Some(format!(
2012 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2013 e.error_tag("required").unwrap_or("the latest version")
2014 )),
2015 _ => None,
2016 });
2017 }
2018
2019 pub fn new_file_in_direction(
2020 workspace: &mut Workspace,
2021 action: &workspace::NewFileInDirection,
2022 cx: &mut ViewContext<Workspace>,
2023 ) {
2024 let project = workspace.project().clone();
2025 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2026 let direction = action.0;
2027
2028 cx.spawn(|workspace, mut cx| async move {
2029 let buffer = create.await?;
2030 workspace.update(&mut cx, move |workspace, cx| {
2031 workspace.split_item(
2032 direction,
2033 Box::new(
2034 cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
2035 ),
2036 cx,
2037 )
2038 })?;
2039 anyhow::Ok(())
2040 })
2041 .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
2042 ErrorCode::RemoteUpgradeRequired => Some(format!(
2043 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2044 e.error_tag("required").unwrap_or("the latest version")
2045 )),
2046 _ => None,
2047 });
2048 }
2049
2050 pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
2051 self.buffer.read(cx).replica_id()
2052 }
2053
2054 pub fn leader_peer_id(&self) -> Option<PeerId> {
2055 self.leader_peer_id
2056 }
2057
2058 pub fn buffer(&self) -> &Model<MultiBuffer> {
2059 &self.buffer
2060 }
2061
2062 pub fn workspace(&self) -> Option<View<Workspace>> {
2063 self.workspace.as_ref()?.0.upgrade()
2064 }
2065
2066 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
2067 self.buffer().read(cx).title(cx)
2068 }
2069
2070 pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
2071 EditorSnapshot {
2072 mode: self.mode,
2073 show_gutter: self.show_gutter,
2074 show_line_numbers: self.show_line_numbers,
2075 show_git_diff_gutter: self.show_git_diff_gutter,
2076 show_code_actions: self.show_code_actions,
2077 show_runnables: self.show_runnables,
2078 render_git_blame_gutter: self.render_git_blame_gutter(cx),
2079 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2080 scroll_anchor: self.scroll_manager.anchor(),
2081 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2082 placeholder_text: self.placeholder_text.clone(),
2083 is_focused: self.focus_handle.is_focused(cx),
2084 current_line_highlight: self
2085 .current_line_highlight
2086 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2087 gutter_hovered: self.gutter_hovered,
2088 }
2089 }
2090
2091 pub fn language_at<T: ToOffset>(&self, point: T, cx: &AppContext) -> Option<Arc<Language>> {
2092 self.buffer.read(cx).language_at(point, cx)
2093 }
2094
2095 pub fn file_at<T: ToOffset>(
2096 &self,
2097 point: T,
2098 cx: &AppContext,
2099 ) -> Option<Arc<dyn language::File>> {
2100 self.buffer.read(cx).read(cx).file_at(point).cloned()
2101 }
2102
2103 pub fn active_excerpt(
2104 &self,
2105 cx: &AppContext,
2106 ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
2107 self.buffer
2108 .read(cx)
2109 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2110 }
2111
2112 pub fn mode(&self) -> EditorMode {
2113 self.mode
2114 }
2115
2116 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2117 self.collaboration_hub.as_deref()
2118 }
2119
2120 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2121 self.collaboration_hub = Some(hub);
2122 }
2123
2124 pub fn set_custom_context_menu(
2125 &mut self,
2126 f: impl 'static
2127 + Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
2128 ) {
2129 self.custom_context_menu = Some(Box::new(f))
2130 }
2131
2132 pub fn set_completion_provider(&mut self, provider: Box<dyn CompletionProvider>) {
2133 self.completion_provider = Some(provider);
2134 }
2135
2136 pub fn set_inline_completion_provider<T>(
2137 &mut self,
2138 provider: Option<Model<T>>,
2139 cx: &mut ViewContext<Self>,
2140 ) where
2141 T: InlineCompletionProvider,
2142 {
2143 self.inline_completion_provider =
2144 provider.map(|provider| RegisteredInlineCompletionProvider {
2145 _subscription: cx.observe(&provider, |this, _, cx| {
2146 if this.focus_handle.is_focused(cx) {
2147 this.update_visible_inline_completion(cx);
2148 }
2149 }),
2150 provider: Arc::new(provider),
2151 });
2152 self.refresh_inline_completion(false, cx);
2153 }
2154
2155 pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
2156 self.placeholder_text.as_deref()
2157 }
2158
2159 pub fn set_placeholder_text(
2160 &mut self,
2161 placeholder_text: impl Into<Arc<str>>,
2162 cx: &mut ViewContext<Self>,
2163 ) {
2164 let placeholder_text = Some(placeholder_text.into());
2165 if self.placeholder_text != placeholder_text {
2166 self.placeholder_text = placeholder_text;
2167 cx.notify();
2168 }
2169 }
2170
2171 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
2172 self.cursor_shape = cursor_shape;
2173
2174 // Disrupt blink for immediate user feedback that the cursor shape has changed
2175 self.blink_manager.update(cx, BlinkManager::show_cursor);
2176
2177 cx.notify();
2178 }
2179
2180 pub fn set_current_line_highlight(
2181 &mut self,
2182 current_line_highlight: Option<CurrentLineHighlight>,
2183 ) {
2184 self.current_line_highlight = current_line_highlight;
2185 }
2186
2187 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2188 self.collapse_matches = collapse_matches;
2189 }
2190
2191 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2192 if self.collapse_matches {
2193 return range.start..range.start;
2194 }
2195 range.clone()
2196 }
2197
2198 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
2199 if self.display_map.read(cx).clip_at_line_ends != clip {
2200 self.display_map
2201 .update(cx, |map, _| map.clip_at_line_ends = clip);
2202 }
2203 }
2204
2205 pub fn set_keymap_context_layer<Tag: 'static>(
2206 &mut self,
2207 context: KeyContext,
2208 cx: &mut ViewContext<Self>,
2209 ) {
2210 self.keymap_context_layers
2211 .insert(TypeId::of::<Tag>(), context);
2212 cx.notify();
2213 }
2214
2215 pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
2216 self.keymap_context_layers.remove(&TypeId::of::<Tag>());
2217 cx.notify();
2218 }
2219
2220 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2221 self.input_enabled = input_enabled;
2222 }
2223
2224 pub fn set_autoindent(&mut self, autoindent: bool) {
2225 if autoindent {
2226 self.autoindent_mode = Some(AutoindentMode::EachLine);
2227 } else {
2228 self.autoindent_mode = None;
2229 }
2230 }
2231
2232 pub fn read_only(&self, cx: &AppContext) -> bool {
2233 self.read_only || self.buffer.read(cx).read_only()
2234 }
2235
2236 pub fn set_read_only(&mut self, read_only: bool) {
2237 self.read_only = read_only;
2238 }
2239
2240 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2241 self.use_autoclose = autoclose;
2242 }
2243
2244 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2245 self.use_auto_surround = auto_surround;
2246 }
2247
2248 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2249 self.auto_replace_emoji_shortcode = auto_replace;
2250 }
2251
2252 pub fn set_show_inline_completions(&mut self, show_inline_completions: bool) {
2253 self.show_inline_completions = show_inline_completions;
2254 }
2255
2256 pub fn set_use_modal_editing(&mut self, to: bool) {
2257 self.use_modal_editing = to;
2258 }
2259
2260 pub fn use_modal_editing(&self) -> bool {
2261 self.use_modal_editing
2262 }
2263
2264 fn selections_did_change(
2265 &mut self,
2266 local: bool,
2267 old_cursor_position: &Anchor,
2268 show_completions: bool,
2269 cx: &mut ViewContext<Self>,
2270 ) {
2271 // Copy selections to primary selection buffer
2272 #[cfg(target_os = "linux")]
2273 if local {
2274 let selections = self.selections.all::<usize>(cx);
2275 let buffer_handle = self.buffer.read(cx).read(cx);
2276
2277 let mut text = String::new();
2278 for (index, selection) in selections.iter().enumerate() {
2279 let text_for_selection = buffer_handle
2280 .text_for_range(selection.start..selection.end)
2281 .collect::<String>();
2282
2283 text.push_str(&text_for_selection);
2284 if index != selections.len() - 1 {
2285 text.push('\n');
2286 }
2287 }
2288
2289 if !text.is_empty() {
2290 cx.write_to_primary(ClipboardItem::new(text));
2291 }
2292 }
2293
2294 if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
2295 self.buffer.update(cx, |buffer, cx| {
2296 buffer.set_active_selections(
2297 &self.selections.disjoint_anchors(),
2298 self.selections.line_mode,
2299 self.cursor_shape,
2300 cx,
2301 )
2302 });
2303 }
2304 let display_map = self
2305 .display_map
2306 .update(cx, |display_map, cx| display_map.snapshot(cx));
2307 let buffer = &display_map.buffer_snapshot;
2308 self.add_selections_state = None;
2309 self.select_next_state = None;
2310 self.select_prev_state = None;
2311 self.select_larger_syntax_node_stack.clear();
2312 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2313 self.snippet_stack
2314 .invalidate(&self.selections.disjoint_anchors(), buffer);
2315 self.take_rename(false, cx);
2316
2317 let new_cursor_position = self.selections.newest_anchor().head();
2318
2319 self.push_to_nav_history(
2320 *old_cursor_position,
2321 Some(new_cursor_position.to_point(buffer)),
2322 cx,
2323 );
2324
2325 if local {
2326 let new_cursor_position = self.selections.newest_anchor().head();
2327 let mut context_menu = self.context_menu.write();
2328 let completion_menu = match context_menu.as_ref() {
2329 Some(ContextMenu::Completions(menu)) => Some(menu),
2330
2331 _ => {
2332 *context_menu = None;
2333 None
2334 }
2335 };
2336
2337 if let Some(completion_menu) = completion_menu {
2338 let cursor_position = new_cursor_position.to_offset(buffer);
2339 let (word_range, kind) = buffer.surrounding_word(completion_menu.initial_position);
2340 if kind == Some(CharKind::Word)
2341 && word_range.to_inclusive().contains(&cursor_position)
2342 {
2343 let mut completion_menu = completion_menu.clone();
2344 drop(context_menu);
2345
2346 let query = Self::completion_query(buffer, cursor_position);
2347 cx.spawn(move |this, mut cx| async move {
2348 completion_menu
2349 .filter(query.as_deref(), cx.background_executor().clone())
2350 .await;
2351
2352 this.update(&mut cx, |this, cx| {
2353 let mut context_menu = this.context_menu.write();
2354 let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else {
2355 return;
2356 };
2357
2358 if menu.id > completion_menu.id {
2359 return;
2360 }
2361
2362 *context_menu = Some(ContextMenu::Completions(completion_menu));
2363 drop(context_menu);
2364 cx.notify();
2365 })
2366 })
2367 .detach();
2368
2369 if show_completions {
2370 self.show_completions(&ShowCompletions { trigger: None }, cx);
2371 }
2372 } else {
2373 drop(context_menu);
2374 self.hide_context_menu(cx);
2375 }
2376 } else {
2377 drop(context_menu);
2378 }
2379
2380 hide_hover(self, cx);
2381
2382 if old_cursor_position.to_display_point(&display_map).row()
2383 != new_cursor_position.to_display_point(&display_map).row()
2384 {
2385 self.available_code_actions.take();
2386 }
2387 self.refresh_code_actions(cx);
2388 self.refresh_document_highlights(cx);
2389 refresh_matching_bracket_highlights(self, cx);
2390 self.discard_inline_completion(false, cx);
2391 linked_editing_ranges::refresh_linked_ranges(self, cx);
2392 if self.git_blame_inline_enabled {
2393 self.start_inline_blame_timer(cx);
2394 }
2395 }
2396
2397 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2398 cx.emit(EditorEvent::SelectionsChanged { local });
2399
2400 if self.selections.disjoint_anchors().len() == 1 {
2401 cx.emit(SearchEvent::ActiveMatchChanged)
2402 }
2403 cx.notify();
2404 }
2405
2406 pub fn change_selections<R>(
2407 &mut self,
2408 autoscroll: Option<Autoscroll>,
2409 cx: &mut ViewContext<Self>,
2410 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2411 ) -> R {
2412 self.change_selections_inner(autoscroll, true, cx, change)
2413 }
2414
2415 pub fn change_selections_inner<R>(
2416 &mut self,
2417 autoscroll: Option<Autoscroll>,
2418 request_completions: bool,
2419 cx: &mut ViewContext<Self>,
2420 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2421 ) -> R {
2422 let old_cursor_position = self.selections.newest_anchor().head();
2423 self.push_to_selection_history();
2424
2425 let (changed, result) = self.selections.change_with(cx, change);
2426
2427 if changed {
2428 if let Some(autoscroll) = autoscroll {
2429 self.request_autoscroll(autoscroll, cx);
2430 }
2431 self.selections_did_change(true, &old_cursor_position, request_completions, cx);
2432
2433 if self.should_open_signature_help_automatically(
2434 &old_cursor_position,
2435 self.signature_help_state.backspace_pressed(),
2436 cx,
2437 ) {
2438 self.show_signature_help(&ShowSignatureHelp, cx);
2439 }
2440 self.signature_help_state.set_backspace_pressed(false);
2441 }
2442
2443 result
2444 }
2445
2446 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2447 where
2448 I: IntoIterator<Item = (Range<S>, T)>,
2449 S: ToOffset,
2450 T: Into<Arc<str>>,
2451 {
2452 if self.read_only(cx) {
2453 return;
2454 }
2455
2456 self.buffer
2457 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2458 }
2459
2460 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut ViewContext<Self>)
2461 where
2462 I: IntoIterator<Item = (Range<S>, T)>,
2463 S: ToOffset,
2464 T: Into<Arc<str>>,
2465 {
2466 if self.read_only(cx) {
2467 return;
2468 }
2469
2470 self.buffer.update(cx, |buffer, cx| {
2471 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2472 });
2473 }
2474
2475 pub fn edit_with_block_indent<I, S, T>(
2476 &mut self,
2477 edits: I,
2478 original_indent_columns: Vec<u32>,
2479 cx: &mut ViewContext<Self>,
2480 ) where
2481 I: IntoIterator<Item = (Range<S>, T)>,
2482 S: ToOffset,
2483 T: Into<Arc<str>>,
2484 {
2485 if self.read_only(cx) {
2486 return;
2487 }
2488
2489 self.buffer.update(cx, |buffer, cx| {
2490 buffer.edit(
2491 edits,
2492 Some(AutoindentMode::Block {
2493 original_indent_columns,
2494 }),
2495 cx,
2496 )
2497 });
2498 }
2499
2500 fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
2501 self.hide_context_menu(cx);
2502
2503 match phase {
2504 SelectPhase::Begin {
2505 position,
2506 add,
2507 click_count,
2508 } => self.begin_selection(position, add, click_count, cx),
2509 SelectPhase::BeginColumnar {
2510 position,
2511 goal_column,
2512 reset,
2513 } => self.begin_columnar_selection(position, goal_column, reset, cx),
2514 SelectPhase::Extend {
2515 position,
2516 click_count,
2517 } => self.extend_selection(position, click_count, cx),
2518 SelectPhase::Update {
2519 position,
2520 goal_column,
2521 scroll_delta,
2522 } => self.update_selection(position, goal_column, scroll_delta, cx),
2523 SelectPhase::End => self.end_selection(cx),
2524 }
2525 }
2526
2527 fn extend_selection(
2528 &mut self,
2529 position: DisplayPoint,
2530 click_count: usize,
2531 cx: &mut ViewContext<Self>,
2532 ) {
2533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2534 let tail = self.selections.newest::<usize>(cx).tail();
2535 self.begin_selection(position, false, click_count, cx);
2536
2537 let position = position.to_offset(&display_map, Bias::Left);
2538 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2539
2540 let mut pending_selection = self
2541 .selections
2542 .pending_anchor()
2543 .expect("extend_selection not called with pending selection");
2544 if position >= tail {
2545 pending_selection.start = tail_anchor;
2546 } else {
2547 pending_selection.end = tail_anchor;
2548 pending_selection.reversed = true;
2549 }
2550
2551 let mut pending_mode = self.selections.pending_mode().unwrap();
2552 match &mut pending_mode {
2553 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2554 _ => {}
2555 }
2556
2557 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
2558 s.set_pending(pending_selection, pending_mode)
2559 });
2560 }
2561
2562 fn begin_selection(
2563 &mut self,
2564 position: DisplayPoint,
2565 add: bool,
2566 click_count: usize,
2567 cx: &mut ViewContext<Self>,
2568 ) {
2569 if !self.focus_handle.is_focused(cx) {
2570 self.last_focused_descendant = None;
2571 cx.focus(&self.focus_handle);
2572 }
2573
2574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2575 let buffer = &display_map.buffer_snapshot;
2576 let newest_selection = self.selections.newest_anchor().clone();
2577 let position = display_map.clip_point(position, Bias::Left);
2578
2579 let start;
2580 let end;
2581 let mode;
2582 let auto_scroll;
2583 match click_count {
2584 1 => {
2585 start = buffer.anchor_before(position.to_point(&display_map));
2586 end = start;
2587 mode = SelectMode::Character;
2588 auto_scroll = true;
2589 }
2590 2 => {
2591 let range = movement::surrounding_word(&display_map, position);
2592 start = buffer.anchor_before(range.start.to_point(&display_map));
2593 end = buffer.anchor_before(range.end.to_point(&display_map));
2594 mode = SelectMode::Word(start..end);
2595 auto_scroll = true;
2596 }
2597 3 => {
2598 let position = display_map
2599 .clip_point(position, Bias::Left)
2600 .to_point(&display_map);
2601 let line_start = display_map.prev_line_boundary(position).0;
2602 let next_line_start = buffer.clip_point(
2603 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2604 Bias::Left,
2605 );
2606 start = buffer.anchor_before(line_start);
2607 end = buffer.anchor_before(next_line_start);
2608 mode = SelectMode::Line(start..end);
2609 auto_scroll = true;
2610 }
2611 _ => {
2612 start = buffer.anchor_before(0);
2613 end = buffer.anchor_before(buffer.len());
2614 mode = SelectMode::All;
2615 auto_scroll = false;
2616 }
2617 }
2618
2619 let point_to_delete: Option<usize> = {
2620 let selected_points: Vec<Selection<Point>> =
2621 self.selections.disjoint_in_range(start..end, cx);
2622
2623 if !add || click_count > 1 {
2624 None
2625 } else if selected_points.len() > 0 {
2626 Some(selected_points[0].id)
2627 } else {
2628 let clicked_point_already_selected =
2629 self.selections.disjoint.iter().find(|selection| {
2630 selection.start.to_point(buffer) == start.to_point(buffer)
2631 || selection.end.to_point(buffer) == end.to_point(buffer)
2632 });
2633
2634 if let Some(selection) = clicked_point_already_selected {
2635 Some(selection.id)
2636 } else {
2637 None
2638 }
2639 }
2640 };
2641
2642 let selections_count = self.selections.count();
2643
2644 self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
2645 if let Some(point_to_delete) = point_to_delete {
2646 s.delete(point_to_delete);
2647
2648 if selections_count == 1 {
2649 s.set_pending_anchor_range(start..end, mode);
2650 }
2651 } else {
2652 if !add {
2653 s.clear_disjoint();
2654 } else if click_count > 1 {
2655 s.delete(newest_selection.id)
2656 }
2657
2658 s.set_pending_anchor_range(start..end, mode);
2659 }
2660 });
2661 }
2662
2663 fn begin_columnar_selection(
2664 &mut self,
2665 position: DisplayPoint,
2666 goal_column: u32,
2667 reset: bool,
2668 cx: &mut ViewContext<Self>,
2669 ) {
2670 if !self.focus_handle.is_focused(cx) {
2671 self.last_focused_descendant = None;
2672 cx.focus(&self.focus_handle);
2673 }
2674
2675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2676
2677 if reset {
2678 let pointer_position = display_map
2679 .buffer_snapshot
2680 .anchor_before(position.to_point(&display_map));
2681
2682 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
2683 s.clear_disjoint();
2684 s.set_pending_anchor_range(
2685 pointer_position..pointer_position,
2686 SelectMode::Character,
2687 );
2688 });
2689 }
2690
2691 let tail = self.selections.newest::<Point>(cx).tail();
2692 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2693
2694 if !reset {
2695 self.select_columns(
2696 tail.to_display_point(&display_map),
2697 position,
2698 goal_column,
2699 &display_map,
2700 cx,
2701 );
2702 }
2703 }
2704
2705 fn update_selection(
2706 &mut self,
2707 position: DisplayPoint,
2708 goal_column: u32,
2709 scroll_delta: gpui::Point<f32>,
2710 cx: &mut ViewContext<Self>,
2711 ) {
2712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2713
2714 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2715 let tail = tail.to_display_point(&display_map);
2716 self.select_columns(tail, position, goal_column, &display_map, cx);
2717 } else if let Some(mut pending) = self.selections.pending_anchor() {
2718 let buffer = self.buffer.read(cx).snapshot(cx);
2719 let head;
2720 let tail;
2721 let mode = self.selections.pending_mode().unwrap();
2722 match &mode {
2723 SelectMode::Character => {
2724 head = position.to_point(&display_map);
2725 tail = pending.tail().to_point(&buffer);
2726 }
2727 SelectMode::Word(original_range) => {
2728 let original_display_range = original_range.start.to_display_point(&display_map)
2729 ..original_range.end.to_display_point(&display_map);
2730 let original_buffer_range = original_display_range.start.to_point(&display_map)
2731 ..original_display_range.end.to_point(&display_map);
2732 if movement::is_inside_word(&display_map, position)
2733 || original_display_range.contains(&position)
2734 {
2735 let word_range = movement::surrounding_word(&display_map, position);
2736 if word_range.start < original_display_range.start {
2737 head = word_range.start.to_point(&display_map);
2738 } else {
2739 head = word_range.end.to_point(&display_map);
2740 }
2741 } else {
2742 head = position.to_point(&display_map);
2743 }
2744
2745 if head <= original_buffer_range.start {
2746 tail = original_buffer_range.end;
2747 } else {
2748 tail = original_buffer_range.start;
2749 }
2750 }
2751 SelectMode::Line(original_range) => {
2752 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2753
2754 let position = display_map
2755 .clip_point(position, Bias::Left)
2756 .to_point(&display_map);
2757 let line_start = display_map.prev_line_boundary(position).0;
2758 let next_line_start = buffer.clip_point(
2759 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2760 Bias::Left,
2761 );
2762
2763 if line_start < original_range.start {
2764 head = line_start
2765 } else {
2766 head = next_line_start
2767 }
2768
2769 if head <= original_range.start {
2770 tail = original_range.end;
2771 } else {
2772 tail = original_range.start;
2773 }
2774 }
2775 SelectMode::All => {
2776 return;
2777 }
2778 };
2779
2780 if head < tail {
2781 pending.start = buffer.anchor_before(head);
2782 pending.end = buffer.anchor_before(tail);
2783 pending.reversed = true;
2784 } else {
2785 pending.start = buffer.anchor_before(tail);
2786 pending.end = buffer.anchor_before(head);
2787 pending.reversed = false;
2788 }
2789
2790 self.change_selections(None, cx, |s| {
2791 s.set_pending(pending, mode);
2792 });
2793 } else {
2794 log::error!("update_selection dispatched with no pending selection");
2795 return;
2796 }
2797
2798 self.apply_scroll_delta(scroll_delta, cx);
2799 cx.notify();
2800 }
2801
2802 fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
2803 self.columnar_selection_tail.take();
2804 if self.selections.pending_anchor().is_some() {
2805 let selections = self.selections.all::<usize>(cx);
2806 self.change_selections(None, cx, |s| {
2807 s.select(selections);
2808 s.clear_pending();
2809 });
2810 }
2811 }
2812
2813 fn select_columns(
2814 &mut self,
2815 tail: DisplayPoint,
2816 head: DisplayPoint,
2817 goal_column: u32,
2818 display_map: &DisplaySnapshot,
2819 cx: &mut ViewContext<Self>,
2820 ) {
2821 let start_row = cmp::min(tail.row(), head.row());
2822 let end_row = cmp::max(tail.row(), head.row());
2823 let start_column = cmp::min(tail.column(), goal_column);
2824 let end_column = cmp::max(tail.column(), goal_column);
2825 let reversed = start_column < tail.column();
2826
2827 let selection_ranges = (start_row.0..=end_row.0)
2828 .map(DisplayRow)
2829 .filter_map(|row| {
2830 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2831 let start = display_map
2832 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2833 .to_point(display_map);
2834 let end = display_map
2835 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2836 .to_point(display_map);
2837 if reversed {
2838 Some(end..start)
2839 } else {
2840 Some(start..end)
2841 }
2842 } else {
2843 None
2844 }
2845 })
2846 .collect::<Vec<_>>();
2847
2848 self.change_selections(None, cx, |s| {
2849 s.select_ranges(selection_ranges);
2850 });
2851 cx.notify();
2852 }
2853
2854 pub fn has_pending_nonempty_selection(&self) -> bool {
2855 let pending_nonempty_selection = match self.selections.pending_anchor() {
2856 Some(Selection { start, end, .. }) => start != end,
2857 None => false,
2858 };
2859
2860 pending_nonempty_selection
2861 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2862 }
2863
2864 pub fn has_pending_selection(&self) -> bool {
2865 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2866 }
2867
2868 pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
2869 self.clear_expanded_diff_hunks(cx);
2870 if self.dismiss_menus_and_popups(true, cx) {
2871 return;
2872 }
2873
2874 if self.mode == EditorMode::Full {
2875 if self.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel()) {
2876 return;
2877 }
2878 }
2879
2880 cx.propagate();
2881 }
2882
2883 pub fn dismiss_menus_and_popups(
2884 &mut self,
2885 should_report_inline_completion_event: bool,
2886 cx: &mut ViewContext<Self>,
2887 ) -> bool {
2888 if self.take_rename(false, cx).is_some() {
2889 return true;
2890 }
2891
2892 if hide_hover(self, cx) {
2893 return true;
2894 }
2895
2896 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2897 return true;
2898 }
2899
2900 if self.hide_context_menu(cx).is_some() {
2901 return true;
2902 }
2903
2904 if self.discard_inline_completion(should_report_inline_completion_event, cx) {
2905 return true;
2906 }
2907
2908 if self.snippet_stack.pop().is_some() {
2909 return true;
2910 }
2911
2912 if self.mode == EditorMode::Full {
2913 if self.active_diagnostics.is_some() {
2914 self.dismiss_diagnostics(cx);
2915 return true;
2916 }
2917 }
2918
2919 false
2920 }
2921
2922 fn linked_editing_ranges_for(
2923 &self,
2924 selection: Range<text::Anchor>,
2925 cx: &AppContext,
2926 ) -> Option<HashMap<Model<Buffer>, Vec<Range<text::Anchor>>>> {
2927 if self.linked_edit_ranges.is_empty() {
2928 return None;
2929 }
2930 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
2931 selection.end.buffer_id.and_then(|end_buffer_id| {
2932 if selection.start.buffer_id != Some(end_buffer_id) {
2933 return None;
2934 }
2935 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
2936 let snapshot = buffer.read(cx).snapshot();
2937 self.linked_edit_ranges
2938 .get(end_buffer_id, selection.start..selection.end, &snapshot)
2939 .map(|ranges| (ranges, snapshot, buffer))
2940 })?;
2941 use text::ToOffset as TO;
2942 // find offset from the start of current range to current cursor position
2943 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
2944
2945 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
2946 let start_difference = start_offset - start_byte_offset;
2947 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
2948 let end_difference = end_offset - start_byte_offset;
2949 // Current range has associated linked ranges.
2950 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2951 for range in linked_ranges.iter() {
2952 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
2953 let end_offset = start_offset + end_difference;
2954 let start_offset = start_offset + start_difference;
2955 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
2956 continue;
2957 }
2958 let start = buffer_snapshot.anchor_after(start_offset);
2959 let end = buffer_snapshot.anchor_after(end_offset);
2960 linked_edits
2961 .entry(buffer.clone())
2962 .or_default()
2963 .push(start..end);
2964 }
2965 Some(linked_edits)
2966 }
2967
2968 pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
2969 let text: Arc<str> = text.into();
2970
2971 if self.read_only(cx) {
2972 return;
2973 }
2974
2975 let selections = self.selections.all_adjusted(cx);
2976 let mut bracket_inserted = false;
2977 let mut edits = Vec::new();
2978 let mut linked_edits = HashMap::<_, Vec<_>>::default();
2979 let mut new_selections = Vec::with_capacity(selections.len());
2980 let mut new_autoclose_regions = Vec::new();
2981 let snapshot = self.buffer.read(cx).read(cx);
2982
2983 for (selection, autoclose_region) in
2984 self.selections_with_autoclose_regions(selections, &snapshot)
2985 {
2986 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
2987 // Determine if the inserted text matches the opening or closing
2988 // bracket of any of this language's bracket pairs.
2989 let mut bracket_pair = None;
2990 let mut is_bracket_pair_start = false;
2991 let mut is_bracket_pair_end = false;
2992 if !text.is_empty() {
2993 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
2994 // and they are removing the character that triggered IME popup.
2995 for (pair, enabled) in scope.brackets() {
2996 if !pair.close && !pair.surround {
2997 continue;
2998 }
2999
3000 if enabled && pair.start.ends_with(text.as_ref()) {
3001 bracket_pair = Some(pair.clone());
3002 is_bracket_pair_start = true;
3003 break;
3004 }
3005 if pair.end.as_str() == text.as_ref() {
3006 bracket_pair = Some(pair.clone());
3007 is_bracket_pair_end = true;
3008 break;
3009 }
3010 }
3011 }
3012
3013 if let Some(bracket_pair) = bracket_pair {
3014 let snapshot_settings = snapshot.settings_at(selection.start, cx);
3015 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3016 let auto_surround =
3017 self.use_auto_surround && snapshot_settings.use_auto_surround;
3018 if selection.is_empty() {
3019 if is_bracket_pair_start {
3020 let prefix_len = bracket_pair.start.len() - text.len();
3021
3022 // If the inserted text is a suffix of an opening bracket and the
3023 // selection is preceded by the rest of the opening bracket, then
3024 // insert the closing bracket.
3025 let following_text_allows_autoclose = snapshot
3026 .chars_at(selection.start)
3027 .next()
3028 .map_or(true, |c| scope.should_autoclose_before(c));
3029 let preceding_text_matches_prefix = prefix_len == 0
3030 || (selection.start.column >= (prefix_len as u32)
3031 && snapshot.contains_str_at(
3032 Point::new(
3033 selection.start.row,
3034 selection.start.column - (prefix_len as u32),
3035 ),
3036 &bracket_pair.start[..prefix_len],
3037 ));
3038
3039 if autoclose
3040 && bracket_pair.close
3041 && following_text_allows_autoclose
3042 && preceding_text_matches_prefix
3043 {
3044 let anchor = snapshot.anchor_before(selection.end);
3045 new_selections.push((selection.map(|_| anchor), text.len()));
3046 new_autoclose_regions.push((
3047 anchor,
3048 text.len(),
3049 selection.id,
3050 bracket_pair.clone(),
3051 ));
3052 edits.push((
3053 selection.range(),
3054 format!("{}{}", text, bracket_pair.end).into(),
3055 ));
3056 bracket_inserted = true;
3057 continue;
3058 }
3059 }
3060
3061 if let Some(region) = autoclose_region {
3062 // If the selection is followed by an auto-inserted closing bracket,
3063 // then don't insert that closing bracket again; just move the selection
3064 // past the closing bracket.
3065 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3066 && text.as_ref() == region.pair.end.as_str();
3067 if should_skip {
3068 let anchor = snapshot.anchor_after(selection.end);
3069 new_selections
3070 .push((selection.map(|_| anchor), region.pair.end.len()));
3071 continue;
3072 }
3073 }
3074
3075 let always_treat_brackets_as_autoclosed = snapshot
3076 .settings_at(selection.start, cx)
3077 .always_treat_brackets_as_autoclosed;
3078 if always_treat_brackets_as_autoclosed
3079 && is_bracket_pair_end
3080 && snapshot.contains_str_at(selection.end, text.as_ref())
3081 {
3082 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3083 // and the inserted text is a closing bracket and the selection is followed
3084 // by the closing bracket then move the selection past the closing bracket.
3085 let anchor = snapshot.anchor_after(selection.end);
3086 new_selections.push((selection.map(|_| anchor), text.len()));
3087 continue;
3088 }
3089 }
3090 // If an opening bracket is 1 character long and is typed while
3091 // text is selected, then surround that text with the bracket pair.
3092 else if auto_surround
3093 && bracket_pair.surround
3094 && is_bracket_pair_start
3095 && bracket_pair.start.chars().count() == 1
3096 {
3097 edits.push((selection.start..selection.start, text.clone()));
3098 edits.push((
3099 selection.end..selection.end,
3100 bracket_pair.end.as_str().into(),
3101 ));
3102 bracket_inserted = true;
3103 new_selections.push((
3104 Selection {
3105 id: selection.id,
3106 start: snapshot.anchor_after(selection.start),
3107 end: snapshot.anchor_before(selection.end),
3108 reversed: selection.reversed,
3109 goal: selection.goal,
3110 },
3111 0,
3112 ));
3113 continue;
3114 }
3115 }
3116 }
3117
3118 if self.auto_replace_emoji_shortcode
3119 && selection.is_empty()
3120 && text.as_ref().ends_with(':')
3121 {
3122 if let Some(possible_emoji_short_code) =
3123 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3124 {
3125 if !possible_emoji_short_code.is_empty() {
3126 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3127 let emoji_shortcode_start = Point::new(
3128 selection.start.row,
3129 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3130 );
3131
3132 // Remove shortcode from buffer
3133 edits.push((
3134 emoji_shortcode_start..selection.start,
3135 "".to_string().into(),
3136 ));
3137 new_selections.push((
3138 Selection {
3139 id: selection.id,
3140 start: snapshot.anchor_after(emoji_shortcode_start),
3141 end: snapshot.anchor_before(selection.start),
3142 reversed: selection.reversed,
3143 goal: selection.goal,
3144 },
3145 0,
3146 ));
3147
3148 // Insert emoji
3149 let selection_start_anchor = snapshot.anchor_after(selection.start);
3150 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3151 edits.push((selection.start..selection.end, emoji.to_string().into()));
3152
3153 continue;
3154 }
3155 }
3156 }
3157 }
3158
3159 // If not handling any auto-close operation, then just replace the selected
3160 // text with the given input and move the selection to the end of the
3161 // newly inserted text.
3162 let anchor = snapshot.anchor_after(selection.end);
3163 if !self.linked_edit_ranges.is_empty() {
3164 let start_anchor = snapshot.anchor_before(selection.start);
3165
3166 let is_word_char = text.chars().next().map_or(true, |char| {
3167 let scope = snapshot.language_scope_at(start_anchor.to_offset(&snapshot));
3168 let kind = char_kind(&scope, char);
3169
3170 kind == CharKind::Word
3171 });
3172
3173 if is_word_char {
3174 if let Some(ranges) = self
3175 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3176 {
3177 for (buffer, edits) in ranges {
3178 linked_edits
3179 .entry(buffer.clone())
3180 .or_default()
3181 .extend(edits.into_iter().map(|range| (range, text.clone())));
3182 }
3183 }
3184 }
3185 }
3186
3187 new_selections.push((selection.map(|_| anchor), 0));
3188 edits.push((selection.start..selection.end, text.clone()));
3189 }
3190
3191 drop(snapshot);
3192
3193 self.transact(cx, |this, cx| {
3194 this.buffer.update(cx, |buffer, cx| {
3195 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3196 });
3197 for (buffer, edits) in linked_edits {
3198 buffer.update(cx, |buffer, cx| {
3199 let snapshot = buffer.snapshot();
3200 let edits = edits
3201 .into_iter()
3202 .map(|(range, text)| {
3203 use text::ToPoint as TP;
3204 let end_point = TP::to_point(&range.end, &snapshot);
3205 let start_point = TP::to_point(&range.start, &snapshot);
3206 (start_point..end_point, text)
3207 })
3208 .sorted_by_key(|(range, _)| range.start)
3209 .collect::<Vec<_>>();
3210 buffer.edit(edits, None, cx);
3211 })
3212 }
3213 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3214 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3215 let snapshot = this.buffer.read(cx).read(cx);
3216 let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
3217 .zip(new_selection_deltas)
3218 .map(|(selection, delta)| Selection {
3219 id: selection.id,
3220 start: selection.start + delta,
3221 end: selection.end + delta,
3222 reversed: selection.reversed,
3223 goal: SelectionGoal::None,
3224 })
3225 .collect::<Vec<_>>();
3226
3227 let mut i = 0;
3228 for (position, delta, selection_id, pair) in new_autoclose_regions {
3229 let position = position.to_offset(&snapshot) + delta;
3230 let start = snapshot.anchor_before(position);
3231 let end = snapshot.anchor_after(position);
3232 while let Some(existing_state) = this.autoclose_regions.get(i) {
3233 match existing_state.range.start.cmp(&start, &snapshot) {
3234 Ordering::Less => i += 1,
3235 Ordering::Greater => break,
3236 Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
3237 Ordering::Less => i += 1,
3238 Ordering::Equal => break,
3239 Ordering::Greater => break,
3240 },
3241 }
3242 }
3243 this.autoclose_regions.insert(
3244 i,
3245 AutocloseRegion {
3246 selection_id,
3247 range: start..end,
3248 pair,
3249 },
3250 );
3251 }
3252
3253 drop(snapshot);
3254 let had_active_inline_completion = this.has_active_inline_completion(cx);
3255 this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
3256 s.select(new_selections)
3257 });
3258
3259 if !bracket_inserted && EditorSettings::get_global(cx).use_on_type_format {
3260 if let Some(on_type_format_task) =
3261 this.trigger_on_type_formatting(text.to_string(), cx)
3262 {
3263 on_type_format_task.detach_and_log_err(cx);
3264 }
3265 }
3266
3267 let editor_settings = EditorSettings::get_global(cx);
3268 if bracket_inserted
3269 && (editor_settings.auto_signature_help
3270 || editor_settings.show_signature_help_after_edits)
3271 {
3272 this.show_signature_help(&ShowSignatureHelp, cx);
3273 }
3274
3275 let trigger_in_words = !had_active_inline_completion;
3276 this.trigger_completion_on_input(&text, trigger_in_words, cx);
3277 linked_editing_ranges::refresh_linked_ranges(this, cx);
3278 this.refresh_inline_completion(true, cx);
3279 });
3280 }
3281
3282 fn find_possible_emoji_shortcode_at_position(
3283 snapshot: &MultiBufferSnapshot,
3284 position: Point,
3285 ) -> Option<String> {
3286 let mut chars = Vec::new();
3287 let mut found_colon = false;
3288 for char in snapshot.reversed_chars_at(position).take(100) {
3289 // Found a possible emoji shortcode in the middle of the buffer
3290 if found_colon {
3291 if char.is_whitespace() {
3292 chars.reverse();
3293 return Some(chars.iter().collect());
3294 }
3295 // If the previous character is not a whitespace, we are in the middle of a word
3296 // and we only want to complete the shortcode if the word is made up of other emojis
3297 let mut containing_word = String::new();
3298 for ch in snapshot
3299 .reversed_chars_at(position)
3300 .skip(chars.len() + 1)
3301 .take(100)
3302 {
3303 if ch.is_whitespace() {
3304 break;
3305 }
3306 containing_word.push(ch);
3307 }
3308 let containing_word = containing_word.chars().rev().collect::<String>();
3309 if util::word_consists_of_emojis(containing_word.as_str()) {
3310 chars.reverse();
3311 return Some(chars.iter().collect());
3312 }
3313 }
3314
3315 if char.is_whitespace() || !char.is_ascii() {
3316 return None;
3317 }
3318 if char == ':' {
3319 found_colon = true;
3320 } else {
3321 chars.push(char);
3322 }
3323 }
3324 // Found a possible emoji shortcode at the beginning of the buffer
3325 chars.reverse();
3326 Some(chars.iter().collect())
3327 }
3328
3329 pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
3330 self.transact(cx, |this, cx| {
3331 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3332 let selections = this.selections.all::<usize>(cx);
3333 let multi_buffer = this.buffer.read(cx);
3334 let buffer = multi_buffer.snapshot(cx);
3335 selections
3336 .iter()
3337 .map(|selection| {
3338 let start_point = selection.start.to_point(&buffer);
3339 let mut indent =
3340 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3341 indent.len = cmp::min(indent.len, start_point.column);
3342 let start = selection.start;
3343 let end = selection.end;
3344 let selection_is_empty = start == end;
3345 let language_scope = buffer.language_scope_at(start);
3346 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3347 &language_scope
3348 {
3349 let leading_whitespace_len = buffer
3350 .reversed_chars_at(start)
3351 .take_while(|c| c.is_whitespace() && *c != '\n')
3352 .map(|c| c.len_utf8())
3353 .sum::<usize>();
3354
3355 let trailing_whitespace_len = buffer
3356 .chars_at(end)
3357 .take_while(|c| c.is_whitespace() && *c != '\n')
3358 .map(|c| c.len_utf8())
3359 .sum::<usize>();
3360
3361 let insert_extra_newline =
3362 language.brackets().any(|(pair, enabled)| {
3363 let pair_start = pair.start.trim_end();
3364 let pair_end = pair.end.trim_start();
3365
3366 enabled
3367 && pair.newline
3368 && buffer.contains_str_at(
3369 end + trailing_whitespace_len,
3370 pair_end,
3371 )
3372 && buffer.contains_str_at(
3373 (start - leading_whitespace_len)
3374 .saturating_sub(pair_start.len()),
3375 pair_start,
3376 )
3377 });
3378
3379 // Comment extension on newline is allowed only for cursor selections
3380 let comment_delimiter = maybe!({
3381 if !selection_is_empty {
3382 return None;
3383 }
3384
3385 if !multi_buffer.settings_at(0, cx).extend_comment_on_newline {
3386 return None;
3387 }
3388
3389 let delimiters = language.line_comment_prefixes();
3390 let max_len_of_delimiter =
3391 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3392 let (snapshot, range) =
3393 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3394
3395 let mut index_of_first_non_whitespace = 0;
3396 let comment_candidate = snapshot
3397 .chars_for_range(range)
3398 .skip_while(|c| {
3399 let should_skip = c.is_whitespace();
3400 if should_skip {
3401 index_of_first_non_whitespace += 1;
3402 }
3403 should_skip
3404 })
3405 .take(max_len_of_delimiter)
3406 .collect::<String>();
3407 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3408 comment_candidate.starts_with(comment_prefix.as_ref())
3409 })?;
3410 let cursor_is_placed_after_comment_marker =
3411 index_of_first_non_whitespace + comment_prefix.len()
3412 <= start_point.column as usize;
3413 if cursor_is_placed_after_comment_marker {
3414 Some(comment_prefix.clone())
3415 } else {
3416 None
3417 }
3418 });
3419 (comment_delimiter, insert_extra_newline)
3420 } else {
3421 (None, false)
3422 };
3423
3424 let capacity_for_delimiter = comment_delimiter
3425 .as_deref()
3426 .map(str::len)
3427 .unwrap_or_default();
3428 let mut new_text =
3429 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3430 new_text.push_str("\n");
3431 new_text.extend(indent.chars());
3432 if let Some(delimiter) = &comment_delimiter {
3433 new_text.push_str(&delimiter);
3434 }
3435 if insert_extra_newline {
3436 new_text = new_text.repeat(2);
3437 }
3438
3439 let anchor = buffer.anchor_after(end);
3440 let new_selection = selection.map(|_| anchor);
3441 (
3442 (start..end, new_text),
3443 (insert_extra_newline, new_selection),
3444 )
3445 })
3446 .unzip()
3447 };
3448
3449 this.edit_with_autoindent(edits, cx);
3450 let buffer = this.buffer.read(cx).snapshot(cx);
3451 let new_selections = selection_fixup_info
3452 .into_iter()
3453 .map(|(extra_newline_inserted, new_selection)| {
3454 let mut cursor = new_selection.end.to_point(&buffer);
3455 if extra_newline_inserted {
3456 cursor.row -= 1;
3457 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3458 }
3459 new_selection.map(|_| cursor)
3460 })
3461 .collect();
3462
3463 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
3464 this.refresh_inline_completion(true, cx);
3465 });
3466 }
3467
3468 pub fn newline_above(&mut self, _: &NewlineAbove, cx: &mut ViewContext<Self>) {
3469 let buffer = self.buffer.read(cx);
3470 let snapshot = buffer.snapshot(cx);
3471
3472 let mut edits = Vec::new();
3473 let mut rows = Vec::new();
3474
3475 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3476 let cursor = selection.head();
3477 let row = cursor.row;
3478
3479 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3480
3481 let newline = "\n".to_string();
3482 edits.push((start_of_line..start_of_line, newline));
3483
3484 rows.push(row + rows_inserted as u32);
3485 }
3486
3487 self.transact(cx, |editor, cx| {
3488 editor.edit(edits, cx);
3489
3490 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3491 let mut index = 0;
3492 s.move_cursors_with(|map, _, _| {
3493 let row = rows[index];
3494 index += 1;
3495
3496 let point = Point::new(row, 0);
3497 let boundary = map.next_line_boundary(point).1;
3498 let clipped = map.clip_point(boundary, Bias::Left);
3499
3500 (clipped, SelectionGoal::None)
3501 });
3502 });
3503
3504 let mut indent_edits = Vec::new();
3505 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3506 for row in rows {
3507 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3508 for (row, indent) in indents {
3509 if indent.len == 0 {
3510 continue;
3511 }
3512
3513 let text = match indent.kind {
3514 IndentKind::Space => " ".repeat(indent.len as usize),
3515 IndentKind::Tab => "\t".repeat(indent.len as usize),
3516 };
3517 let point = Point::new(row.0, 0);
3518 indent_edits.push((point..point, text));
3519 }
3520 }
3521 editor.edit(indent_edits, cx);
3522 });
3523 }
3524
3525 pub fn newline_below(&mut self, _: &NewlineBelow, cx: &mut ViewContext<Self>) {
3526 let buffer = self.buffer.read(cx);
3527 let snapshot = buffer.snapshot(cx);
3528
3529 let mut edits = Vec::new();
3530 let mut rows = Vec::new();
3531 let mut rows_inserted = 0;
3532
3533 for selection in self.selections.all_adjusted(cx) {
3534 let cursor = selection.head();
3535 let row = cursor.row;
3536
3537 let point = Point::new(row + 1, 0);
3538 let start_of_line = snapshot.clip_point(point, Bias::Left);
3539
3540 let newline = "\n".to_string();
3541 edits.push((start_of_line..start_of_line, newline));
3542
3543 rows_inserted += 1;
3544 rows.push(row + rows_inserted);
3545 }
3546
3547 self.transact(cx, |editor, cx| {
3548 editor.edit(edits, cx);
3549
3550 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
3551 let mut index = 0;
3552 s.move_cursors_with(|map, _, _| {
3553 let row = rows[index];
3554 index += 1;
3555
3556 let point = Point::new(row, 0);
3557 let boundary = map.next_line_boundary(point).1;
3558 let clipped = map.clip_point(boundary, Bias::Left);
3559
3560 (clipped, SelectionGoal::None)
3561 });
3562 });
3563
3564 let mut indent_edits = Vec::new();
3565 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3566 for row in rows {
3567 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3568 for (row, indent) in indents {
3569 if indent.len == 0 {
3570 continue;
3571 }
3572
3573 let text = match indent.kind {
3574 IndentKind::Space => " ".repeat(indent.len as usize),
3575 IndentKind::Tab => "\t".repeat(indent.len as usize),
3576 };
3577 let point = Point::new(row.0, 0);
3578 indent_edits.push((point..point, text));
3579 }
3580 }
3581 editor.edit(indent_edits, cx);
3582 });
3583 }
3584
3585 pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
3586 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3587 original_indent_columns: Vec::new(),
3588 });
3589 self.insert_with_autoindent_mode(text, autoindent, cx);
3590 }
3591
3592 fn insert_with_autoindent_mode(
3593 &mut self,
3594 text: &str,
3595 autoindent_mode: Option<AutoindentMode>,
3596 cx: &mut ViewContext<Self>,
3597 ) {
3598 if self.read_only(cx) {
3599 return;
3600 }
3601
3602 let text: Arc<str> = text.into();
3603 self.transact(cx, |this, cx| {
3604 let old_selections = this.selections.all_adjusted(cx);
3605 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3606 let anchors = {
3607 let snapshot = buffer.read(cx);
3608 old_selections
3609 .iter()
3610 .map(|s| {
3611 let anchor = snapshot.anchor_after(s.head());
3612 s.map(|_| anchor)
3613 })
3614 .collect::<Vec<_>>()
3615 };
3616 buffer.edit(
3617 old_selections
3618 .iter()
3619 .map(|s| (s.start..s.end, text.clone())),
3620 autoindent_mode,
3621 cx,
3622 );
3623 anchors
3624 });
3625
3626 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3627 s.select_anchors(selection_anchors);
3628 })
3629 });
3630 }
3631
3632 fn trigger_completion_on_input(
3633 &mut self,
3634 text: &str,
3635 trigger_in_words: bool,
3636 cx: &mut ViewContext<Self>,
3637 ) {
3638 if self.is_completion_trigger(text, trigger_in_words, cx) {
3639 self.show_completions(
3640 &ShowCompletions {
3641 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3642 },
3643 cx,
3644 );
3645 } else {
3646 self.hide_context_menu(cx);
3647 }
3648 }
3649
3650 fn is_completion_trigger(
3651 &self,
3652 text: &str,
3653 trigger_in_words: bool,
3654 cx: &mut ViewContext<Self>,
3655 ) -> bool {
3656 let position = self.selections.newest_anchor().head();
3657 let multibuffer = self.buffer.read(cx);
3658 let Some(buffer) = position
3659 .buffer_id
3660 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3661 else {
3662 return false;
3663 };
3664
3665 if let Some(completion_provider) = &self.completion_provider {
3666 completion_provider.is_completion_trigger(
3667 &buffer,
3668 position.text_anchor,
3669 text,
3670 trigger_in_words,
3671 cx,
3672 )
3673 } else {
3674 false
3675 }
3676 }
3677
3678 /// If any empty selections is touching the start of its innermost containing autoclose
3679 /// region, expand it to select the brackets.
3680 fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
3681 let selections = self.selections.all::<usize>(cx);
3682 let buffer = self.buffer.read(cx).read(cx);
3683 let new_selections = self
3684 .selections_with_autoclose_regions(selections, &buffer)
3685 .map(|(mut selection, region)| {
3686 if !selection.is_empty() {
3687 return selection;
3688 }
3689
3690 if let Some(region) = region {
3691 let mut range = region.range.to_offset(&buffer);
3692 if selection.start == range.start && range.start >= region.pair.start.len() {
3693 range.start -= region.pair.start.len();
3694 if buffer.contains_str_at(range.start, ®ion.pair.start)
3695 && buffer.contains_str_at(range.end, ®ion.pair.end)
3696 {
3697 range.end += region.pair.end.len();
3698 selection.start = range.start;
3699 selection.end = range.end;
3700
3701 return selection;
3702 }
3703 }
3704 }
3705
3706 let always_treat_brackets_as_autoclosed = buffer
3707 .settings_at(selection.start, cx)
3708 .always_treat_brackets_as_autoclosed;
3709
3710 if !always_treat_brackets_as_autoclosed {
3711 return selection;
3712 }
3713
3714 if let Some(scope) = buffer.language_scope_at(selection.start) {
3715 for (pair, enabled) in scope.brackets() {
3716 if !enabled || !pair.close {
3717 continue;
3718 }
3719
3720 if buffer.contains_str_at(selection.start, &pair.end) {
3721 let pair_start_len = pair.start.len();
3722 if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
3723 {
3724 selection.start -= pair_start_len;
3725 selection.end += pair.end.len();
3726
3727 return selection;
3728 }
3729 }
3730 }
3731 }
3732
3733 selection
3734 })
3735 .collect();
3736
3737 drop(buffer);
3738 self.change_selections(None, cx, |selections| selections.select(new_selections));
3739 }
3740
3741 /// Iterate the given selections, and for each one, find the smallest surrounding
3742 /// autoclose region. This uses the ordering of the selections and the autoclose
3743 /// regions to avoid repeated comparisons.
3744 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3745 &'a self,
3746 selections: impl IntoIterator<Item = Selection<D>>,
3747 buffer: &'a MultiBufferSnapshot,
3748 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3749 let mut i = 0;
3750 let mut regions = self.autoclose_regions.as_slice();
3751 selections.into_iter().map(move |selection| {
3752 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3753
3754 let mut enclosing = None;
3755 while let Some(pair_state) = regions.get(i) {
3756 if pair_state.range.end.to_offset(buffer) < range.start {
3757 regions = ®ions[i + 1..];
3758 i = 0;
3759 } else if pair_state.range.start.to_offset(buffer) > range.end {
3760 break;
3761 } else {
3762 if pair_state.selection_id == selection.id {
3763 enclosing = Some(pair_state);
3764 }
3765 i += 1;
3766 }
3767 }
3768
3769 (selection.clone(), enclosing)
3770 })
3771 }
3772
3773 /// Remove any autoclose regions that no longer contain their selection.
3774 fn invalidate_autoclose_regions(
3775 &mut self,
3776 mut selections: &[Selection<Anchor>],
3777 buffer: &MultiBufferSnapshot,
3778 ) {
3779 self.autoclose_regions.retain(|state| {
3780 let mut i = 0;
3781 while let Some(selection) = selections.get(i) {
3782 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3783 selections = &selections[1..];
3784 continue;
3785 }
3786 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3787 break;
3788 }
3789 if selection.id == state.selection_id {
3790 return true;
3791 } else {
3792 i += 1;
3793 }
3794 }
3795 false
3796 });
3797 }
3798
3799 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3800 let offset = position.to_offset(buffer);
3801 let (word_range, kind) = buffer.surrounding_word(offset);
3802 if offset > word_range.start && kind == Some(CharKind::Word) {
3803 Some(
3804 buffer
3805 .text_for_range(word_range.start..offset)
3806 .collect::<String>(),
3807 )
3808 } else {
3809 None
3810 }
3811 }
3812
3813 pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
3814 self.refresh_inlay_hints(
3815 InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
3816 cx,
3817 );
3818 }
3819
3820 pub fn inlay_hints_enabled(&self) -> bool {
3821 self.inlay_hint_cache.enabled
3822 }
3823
3824 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
3825 if self.project.is_none() || self.mode != EditorMode::Full {
3826 return;
3827 }
3828
3829 let reason_description = reason.description();
3830 let ignore_debounce = matches!(
3831 reason,
3832 InlayHintRefreshReason::SettingsChange(_)
3833 | InlayHintRefreshReason::Toggle(_)
3834 | InlayHintRefreshReason::ExcerptsRemoved(_)
3835 );
3836 let (invalidate_cache, required_languages) = match reason {
3837 InlayHintRefreshReason::Toggle(enabled) => {
3838 self.inlay_hint_cache.enabled = enabled;
3839 if enabled {
3840 (InvalidationStrategy::RefreshRequested, None)
3841 } else {
3842 self.inlay_hint_cache.clear();
3843 self.splice_inlays(
3844 self.visible_inlay_hints(cx)
3845 .iter()
3846 .map(|inlay| inlay.id)
3847 .collect(),
3848 Vec::new(),
3849 cx,
3850 );
3851 return;
3852 }
3853 }
3854 InlayHintRefreshReason::SettingsChange(new_settings) => {
3855 match self.inlay_hint_cache.update_settings(
3856 &self.buffer,
3857 new_settings,
3858 self.visible_inlay_hints(cx),
3859 cx,
3860 ) {
3861 ControlFlow::Break(Some(InlaySplice {
3862 to_remove,
3863 to_insert,
3864 })) => {
3865 self.splice_inlays(to_remove, to_insert, cx);
3866 return;
3867 }
3868 ControlFlow::Break(None) => return,
3869 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
3870 }
3871 }
3872 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
3873 if let Some(InlaySplice {
3874 to_remove,
3875 to_insert,
3876 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
3877 {
3878 self.splice_inlays(to_remove, to_insert, cx);
3879 }
3880 return;
3881 }
3882 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
3883 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
3884 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
3885 }
3886 InlayHintRefreshReason::RefreshRequested => {
3887 (InvalidationStrategy::RefreshRequested, None)
3888 }
3889 };
3890
3891 if let Some(InlaySplice {
3892 to_remove,
3893 to_insert,
3894 }) = self.inlay_hint_cache.spawn_hint_refresh(
3895 reason_description,
3896 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
3897 invalidate_cache,
3898 ignore_debounce,
3899 cx,
3900 ) {
3901 self.splice_inlays(to_remove, to_insert, cx);
3902 }
3903 }
3904
3905 fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
3906 self.display_map
3907 .read(cx)
3908 .current_inlays()
3909 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
3910 .cloned()
3911 .collect()
3912 }
3913
3914 pub fn excerpts_for_inlay_hints_query(
3915 &self,
3916 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
3917 cx: &mut ViewContext<Editor>,
3918 ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> {
3919 let Some(project) = self.project.as_ref() else {
3920 return HashMap::default();
3921 };
3922 let project = project.read(cx);
3923 let multi_buffer = self.buffer().read(cx);
3924 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
3925 let multi_buffer_visible_start = self
3926 .scroll_manager
3927 .anchor()
3928 .anchor
3929 .to_point(&multi_buffer_snapshot);
3930 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
3931 multi_buffer_visible_start
3932 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
3933 Bias::Left,
3934 );
3935 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
3936 multi_buffer
3937 .range_to_buffer_ranges(multi_buffer_visible_range, cx)
3938 .into_iter()
3939 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
3940 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
3941 let buffer = buffer_handle.read(cx);
3942 let buffer_file = project::File::from_dyn(buffer.file())?;
3943 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
3944 let worktree_entry = buffer_worktree
3945 .read(cx)
3946 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
3947 if worktree_entry.is_ignored {
3948 return None;
3949 }
3950
3951 let language = buffer.language()?;
3952 if let Some(restrict_to_languages) = restrict_to_languages {
3953 if !restrict_to_languages.contains(language) {
3954 return None;
3955 }
3956 }
3957 Some((
3958 excerpt_id,
3959 (
3960 buffer_handle,
3961 buffer.version().clone(),
3962 excerpt_visible_range,
3963 ),
3964 ))
3965 })
3966 .collect()
3967 }
3968
3969 pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
3970 TextLayoutDetails {
3971 text_system: cx.text_system().clone(),
3972 editor_style: self.style.clone().unwrap(),
3973 rem_size: cx.rem_size(),
3974 scroll_anchor: self.scroll_manager.anchor(),
3975 visible_rows: self.visible_line_count(),
3976 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
3977 }
3978 }
3979
3980 fn splice_inlays(
3981 &self,
3982 to_remove: Vec<InlayId>,
3983 to_insert: Vec<Inlay>,
3984 cx: &mut ViewContext<Self>,
3985 ) {
3986 self.display_map.update(cx, |display_map, cx| {
3987 display_map.splice_inlays(to_remove, to_insert, cx);
3988 });
3989 cx.notify();
3990 }
3991
3992 fn trigger_on_type_formatting(
3993 &self,
3994 input: String,
3995 cx: &mut ViewContext<Self>,
3996 ) -> Option<Task<Result<()>>> {
3997 if input.len() != 1 {
3998 return None;
3999 }
4000
4001 let project = self.project.as_ref()?;
4002 let position = self.selections.newest_anchor().head();
4003 let (buffer, buffer_position) = self
4004 .buffer
4005 .read(cx)
4006 .text_anchor_for_position(position, cx)?;
4007
4008 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4009 // hence we do LSP request & edit on host side only — add formats to host's history.
4010 let push_to_lsp_host_history = true;
4011 // If this is not the host, append its history with new edits.
4012 let push_to_client_history = project.read(cx).is_remote();
4013
4014 let on_type_formatting = project.update(cx, |project, cx| {
4015 project.on_type_format(
4016 buffer.clone(),
4017 buffer_position,
4018 input,
4019 push_to_lsp_host_history,
4020 cx,
4021 )
4022 });
4023 Some(cx.spawn(|editor, mut cx| async move {
4024 if let Some(transaction) = on_type_formatting.await? {
4025 if push_to_client_history {
4026 buffer
4027 .update(&mut cx, |buffer, _| {
4028 buffer.push_transaction(transaction, Instant::now());
4029 })
4030 .ok();
4031 }
4032 editor.update(&mut cx, |editor, cx| {
4033 editor.refresh_document_highlights(cx);
4034 })?;
4035 }
4036 Ok(())
4037 }))
4038 }
4039
4040 pub fn show_completions(&mut self, options: &ShowCompletions, cx: &mut ViewContext<Self>) {
4041 if self.pending_rename.is_some() {
4042 return;
4043 }
4044
4045 let Some(provider) = self.completion_provider.as_ref() else {
4046 return;
4047 };
4048
4049 let position = self.selections.newest_anchor().head();
4050 let (buffer, buffer_position) =
4051 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4052 output
4053 } else {
4054 return;
4055 };
4056
4057 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4058 let is_followup_invoke = {
4059 let context_menu_state = self.context_menu.read();
4060 matches!(
4061 context_menu_state.deref(),
4062 Some(ContextMenu::Completions(_))
4063 )
4064 };
4065 let trigger_kind = match (&options.trigger, is_followup_invoke) {
4066 (_, true) => CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS,
4067 (Some(trigger), _) if buffer.read(cx).completion_triggers().contains(&trigger) => {
4068 CompletionTriggerKind::TRIGGER_CHARACTER
4069 }
4070
4071 _ => CompletionTriggerKind::INVOKED,
4072 };
4073 let completion_context = CompletionContext {
4074 trigger_character: options.trigger.as_ref().and_then(|trigger| {
4075 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4076 Some(String::from(trigger))
4077 } else {
4078 None
4079 }
4080 }),
4081 trigger_kind,
4082 };
4083 let completions = provider.completions(&buffer, buffer_position, completion_context, cx);
4084
4085 let id = post_inc(&mut self.next_completion_id);
4086 let task = cx.spawn(|this, mut cx| {
4087 async move {
4088 this.update(&mut cx, |this, _| {
4089 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4090 })?;
4091 let completions = completions.await.log_err();
4092 let menu = if let Some(completions) = completions {
4093 let mut menu = CompletionsMenu {
4094 id,
4095 initial_position: position,
4096 match_candidates: completions
4097 .iter()
4098 .enumerate()
4099 .map(|(id, completion)| {
4100 StringMatchCandidate::new(
4101 id,
4102 completion.label.text[completion.label.filter_range.clone()]
4103 .into(),
4104 )
4105 })
4106 .collect(),
4107 buffer: buffer.clone(),
4108 completions: Arc::new(RwLock::new(completions.into())),
4109 matches: Vec::new().into(),
4110 selected_item: 0,
4111 scroll_handle: UniformListScrollHandle::new(),
4112 selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new(
4113 DebouncedDelay::new(),
4114 )),
4115 };
4116 menu.filter(query.as_deref(), cx.background_executor().clone())
4117 .await;
4118
4119 if menu.matches.is_empty() {
4120 None
4121 } else {
4122 this.update(&mut cx, |editor, cx| {
4123 let completions = menu.completions.clone();
4124 let matches = menu.matches.clone();
4125
4126 let delay_ms = EditorSettings::get_global(cx)
4127 .completion_documentation_secondary_query_debounce;
4128 let delay = Duration::from_millis(delay_ms);
4129 editor
4130 .completion_documentation_pre_resolve_debounce
4131 .fire_new(delay, cx, |editor, cx| {
4132 CompletionsMenu::pre_resolve_completion_documentation(
4133 buffer,
4134 completions,
4135 matches,
4136 editor,
4137 cx,
4138 )
4139 });
4140 })
4141 .ok();
4142 Some(menu)
4143 }
4144 } else {
4145 None
4146 };
4147
4148 this.update(&mut cx, |this, cx| {
4149 let mut context_menu = this.context_menu.write();
4150 match context_menu.as_ref() {
4151 None => {}
4152
4153 Some(ContextMenu::Completions(prev_menu)) => {
4154 if prev_menu.id > id {
4155 return;
4156 }
4157 }
4158
4159 _ => return,
4160 }
4161
4162 if this.focus_handle.is_focused(cx) && menu.is_some() {
4163 let menu = menu.unwrap();
4164 *context_menu = Some(ContextMenu::Completions(menu));
4165 drop(context_menu);
4166 this.discard_inline_completion(false, cx);
4167 cx.notify();
4168 } else if this.completion_tasks.len() <= 1 {
4169 // If there are no more completion tasks and the last menu was
4170 // empty, we should hide it. If it was already hidden, we should
4171 // also show the copilot completion when available.
4172 drop(context_menu);
4173 if this.hide_context_menu(cx).is_none() {
4174 this.update_visible_inline_completion(cx);
4175 }
4176 }
4177 })?;
4178
4179 Ok::<_, anyhow::Error>(())
4180 }
4181 .log_err()
4182 });
4183
4184 self.completion_tasks.push((id, task));
4185 }
4186
4187 pub fn confirm_completion(
4188 &mut self,
4189 action: &ConfirmCompletion,
4190 cx: &mut ViewContext<Self>,
4191 ) -> Option<Task<Result<()>>> {
4192 use language::ToOffset as _;
4193
4194 let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
4195 menu
4196 } else {
4197 return None;
4198 };
4199
4200 let mat = completions_menu
4201 .matches
4202 .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
4203 let buffer_handle = completions_menu.buffer;
4204 let completions = completions_menu.completions.read();
4205 let completion = completions.get(mat.candidate_id)?;
4206 cx.stop_propagation();
4207
4208 let snippet;
4209 let text;
4210
4211 if completion.is_snippet() {
4212 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4213 text = snippet.as_ref().unwrap().text.clone();
4214 } else {
4215 snippet = None;
4216 text = completion.new_text.clone();
4217 };
4218 let selections = self.selections.all::<usize>(cx);
4219 let buffer = buffer_handle.read(cx);
4220 let old_range = completion.old_range.to_offset(buffer);
4221 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4222
4223 let newest_selection = self.selections.newest_anchor();
4224 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4225 return None;
4226 }
4227
4228 let lookbehind = newest_selection
4229 .start
4230 .text_anchor
4231 .to_offset(buffer)
4232 .saturating_sub(old_range.start);
4233 let lookahead = old_range
4234 .end
4235 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4236 let mut common_prefix_len = old_text
4237 .bytes()
4238 .zip(text.bytes())
4239 .take_while(|(a, b)| a == b)
4240 .count();
4241
4242 let snapshot = self.buffer.read(cx).snapshot(cx);
4243 let mut range_to_replace: Option<Range<isize>> = None;
4244 let mut ranges = Vec::new();
4245 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4246 for selection in &selections {
4247 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4248 let start = selection.start.saturating_sub(lookbehind);
4249 let end = selection.end + lookahead;
4250 if selection.id == newest_selection.id {
4251 range_to_replace = Some(
4252 ((start + common_prefix_len) as isize - selection.start as isize)
4253 ..(end as isize - selection.start as isize),
4254 );
4255 }
4256 ranges.push(start + common_prefix_len..end);
4257 } else {
4258 common_prefix_len = 0;
4259 ranges.clear();
4260 ranges.extend(selections.iter().map(|s| {
4261 if s.id == newest_selection.id {
4262 range_to_replace = Some(
4263 old_range.start.to_offset_utf16(&snapshot).0 as isize
4264 - selection.start as isize
4265 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4266 - selection.start as isize,
4267 );
4268 old_range.clone()
4269 } else {
4270 s.start..s.end
4271 }
4272 }));
4273 break;
4274 }
4275 if !self.linked_edit_ranges.is_empty() {
4276 let start_anchor = snapshot.anchor_before(selection.head());
4277 let end_anchor = snapshot.anchor_after(selection.tail());
4278 if let Some(ranges) = self
4279 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4280 {
4281 for (buffer, edits) in ranges {
4282 linked_edits.entry(buffer.clone()).or_default().extend(
4283 edits
4284 .into_iter()
4285 .map(|range| (range, text[common_prefix_len..].to_owned())),
4286 );
4287 }
4288 }
4289 }
4290 }
4291 let text = &text[common_prefix_len..];
4292
4293 cx.emit(EditorEvent::InputHandled {
4294 utf16_range_to_replace: range_to_replace,
4295 text: text.into(),
4296 });
4297
4298 self.transact(cx, |this, cx| {
4299 if let Some(mut snippet) = snippet {
4300 snippet.text = text.to_string();
4301 for tabstop in snippet.tabstops.iter_mut().flatten() {
4302 tabstop.start -= common_prefix_len as isize;
4303 tabstop.end -= common_prefix_len as isize;
4304 }
4305
4306 this.insert_snippet(&ranges, snippet, cx).log_err();
4307 } else {
4308 this.buffer.update(cx, |buffer, cx| {
4309 buffer.edit(
4310 ranges.iter().map(|range| (range.clone(), text)),
4311 this.autoindent_mode.clone(),
4312 cx,
4313 );
4314 });
4315 }
4316 for (buffer, edits) in linked_edits {
4317 buffer.update(cx, |buffer, cx| {
4318 let snapshot = buffer.snapshot();
4319 let edits = edits
4320 .into_iter()
4321 .map(|(range, text)| {
4322 use text::ToPoint as TP;
4323 let end_point = TP::to_point(&range.end, &snapshot);
4324 let start_point = TP::to_point(&range.start, &snapshot);
4325 (start_point..end_point, text)
4326 })
4327 .sorted_by_key(|(range, _)| range.start)
4328 .collect::<Vec<_>>();
4329 buffer.edit(edits, None, cx);
4330 })
4331 }
4332
4333 this.refresh_inline_completion(true, cx);
4334 });
4335
4336 if let Some(confirm) = completion.confirm.as_ref() {
4337 (confirm)(cx);
4338 }
4339
4340 if completion.show_new_completions_on_confirm {
4341 self.show_completions(&ShowCompletions { trigger: None }, cx);
4342 }
4343
4344 let provider = self.completion_provider.as_ref()?;
4345 let apply_edits = provider.apply_additional_edits_for_completion(
4346 buffer_handle,
4347 completion.clone(),
4348 true,
4349 cx,
4350 );
4351
4352 let editor_settings = EditorSettings::get_global(cx);
4353 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4354 // After the code completion is finished, users often want to know what signatures are needed.
4355 // so we should automatically call signature_help
4356 self.show_signature_help(&ShowSignatureHelp, cx);
4357 }
4358
4359 Some(cx.foreground_executor().spawn(async move {
4360 apply_edits.await?;
4361 Ok(())
4362 }))
4363 }
4364
4365 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
4366 let mut context_menu = self.context_menu.write();
4367 if let Some(ContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4368 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4369 // Toggle if we're selecting the same one
4370 *context_menu = None;
4371 cx.notify();
4372 return;
4373 } else {
4374 // Otherwise, clear it and start a new one
4375 *context_menu = None;
4376 cx.notify();
4377 }
4378 }
4379 drop(context_menu);
4380 let snapshot = self.snapshot(cx);
4381 let deployed_from_indicator = action.deployed_from_indicator;
4382 let mut task = self.code_actions_task.take();
4383 let action = action.clone();
4384 cx.spawn(|editor, mut cx| async move {
4385 while let Some(prev_task) = task {
4386 prev_task.await;
4387 task = editor.update(&mut cx, |this, _| this.code_actions_task.take())?;
4388 }
4389
4390 let spawned_test_task = editor.update(&mut cx, |editor, cx| {
4391 if editor.focus_handle.is_focused(cx) {
4392 let multibuffer_point = action
4393 .deployed_from_indicator
4394 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4395 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4396 let (buffer, buffer_row) = snapshot
4397 .buffer_snapshot
4398 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4399 .and_then(|(buffer_snapshot, range)| {
4400 editor
4401 .buffer
4402 .read(cx)
4403 .buffer(buffer_snapshot.remote_id())
4404 .map(|buffer| (buffer, range.start.row))
4405 })?;
4406 let (_, code_actions) = editor
4407 .available_code_actions
4408 .clone()
4409 .and_then(|(location, code_actions)| {
4410 let snapshot = location.buffer.read(cx).snapshot();
4411 let point_range = location.range.to_point(&snapshot);
4412 let point_range = point_range.start.row..=point_range.end.row;
4413 if point_range.contains(&buffer_row) {
4414 Some((location, code_actions))
4415 } else {
4416 None
4417 }
4418 })
4419 .unzip();
4420 let buffer_id = buffer.read(cx).remote_id();
4421 let tasks = editor
4422 .tasks
4423 .get(&(buffer_id, buffer_row))
4424 .map(|t| Arc::new(t.to_owned()));
4425 if tasks.is_none() && code_actions.is_none() {
4426 return None;
4427 }
4428
4429 editor.completion_tasks.clear();
4430 editor.discard_inline_completion(false, cx);
4431 let task_context =
4432 tasks
4433 .as_ref()
4434 .zip(editor.project.clone())
4435 .map(|(tasks, project)| {
4436 let position = Point::new(buffer_row, tasks.column);
4437 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
4438 let location = Location {
4439 buffer: buffer.clone(),
4440 range: range_start..range_start,
4441 };
4442 // Fill in the environmental variables from the tree-sitter captures
4443 let mut captured_task_variables = TaskVariables::default();
4444 for (capture_name, value) in tasks.extra_variables.clone() {
4445 captured_task_variables.insert(
4446 task::VariableName::Custom(capture_name.into()),
4447 value.clone(),
4448 );
4449 }
4450 project.update(cx, |project, cx| {
4451 project.task_context_for_location(
4452 captured_task_variables,
4453 location,
4454 cx,
4455 )
4456 })
4457 });
4458
4459 Some(cx.spawn(|editor, mut cx| async move {
4460 let task_context = match task_context {
4461 Some(task_context) => task_context.await,
4462 None => None,
4463 };
4464 let resolved_tasks =
4465 tasks.zip(task_context).map(|(tasks, task_context)| {
4466 Arc::new(ResolvedTasks {
4467 templates: tasks
4468 .templates
4469 .iter()
4470 .filter_map(|(kind, template)| {
4471 template
4472 .resolve_task(&kind.to_id_base(), &task_context)
4473 .map(|task| (kind.clone(), task))
4474 })
4475 .collect(),
4476 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4477 multibuffer_point.row,
4478 tasks.column,
4479 )),
4480 })
4481 });
4482 let spawn_straight_away = resolved_tasks
4483 .as_ref()
4484 .map_or(false, |tasks| tasks.templates.len() == 1)
4485 && code_actions
4486 .as_ref()
4487 .map_or(true, |actions| actions.is_empty());
4488 if let Some(task) = editor
4489 .update(&mut cx, |editor, cx| {
4490 *editor.context_menu.write() =
4491 Some(ContextMenu::CodeActions(CodeActionsMenu {
4492 buffer,
4493 actions: CodeActionContents {
4494 tasks: resolved_tasks,
4495 actions: code_actions,
4496 },
4497 selected_item: Default::default(),
4498 scroll_handle: UniformListScrollHandle::default(),
4499 deployed_from_indicator,
4500 }));
4501 if spawn_straight_away {
4502 if let Some(task) = editor.confirm_code_action(
4503 &ConfirmCodeAction { item_ix: Some(0) },
4504 cx,
4505 ) {
4506 cx.notify();
4507 return task;
4508 }
4509 }
4510 cx.notify();
4511 Task::ready(Ok(()))
4512 })
4513 .ok()
4514 {
4515 task.await
4516 } else {
4517 Ok(())
4518 }
4519 }))
4520 } else {
4521 Some(Task::ready(Ok(())))
4522 }
4523 })?;
4524 if let Some(task) = spawned_test_task {
4525 task.await?;
4526 }
4527
4528 Ok::<_, anyhow::Error>(())
4529 })
4530 .detach_and_log_err(cx);
4531 }
4532
4533 pub fn confirm_code_action(
4534 &mut self,
4535 action: &ConfirmCodeAction,
4536 cx: &mut ViewContext<Self>,
4537 ) -> Option<Task<Result<()>>> {
4538 let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
4539 menu
4540 } else {
4541 return None;
4542 };
4543 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4544 let action = actions_menu.actions.get(action_ix)?;
4545 let title = action.label();
4546 let buffer = actions_menu.buffer;
4547 let workspace = self.workspace()?;
4548
4549 match action {
4550 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4551 workspace.update(cx, |workspace, cx| {
4552 workspace::tasks::schedule_resolved_task(
4553 workspace,
4554 task_source_kind,
4555 resolved_task,
4556 false,
4557 cx,
4558 );
4559
4560 Some(Task::ready(Ok(())))
4561 })
4562 }
4563 CodeActionsItem::CodeAction(action) => {
4564 let apply_code_actions = workspace
4565 .read(cx)
4566 .project()
4567 .clone()
4568 .update(cx, |project, cx| {
4569 project.apply_code_action(buffer, action, true, cx)
4570 });
4571 let workspace = workspace.downgrade();
4572 Some(cx.spawn(|editor, cx| async move {
4573 let project_transaction = apply_code_actions.await?;
4574 Self::open_project_transaction(
4575 &editor,
4576 workspace,
4577 project_transaction,
4578 title,
4579 cx,
4580 )
4581 .await
4582 }))
4583 }
4584 }
4585 }
4586
4587 pub async fn open_project_transaction(
4588 this: &WeakView<Editor>,
4589 workspace: WeakView<Workspace>,
4590 transaction: ProjectTransaction,
4591 title: String,
4592 mut cx: AsyncWindowContext,
4593 ) -> Result<()> {
4594 let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
4595
4596 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4597 cx.update(|cx| {
4598 entries.sort_unstable_by_key(|(buffer, _)| {
4599 buffer.read(cx).file().map(|f| f.path().clone())
4600 });
4601 })?;
4602
4603 // If the project transaction's edits are all contained within this editor, then
4604 // avoid opening a new editor to display them.
4605
4606 if let Some((buffer, transaction)) = entries.first() {
4607 if entries.len() == 1 {
4608 let excerpt = this.update(&mut cx, |editor, cx| {
4609 editor
4610 .buffer()
4611 .read(cx)
4612 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4613 })?;
4614 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4615 if excerpted_buffer == *buffer {
4616 let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
4617 let excerpt_range = excerpt_range.to_offset(buffer);
4618 buffer
4619 .edited_ranges_for_transaction::<usize>(transaction)
4620 .all(|range| {
4621 excerpt_range.start <= range.start
4622 && excerpt_range.end >= range.end
4623 })
4624 })?;
4625
4626 if all_edits_within_excerpt {
4627 return Ok(());
4628 }
4629 }
4630 }
4631 }
4632 } else {
4633 return Ok(());
4634 }
4635
4636 let mut ranges_to_highlight = Vec::new();
4637 let excerpt_buffer = cx.new_model(|cx| {
4638 let mut multibuffer =
4639 MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
4640 for (buffer_handle, transaction) in &entries {
4641 let buffer = buffer_handle.read(cx);
4642 ranges_to_highlight.extend(
4643 multibuffer.push_excerpts_with_context_lines(
4644 buffer_handle.clone(),
4645 buffer
4646 .edited_ranges_for_transaction::<usize>(transaction)
4647 .collect(),
4648 DEFAULT_MULTIBUFFER_CONTEXT,
4649 cx,
4650 ),
4651 );
4652 }
4653 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4654 multibuffer
4655 })?;
4656
4657 workspace.update(&mut cx, |workspace, cx| {
4658 let project = workspace.project().clone();
4659 let editor =
4660 cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, cx));
4661 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, cx);
4662 editor.update(cx, |editor, cx| {
4663 editor.highlight_background::<Self>(
4664 &ranges_to_highlight,
4665 |theme| theme.editor_highlighted_line_background,
4666 cx,
4667 );
4668 });
4669 })?;
4670
4671 Ok(())
4672 }
4673
4674 fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4675 let project = self.project.clone()?;
4676 let buffer = self.buffer.read(cx);
4677 let newest_selection = self.selections.newest_anchor().clone();
4678 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4679 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4680 if start_buffer != end_buffer {
4681 return None;
4682 }
4683
4684 self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
4685 cx.background_executor()
4686 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4687 .await;
4688
4689 let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
4690 project.code_actions(&start_buffer, start..end, cx)
4691 }) {
4692 code_actions.await
4693 } else {
4694 Vec::new()
4695 };
4696
4697 this.update(&mut cx, |this, cx| {
4698 this.available_code_actions = if actions.is_empty() {
4699 None
4700 } else {
4701 Some((
4702 Location {
4703 buffer: start_buffer,
4704 range: start..end,
4705 },
4706 actions.into(),
4707 ))
4708 };
4709 cx.notify();
4710 })
4711 .log_err();
4712 }));
4713 None
4714 }
4715
4716 fn start_inline_blame_timer(&mut self, cx: &mut ViewContext<Self>) {
4717 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4718 self.show_git_blame_inline = false;
4719
4720 self.show_git_blame_inline_delay_task = Some(cx.spawn(|this, mut cx| async move {
4721 cx.background_executor().timer(delay).await;
4722
4723 this.update(&mut cx, |this, cx| {
4724 this.show_git_blame_inline = true;
4725 cx.notify();
4726 })
4727 .log_err();
4728 }));
4729 }
4730 }
4731
4732 fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4733 if self.pending_rename.is_some() {
4734 return None;
4735 }
4736
4737 let project = self.project.clone()?;
4738 let buffer = self.buffer.read(cx);
4739 let newest_selection = self.selections.newest_anchor().clone();
4740 let cursor_position = newest_selection.head();
4741 let (cursor_buffer, cursor_buffer_position) =
4742 buffer.text_anchor_for_position(cursor_position, cx)?;
4743 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4744 if cursor_buffer != tail_buffer {
4745 return None;
4746 }
4747
4748 self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
4749 cx.background_executor()
4750 .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
4751 .await;
4752
4753 let highlights = if let Some(highlights) = project
4754 .update(&mut cx, |project, cx| {
4755 project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4756 })
4757 .log_err()
4758 {
4759 highlights.await.log_err()
4760 } else {
4761 None
4762 };
4763
4764 if let Some(highlights) = highlights {
4765 this.update(&mut cx, |this, cx| {
4766 if this.pending_rename.is_some() {
4767 return;
4768 }
4769
4770 let buffer_id = cursor_position.buffer_id;
4771 let buffer = this.buffer.read(cx);
4772 if !buffer
4773 .text_anchor_for_position(cursor_position, cx)
4774 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
4775 {
4776 return;
4777 }
4778
4779 let cursor_buffer_snapshot = cursor_buffer.read(cx);
4780 let mut write_ranges = Vec::new();
4781 let mut read_ranges = Vec::new();
4782 for highlight in highlights {
4783 for (excerpt_id, excerpt_range) in
4784 buffer.excerpts_for_buffer(&cursor_buffer, cx)
4785 {
4786 let start = highlight
4787 .range
4788 .start
4789 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
4790 let end = highlight
4791 .range
4792 .end
4793 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
4794 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
4795 continue;
4796 }
4797
4798 let range = Anchor {
4799 buffer_id,
4800 excerpt_id: excerpt_id,
4801 text_anchor: start,
4802 }..Anchor {
4803 buffer_id,
4804 excerpt_id,
4805 text_anchor: end,
4806 };
4807 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
4808 write_ranges.push(range);
4809 } else {
4810 read_ranges.push(range);
4811 }
4812 }
4813 }
4814
4815 this.highlight_background::<DocumentHighlightRead>(
4816 &read_ranges,
4817 |theme| theme.editor_document_highlight_read_background,
4818 cx,
4819 );
4820 this.highlight_background::<DocumentHighlightWrite>(
4821 &write_ranges,
4822 |theme| theme.editor_document_highlight_write_background,
4823 cx,
4824 );
4825 cx.notify();
4826 })
4827 .log_err();
4828 }
4829 }));
4830 None
4831 }
4832
4833 fn refresh_inline_completion(
4834 &mut self,
4835 debounce: bool,
4836 cx: &mut ViewContext<Self>,
4837 ) -> Option<()> {
4838 let provider = self.inline_completion_provider()?;
4839 let cursor = self.selections.newest_anchor().head();
4840 let (buffer, cursor_buffer_position) =
4841 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4842 if !self.show_inline_completions
4843 || !provider.is_enabled(&buffer, cursor_buffer_position, cx)
4844 {
4845 self.discard_inline_completion(false, cx);
4846 return None;
4847 }
4848
4849 self.update_visible_inline_completion(cx);
4850 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
4851 Some(())
4852 }
4853
4854 fn cycle_inline_completion(
4855 &mut self,
4856 direction: Direction,
4857 cx: &mut ViewContext<Self>,
4858 ) -> Option<()> {
4859 let provider = self.inline_completion_provider()?;
4860 let cursor = self.selections.newest_anchor().head();
4861 let (buffer, cursor_buffer_position) =
4862 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4863 if !self.show_inline_completions
4864 || !provider.is_enabled(&buffer, cursor_buffer_position, cx)
4865 {
4866 return None;
4867 }
4868
4869 provider.cycle(buffer, cursor_buffer_position, direction, cx);
4870 self.update_visible_inline_completion(cx);
4871
4872 Some(())
4873 }
4874
4875 pub fn show_inline_completion(&mut self, _: &ShowInlineCompletion, cx: &mut ViewContext<Self>) {
4876 if !self.has_active_inline_completion(cx) {
4877 self.refresh_inline_completion(false, cx);
4878 return;
4879 }
4880
4881 self.update_visible_inline_completion(cx);
4882 }
4883
4884 pub fn display_cursor_names(&mut self, _: &DisplayCursorNames, cx: &mut ViewContext<Self>) {
4885 self.show_cursor_names(cx);
4886 }
4887
4888 fn show_cursor_names(&mut self, cx: &mut ViewContext<Self>) {
4889 self.show_cursor_names = true;
4890 cx.notify();
4891 cx.spawn(|this, mut cx| async move {
4892 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
4893 this.update(&mut cx, |this, cx| {
4894 this.show_cursor_names = false;
4895 cx.notify()
4896 })
4897 .ok()
4898 })
4899 .detach();
4900 }
4901
4902 pub fn next_inline_completion(&mut self, _: &NextInlineCompletion, cx: &mut ViewContext<Self>) {
4903 if self.has_active_inline_completion(cx) {
4904 self.cycle_inline_completion(Direction::Next, cx);
4905 } else {
4906 let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
4907 if is_copilot_disabled {
4908 cx.propagate();
4909 }
4910 }
4911 }
4912
4913 pub fn previous_inline_completion(
4914 &mut self,
4915 _: &PreviousInlineCompletion,
4916 cx: &mut ViewContext<Self>,
4917 ) {
4918 if self.has_active_inline_completion(cx) {
4919 self.cycle_inline_completion(Direction::Prev, cx);
4920 } else {
4921 let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
4922 if is_copilot_disabled {
4923 cx.propagate();
4924 }
4925 }
4926 }
4927
4928 pub fn accept_inline_completion(
4929 &mut self,
4930 _: &AcceptInlineCompletion,
4931 cx: &mut ViewContext<Self>,
4932 ) {
4933 let Some(completion) = self.take_active_inline_completion(cx) else {
4934 return;
4935 };
4936 if let Some(provider) = self.inline_completion_provider() {
4937 provider.accept(cx);
4938 }
4939
4940 cx.emit(EditorEvent::InputHandled {
4941 utf16_range_to_replace: None,
4942 text: completion.text.to_string().into(),
4943 });
4944 self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
4945 self.refresh_inline_completion(true, cx);
4946 cx.notify();
4947 }
4948
4949 pub fn accept_partial_inline_completion(
4950 &mut self,
4951 _: &AcceptPartialInlineCompletion,
4952 cx: &mut ViewContext<Self>,
4953 ) {
4954 if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
4955 if let Some(completion) = self.take_active_inline_completion(cx) {
4956 let mut partial_completion = completion
4957 .text
4958 .chars()
4959 .by_ref()
4960 .take_while(|c| c.is_alphabetic())
4961 .collect::<String>();
4962 if partial_completion.is_empty() {
4963 partial_completion = completion
4964 .text
4965 .chars()
4966 .by_ref()
4967 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
4968 .collect::<String>();
4969 }
4970
4971 cx.emit(EditorEvent::InputHandled {
4972 utf16_range_to_replace: None,
4973 text: partial_completion.clone().into(),
4974 });
4975 self.insert_with_autoindent_mode(&partial_completion, None, cx);
4976 self.refresh_inline_completion(true, cx);
4977 cx.notify();
4978 }
4979 }
4980 }
4981
4982 fn discard_inline_completion(
4983 &mut self,
4984 should_report_inline_completion_event: bool,
4985 cx: &mut ViewContext<Self>,
4986 ) -> bool {
4987 if let Some(provider) = self.inline_completion_provider() {
4988 provider.discard(should_report_inline_completion_event, cx);
4989 }
4990
4991 self.take_active_inline_completion(cx).is_some()
4992 }
4993
4994 pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
4995 if let Some(completion) = self.active_inline_completion.as_ref() {
4996 let buffer = self.buffer.read(cx).read(cx);
4997 completion.position.is_valid(&buffer)
4998 } else {
4999 false
5000 }
5001 }
5002
5003 fn take_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
5004 let completion = self.active_inline_completion.take()?;
5005 self.display_map.update(cx, |map, cx| {
5006 map.splice_inlays(vec![completion.id], Default::default(), cx);
5007 });
5008 let buffer = self.buffer.read(cx).read(cx);
5009
5010 if completion.position.is_valid(&buffer) {
5011 Some(completion)
5012 } else {
5013 None
5014 }
5015 }
5016
5017 fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
5018 let selection = self.selections.newest_anchor();
5019 let cursor = selection.head();
5020
5021 if self.context_menu.read().is_none()
5022 && self.completion_tasks.is_empty()
5023 && selection.start == selection.end
5024 {
5025 if let Some(provider) = self.inline_completion_provider() {
5026 if let Some((buffer, cursor_buffer_position)) =
5027 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5028 {
5029 if let Some(text) =
5030 provider.active_completion_text(&buffer, cursor_buffer_position, cx)
5031 {
5032 let text = Rope::from(text);
5033 let mut to_remove = Vec::new();
5034 if let Some(completion) = self.active_inline_completion.take() {
5035 to_remove.push(completion.id);
5036 }
5037
5038 let completion_inlay =
5039 Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
5040 self.active_inline_completion = Some(completion_inlay.clone());
5041 self.display_map.update(cx, move |map, cx| {
5042 map.splice_inlays(to_remove, vec![completion_inlay], cx)
5043 });
5044 cx.notify();
5045 return;
5046 }
5047 }
5048 }
5049 }
5050
5051 self.discard_inline_completion(false, cx);
5052 }
5053
5054 fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
5055 Some(self.inline_completion_provider.as_ref()?.provider.clone())
5056 }
5057
5058 fn render_code_actions_indicator(
5059 &self,
5060 _style: &EditorStyle,
5061 row: DisplayRow,
5062 is_active: bool,
5063 cx: &mut ViewContext<Self>,
5064 ) -> Option<IconButton> {
5065 if self.available_code_actions.is_some() {
5066 Some(
5067 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
5068 .shape(ui::IconButtonShape::Square)
5069 .icon_size(IconSize::XSmall)
5070 .icon_color(Color::Muted)
5071 .selected(is_active)
5072 .on_click(cx.listener(move |editor, _e, cx| {
5073 editor.focus(cx);
5074 editor.toggle_code_actions(
5075 &ToggleCodeActions {
5076 deployed_from_indicator: Some(row),
5077 },
5078 cx,
5079 );
5080 })),
5081 )
5082 } else {
5083 None
5084 }
5085 }
5086
5087 fn clear_tasks(&mut self) {
5088 self.tasks.clear()
5089 }
5090
5091 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
5092 if let Some(_) = self.tasks.insert(key, value) {
5093 // This case should hopefully be rare, but just in case...
5094 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
5095 }
5096 }
5097
5098 fn render_run_indicator(
5099 &self,
5100 _style: &EditorStyle,
5101 is_active: bool,
5102 row: DisplayRow,
5103 cx: &mut ViewContext<Self>,
5104 ) -> IconButton {
5105 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
5106 .shape(ui::IconButtonShape::Square)
5107 .icon_size(IconSize::XSmall)
5108 .icon_color(Color::Muted)
5109 .selected(is_active)
5110 .on_click(cx.listener(move |editor, _e, cx| {
5111 editor.focus(cx);
5112 editor.toggle_code_actions(
5113 &ToggleCodeActions {
5114 deployed_from_indicator: Some(row),
5115 },
5116 cx,
5117 );
5118 }))
5119 }
5120
5121 pub fn context_menu_visible(&self) -> bool {
5122 self.context_menu
5123 .read()
5124 .as_ref()
5125 .map_or(false, |menu| menu.visible())
5126 }
5127
5128 fn render_context_menu(
5129 &self,
5130 cursor_position: DisplayPoint,
5131 style: &EditorStyle,
5132 max_height: Pixels,
5133 cx: &mut ViewContext<Editor>,
5134 ) -> Option<(ContextMenuOrigin, AnyElement)> {
5135 self.context_menu.read().as_ref().map(|menu| {
5136 menu.render(
5137 cursor_position,
5138 style,
5139 max_height,
5140 self.workspace.as_ref().map(|(w, _)| w.clone()),
5141 cx,
5142 )
5143 })
5144 }
5145
5146 fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
5147 cx.notify();
5148 self.completion_tasks.clear();
5149 let context_menu = self.context_menu.write().take();
5150 if context_menu.is_some() {
5151 self.update_visible_inline_completion(cx);
5152 }
5153 context_menu
5154 }
5155
5156 pub fn insert_snippet(
5157 &mut self,
5158 insertion_ranges: &[Range<usize>],
5159 snippet: Snippet,
5160 cx: &mut ViewContext<Self>,
5161 ) -> Result<()> {
5162 struct Tabstop<T> {
5163 is_end_tabstop: bool,
5164 ranges: Vec<Range<T>>,
5165 }
5166
5167 let tabstops = self.buffer.update(cx, |buffer, cx| {
5168 let snippet_text: Arc<str> = snippet.text.clone().into();
5169 buffer.edit(
5170 insertion_ranges
5171 .iter()
5172 .cloned()
5173 .map(|range| (range, snippet_text.clone())),
5174 Some(AutoindentMode::EachLine),
5175 cx,
5176 );
5177
5178 let snapshot = &*buffer.read(cx);
5179 let snippet = &snippet;
5180 snippet
5181 .tabstops
5182 .iter()
5183 .map(|tabstop| {
5184 let is_end_tabstop = tabstop.first().map_or(false, |tabstop| {
5185 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
5186 });
5187 let mut tabstop_ranges = tabstop
5188 .iter()
5189 .flat_map(|tabstop_range| {
5190 let mut delta = 0_isize;
5191 insertion_ranges.iter().map(move |insertion_range| {
5192 let insertion_start = insertion_range.start as isize + delta;
5193 delta +=
5194 snippet.text.len() as isize - insertion_range.len() as isize;
5195
5196 let start = ((insertion_start + tabstop_range.start) as usize)
5197 .min(snapshot.len());
5198 let end = ((insertion_start + tabstop_range.end) as usize)
5199 .min(snapshot.len());
5200 snapshot.anchor_before(start)..snapshot.anchor_after(end)
5201 })
5202 })
5203 .collect::<Vec<_>>();
5204 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
5205
5206 Tabstop {
5207 is_end_tabstop,
5208 ranges: tabstop_ranges,
5209 }
5210 })
5211 .collect::<Vec<_>>()
5212 });
5213 if let Some(tabstop) = tabstops.first() {
5214 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5215 s.select_ranges(tabstop.ranges.iter().cloned());
5216 });
5217
5218 // If we're already at the last tabstop and it's at the end of the snippet,
5219 // we're done, we don't need to keep the state around.
5220 if !tabstop.is_end_tabstop {
5221 let ranges = tabstops
5222 .into_iter()
5223 .map(|tabstop| tabstop.ranges)
5224 .collect::<Vec<_>>();
5225 self.snippet_stack.push(SnippetState {
5226 active_index: 0,
5227 ranges,
5228 });
5229 }
5230
5231 // Check whether the just-entered snippet ends with an auto-closable bracket.
5232 if self.autoclose_regions.is_empty() {
5233 let snapshot = self.buffer.read(cx).snapshot(cx);
5234 for selection in &mut self.selections.all::<Point>(cx) {
5235 let selection_head = selection.head();
5236 let Some(scope) = snapshot.language_scope_at(selection_head) else {
5237 continue;
5238 };
5239
5240 let mut bracket_pair = None;
5241 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
5242 let prev_chars = snapshot
5243 .reversed_chars_at(selection_head)
5244 .collect::<String>();
5245 for (pair, enabled) in scope.brackets() {
5246 if enabled
5247 && pair.close
5248 && prev_chars.starts_with(pair.start.as_str())
5249 && next_chars.starts_with(pair.end.as_str())
5250 {
5251 bracket_pair = Some(pair.clone());
5252 break;
5253 }
5254 }
5255 if let Some(pair) = bracket_pair {
5256 let start = snapshot.anchor_after(selection_head);
5257 let end = snapshot.anchor_after(selection_head);
5258 self.autoclose_regions.push(AutocloseRegion {
5259 selection_id: selection.id,
5260 range: start..end,
5261 pair,
5262 });
5263 }
5264 }
5265 }
5266 }
5267 Ok(())
5268 }
5269
5270 pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
5271 self.move_to_snippet_tabstop(Bias::Right, cx)
5272 }
5273
5274 pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
5275 self.move_to_snippet_tabstop(Bias::Left, cx)
5276 }
5277
5278 pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
5279 if let Some(mut snippet) = self.snippet_stack.pop() {
5280 match bias {
5281 Bias::Left => {
5282 if snippet.active_index > 0 {
5283 snippet.active_index -= 1;
5284 } else {
5285 self.snippet_stack.push(snippet);
5286 return false;
5287 }
5288 }
5289 Bias::Right => {
5290 if snippet.active_index + 1 < snippet.ranges.len() {
5291 snippet.active_index += 1;
5292 } else {
5293 self.snippet_stack.push(snippet);
5294 return false;
5295 }
5296 }
5297 }
5298 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
5299 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5300 s.select_anchor_ranges(current_ranges.iter().cloned())
5301 });
5302 // If snippet state is not at the last tabstop, push it back on the stack
5303 if snippet.active_index + 1 < snippet.ranges.len() {
5304 self.snippet_stack.push(snippet);
5305 }
5306 return true;
5307 }
5308 }
5309
5310 false
5311 }
5312
5313 pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
5314 self.transact(cx, |this, cx| {
5315 this.select_all(&SelectAll, cx);
5316 this.insert("", cx);
5317 });
5318 }
5319
5320 pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
5321 self.transact(cx, |this, cx| {
5322 this.select_autoclose_pair(cx);
5323 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
5324 if !this.linked_edit_ranges.is_empty() {
5325 let selections = this.selections.all::<MultiBufferPoint>(cx);
5326 let snapshot = this.buffer.read(cx).snapshot(cx);
5327
5328 for selection in selections.iter() {
5329 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
5330 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
5331 if selection_start.buffer_id != selection_end.buffer_id {
5332 continue;
5333 }
5334 if let Some(ranges) =
5335 this.linked_editing_ranges_for(selection_start..selection_end, cx)
5336 {
5337 for (buffer, entries) in ranges {
5338 linked_ranges.entry(buffer).or_default().extend(entries);
5339 }
5340 }
5341 }
5342 }
5343
5344 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
5345 if !this.selections.line_mode {
5346 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5347 for selection in &mut selections {
5348 if selection.is_empty() {
5349 let old_head = selection.head();
5350 let mut new_head =
5351 movement::left(&display_map, old_head.to_display_point(&display_map))
5352 .to_point(&display_map);
5353 if let Some((buffer, line_buffer_range)) = display_map
5354 .buffer_snapshot
5355 .buffer_line_for_row(MultiBufferRow(old_head.row))
5356 {
5357 let indent_size =
5358 buffer.indent_size_for_line(line_buffer_range.start.row);
5359 let indent_len = match indent_size.kind {
5360 IndentKind::Space => {
5361 buffer.settings_at(line_buffer_range.start, cx).tab_size
5362 }
5363 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
5364 };
5365 if old_head.column <= indent_size.len && old_head.column > 0 {
5366 let indent_len = indent_len.get();
5367 new_head = cmp::min(
5368 new_head,
5369 MultiBufferPoint::new(
5370 old_head.row,
5371 ((old_head.column - 1) / indent_len) * indent_len,
5372 ),
5373 );
5374 }
5375 }
5376
5377 selection.set_head(new_head, SelectionGoal::None);
5378 }
5379 }
5380 }
5381
5382 this.signature_help_state.set_backspace_pressed(true);
5383 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5384 this.insert("", cx);
5385 let empty_str: Arc<str> = Arc::from("");
5386 for (buffer, edits) in linked_ranges {
5387 let snapshot = buffer.read(cx).snapshot();
5388 use text::ToPoint as TP;
5389
5390 let edits = edits
5391 .into_iter()
5392 .map(|range| {
5393 let end_point = TP::to_point(&range.end, &snapshot);
5394 let mut start_point = TP::to_point(&range.start, &snapshot);
5395
5396 if end_point == start_point {
5397 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
5398 .saturating_sub(1);
5399 start_point = TP::to_point(&offset, &snapshot);
5400 };
5401
5402 (start_point..end_point, empty_str.clone())
5403 })
5404 .sorted_by_key(|(range, _)| range.start)
5405 .collect::<Vec<_>>();
5406 buffer.update(cx, |this, cx| {
5407 this.edit(edits, None, cx);
5408 })
5409 }
5410 this.refresh_inline_completion(true, cx);
5411 linked_editing_ranges::refresh_linked_ranges(this, cx);
5412 });
5413 }
5414
5415 pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
5416 self.transact(cx, |this, cx| {
5417 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5418 let line_mode = s.line_mode;
5419 s.move_with(|map, selection| {
5420 if selection.is_empty() && !line_mode {
5421 let cursor = movement::right(map, selection.head());
5422 selection.end = cursor;
5423 selection.reversed = true;
5424 selection.goal = SelectionGoal::None;
5425 }
5426 })
5427 });
5428 this.insert("", cx);
5429 this.refresh_inline_completion(true, cx);
5430 });
5431 }
5432
5433 pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
5434 if self.move_to_prev_snippet_tabstop(cx) {
5435 return;
5436 }
5437
5438 self.outdent(&Outdent, cx);
5439 }
5440
5441 pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
5442 if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) {
5443 return;
5444 }
5445
5446 let mut selections = self.selections.all_adjusted(cx);
5447 let buffer = self.buffer.read(cx);
5448 let snapshot = buffer.snapshot(cx);
5449 let rows_iter = selections.iter().map(|s| s.head().row);
5450 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
5451
5452 let mut edits = Vec::new();
5453 let mut prev_edited_row = 0;
5454 let mut row_delta = 0;
5455 for selection in &mut selections {
5456 if selection.start.row != prev_edited_row {
5457 row_delta = 0;
5458 }
5459 prev_edited_row = selection.end.row;
5460
5461 // If the selection is non-empty, then increase the indentation of the selected lines.
5462 if !selection.is_empty() {
5463 row_delta =
5464 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5465 continue;
5466 }
5467
5468 // If the selection is empty and the cursor is in the leading whitespace before the
5469 // suggested indentation, then auto-indent the line.
5470 let cursor = selection.head();
5471 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
5472 if let Some(suggested_indent) =
5473 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
5474 {
5475 if cursor.column < suggested_indent.len
5476 && cursor.column <= current_indent.len
5477 && current_indent.len <= suggested_indent.len
5478 {
5479 selection.start = Point::new(cursor.row, suggested_indent.len);
5480 selection.end = selection.start;
5481 if row_delta == 0 {
5482 edits.extend(Buffer::edit_for_indent_size_adjustment(
5483 cursor.row,
5484 current_indent,
5485 suggested_indent,
5486 ));
5487 row_delta = suggested_indent.len - current_indent.len;
5488 }
5489 continue;
5490 }
5491 }
5492
5493 // Otherwise, insert a hard or soft tab.
5494 let settings = buffer.settings_at(cursor, cx);
5495 let tab_size = if settings.hard_tabs {
5496 IndentSize::tab()
5497 } else {
5498 let tab_size = settings.tab_size.get();
5499 let char_column = snapshot
5500 .text_for_range(Point::new(cursor.row, 0)..cursor)
5501 .flat_map(str::chars)
5502 .count()
5503 + row_delta as usize;
5504 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
5505 IndentSize::spaces(chars_to_next_tab_stop)
5506 };
5507 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
5508 selection.end = selection.start;
5509 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
5510 row_delta += tab_size.len;
5511 }
5512
5513 self.transact(cx, |this, cx| {
5514 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5515 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5516 this.refresh_inline_completion(true, cx);
5517 });
5518 }
5519
5520 pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
5521 if self.read_only(cx) {
5522 return;
5523 }
5524 let mut selections = self.selections.all::<Point>(cx);
5525 let mut prev_edited_row = 0;
5526 let mut row_delta = 0;
5527 let mut edits = Vec::new();
5528 let buffer = self.buffer.read(cx);
5529 let snapshot = buffer.snapshot(cx);
5530 for selection in &mut selections {
5531 if selection.start.row != prev_edited_row {
5532 row_delta = 0;
5533 }
5534 prev_edited_row = selection.end.row;
5535
5536 row_delta =
5537 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5538 }
5539
5540 self.transact(cx, |this, cx| {
5541 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5542 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5543 });
5544 }
5545
5546 fn indent_selection(
5547 buffer: &MultiBuffer,
5548 snapshot: &MultiBufferSnapshot,
5549 selection: &mut Selection<Point>,
5550 edits: &mut Vec<(Range<Point>, String)>,
5551 delta_for_start_row: u32,
5552 cx: &AppContext,
5553 ) -> u32 {
5554 let settings = buffer.settings_at(selection.start, cx);
5555 let tab_size = settings.tab_size.get();
5556 let indent_kind = if settings.hard_tabs {
5557 IndentKind::Tab
5558 } else {
5559 IndentKind::Space
5560 };
5561 let mut start_row = selection.start.row;
5562 let mut end_row = selection.end.row + 1;
5563
5564 // If a selection ends at the beginning of a line, don't indent
5565 // that last line.
5566 if selection.end.column == 0 && selection.end.row > selection.start.row {
5567 end_row -= 1;
5568 }
5569
5570 // Avoid re-indenting a row that has already been indented by a
5571 // previous selection, but still update this selection's column
5572 // to reflect that indentation.
5573 if delta_for_start_row > 0 {
5574 start_row += 1;
5575 selection.start.column += delta_for_start_row;
5576 if selection.end.row == selection.start.row {
5577 selection.end.column += delta_for_start_row;
5578 }
5579 }
5580
5581 let mut delta_for_end_row = 0;
5582 let has_multiple_rows = start_row + 1 != end_row;
5583 for row in start_row..end_row {
5584 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
5585 let indent_delta = match (current_indent.kind, indent_kind) {
5586 (IndentKind::Space, IndentKind::Space) => {
5587 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
5588 IndentSize::spaces(columns_to_next_tab_stop)
5589 }
5590 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
5591 (_, IndentKind::Tab) => IndentSize::tab(),
5592 };
5593
5594 let start = if has_multiple_rows || current_indent.len < selection.start.column {
5595 0
5596 } else {
5597 selection.start.column
5598 };
5599 let row_start = Point::new(row, start);
5600 edits.push((
5601 row_start..row_start,
5602 indent_delta.chars().collect::<String>(),
5603 ));
5604
5605 // Update this selection's endpoints to reflect the indentation.
5606 if row == selection.start.row {
5607 selection.start.column += indent_delta.len;
5608 }
5609 if row == selection.end.row {
5610 selection.end.column += indent_delta.len;
5611 delta_for_end_row = indent_delta.len;
5612 }
5613 }
5614
5615 if selection.start.row == selection.end.row {
5616 delta_for_start_row + delta_for_end_row
5617 } else {
5618 delta_for_end_row
5619 }
5620 }
5621
5622 pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
5623 if self.read_only(cx) {
5624 return;
5625 }
5626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5627 let selections = self.selections.all::<Point>(cx);
5628 let mut deletion_ranges = Vec::new();
5629 let mut last_outdent = None;
5630 {
5631 let buffer = self.buffer.read(cx);
5632 let snapshot = buffer.snapshot(cx);
5633 for selection in &selections {
5634 let settings = buffer.settings_at(selection.start, cx);
5635 let tab_size = settings.tab_size.get();
5636 let mut rows = selection.spanned_rows(false, &display_map);
5637
5638 // Avoid re-outdenting a row that has already been outdented by a
5639 // previous selection.
5640 if let Some(last_row) = last_outdent {
5641 if last_row == rows.start {
5642 rows.start = rows.start.next_row();
5643 }
5644 }
5645 let has_multiple_rows = rows.len() > 1;
5646 for row in rows.iter_rows() {
5647 let indent_size = snapshot.indent_size_for_line(row);
5648 if indent_size.len > 0 {
5649 let deletion_len = match indent_size.kind {
5650 IndentKind::Space => {
5651 let columns_to_prev_tab_stop = indent_size.len % tab_size;
5652 if columns_to_prev_tab_stop == 0 {
5653 tab_size
5654 } else {
5655 columns_to_prev_tab_stop
5656 }
5657 }
5658 IndentKind::Tab => 1,
5659 };
5660 let start = if has_multiple_rows
5661 || deletion_len > selection.start.column
5662 || indent_size.len < selection.start.column
5663 {
5664 0
5665 } else {
5666 selection.start.column - deletion_len
5667 };
5668 deletion_ranges.push(
5669 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
5670 );
5671 last_outdent = Some(row);
5672 }
5673 }
5674 }
5675 }
5676
5677 self.transact(cx, |this, cx| {
5678 this.buffer.update(cx, |buffer, cx| {
5679 let empty_str: Arc<str> = "".into();
5680 buffer.edit(
5681 deletion_ranges
5682 .into_iter()
5683 .map(|range| (range, empty_str.clone())),
5684 None,
5685 cx,
5686 );
5687 });
5688 let selections = this.selections.all::<usize>(cx);
5689 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5690 });
5691 }
5692
5693 pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
5694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5695 let selections = self.selections.all::<Point>(cx);
5696
5697 let mut new_cursors = Vec::new();
5698 let mut edit_ranges = Vec::new();
5699 let mut selections = selections.iter().peekable();
5700 while let Some(selection) = selections.next() {
5701 let mut rows = selection.spanned_rows(false, &display_map);
5702 let goal_display_column = selection.head().to_display_point(&display_map).column();
5703
5704 // Accumulate contiguous regions of rows that we want to delete.
5705 while let Some(next_selection) = selections.peek() {
5706 let next_rows = next_selection.spanned_rows(false, &display_map);
5707 if next_rows.start <= rows.end {
5708 rows.end = next_rows.end;
5709 selections.next().unwrap();
5710 } else {
5711 break;
5712 }
5713 }
5714
5715 let buffer = &display_map.buffer_snapshot;
5716 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
5717 let edit_end;
5718 let cursor_buffer_row;
5719 if buffer.max_point().row >= rows.end.0 {
5720 // If there's a line after the range, delete the \n from the end of the row range
5721 // and position the cursor on the next line.
5722 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
5723 cursor_buffer_row = rows.end;
5724 } else {
5725 // If there isn't a line after the range, delete the \n from the line before the
5726 // start of the row range and position the cursor there.
5727 edit_start = edit_start.saturating_sub(1);
5728 edit_end = buffer.len();
5729 cursor_buffer_row = rows.start.previous_row();
5730 }
5731
5732 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
5733 *cursor.column_mut() =
5734 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
5735
5736 new_cursors.push((
5737 selection.id,
5738 buffer.anchor_after(cursor.to_point(&display_map)),
5739 ));
5740 edit_ranges.push(edit_start..edit_end);
5741 }
5742
5743 self.transact(cx, |this, cx| {
5744 let buffer = this.buffer.update(cx, |buffer, cx| {
5745 let empty_str: Arc<str> = "".into();
5746 buffer.edit(
5747 edit_ranges
5748 .into_iter()
5749 .map(|range| (range, empty_str.clone())),
5750 None,
5751 cx,
5752 );
5753 buffer.snapshot(cx)
5754 });
5755 let new_selections = new_cursors
5756 .into_iter()
5757 .map(|(id, cursor)| {
5758 let cursor = cursor.to_point(&buffer);
5759 Selection {
5760 id,
5761 start: cursor,
5762 end: cursor,
5763 reversed: false,
5764 goal: SelectionGoal::None,
5765 }
5766 })
5767 .collect();
5768
5769 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5770 s.select(new_selections);
5771 });
5772 });
5773 }
5774
5775 pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
5776 if self.read_only(cx) {
5777 return;
5778 }
5779 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
5780 for selection in self.selections.all::<Point>(cx) {
5781 let start = MultiBufferRow(selection.start.row);
5782 let end = if selection.start.row == selection.end.row {
5783 MultiBufferRow(selection.start.row + 1)
5784 } else {
5785 MultiBufferRow(selection.end.row)
5786 };
5787
5788 if let Some(last_row_range) = row_ranges.last_mut() {
5789 if start <= last_row_range.end {
5790 last_row_range.end = end;
5791 continue;
5792 }
5793 }
5794 row_ranges.push(start..end);
5795 }
5796
5797 let snapshot = self.buffer.read(cx).snapshot(cx);
5798 let mut cursor_positions = Vec::new();
5799 for row_range in &row_ranges {
5800 let anchor = snapshot.anchor_before(Point::new(
5801 row_range.end.previous_row().0,
5802 snapshot.line_len(row_range.end.previous_row()),
5803 ));
5804 cursor_positions.push(anchor..anchor);
5805 }
5806
5807 self.transact(cx, |this, cx| {
5808 for row_range in row_ranges.into_iter().rev() {
5809 for row in row_range.iter_rows().rev() {
5810 let end_of_line = Point::new(row.0, snapshot.line_len(row));
5811 let next_line_row = row.next_row();
5812 let indent = snapshot.indent_size_for_line(next_line_row);
5813 let start_of_next_line = Point::new(next_line_row.0, indent.len);
5814
5815 let replace = if snapshot.line_len(next_line_row) > indent.len {
5816 " "
5817 } else {
5818 ""
5819 };
5820
5821 this.buffer.update(cx, |buffer, cx| {
5822 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
5823 });
5824 }
5825 }
5826
5827 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5828 s.select_anchor_ranges(cursor_positions)
5829 });
5830 });
5831 }
5832
5833 pub fn sort_lines_case_sensitive(
5834 &mut self,
5835 _: &SortLinesCaseSensitive,
5836 cx: &mut ViewContext<Self>,
5837 ) {
5838 self.manipulate_lines(cx, |lines| lines.sort())
5839 }
5840
5841 pub fn sort_lines_case_insensitive(
5842 &mut self,
5843 _: &SortLinesCaseInsensitive,
5844 cx: &mut ViewContext<Self>,
5845 ) {
5846 self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
5847 }
5848
5849 pub fn unique_lines_case_insensitive(
5850 &mut self,
5851 _: &UniqueLinesCaseInsensitive,
5852 cx: &mut ViewContext<Self>,
5853 ) {
5854 self.manipulate_lines(cx, |lines| {
5855 let mut seen = HashSet::default();
5856 lines.retain(|line| seen.insert(line.to_lowercase()));
5857 })
5858 }
5859
5860 pub fn unique_lines_case_sensitive(
5861 &mut self,
5862 _: &UniqueLinesCaseSensitive,
5863 cx: &mut ViewContext<Self>,
5864 ) {
5865 self.manipulate_lines(cx, |lines| {
5866 let mut seen = HashSet::default();
5867 lines.retain(|line| seen.insert(*line));
5868 })
5869 }
5870
5871 pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
5872 let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
5873 if !revert_changes.is_empty() {
5874 self.transact(cx, |editor, cx| {
5875 editor.buffer().update(cx, |multi_buffer, cx| {
5876 for (buffer_id, changes) in revert_changes {
5877 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
5878 buffer.update(cx, |buffer, cx| {
5879 buffer.edit(
5880 changes.into_iter().map(|(range, text)| {
5881 (range, text.to_string().map(Arc::<str>::from))
5882 }),
5883 None,
5884 cx,
5885 );
5886 });
5887 }
5888 }
5889 });
5890 editor.change_selections(None, cx, |selections| selections.refresh());
5891 });
5892 }
5893 }
5894
5895 pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
5896 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
5897 let project_path = buffer.read(cx).project_path(cx)?;
5898 let project = self.project.as_ref()?.read(cx);
5899 let entry = project.entry_for_path(&project_path, cx)?;
5900 let abs_path = project.absolute_path(&project_path, cx)?;
5901 let parent = if entry.is_symlink {
5902 abs_path.canonicalize().ok()?
5903 } else {
5904 abs_path
5905 }
5906 .parent()?
5907 .to_path_buf();
5908 Some(parent)
5909 }) {
5910 cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone());
5911 }
5912 }
5913
5914 fn gather_revert_changes(
5915 &mut self,
5916 selections: &[Selection<Anchor>],
5917 cx: &mut ViewContext<'_, Editor>,
5918 ) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
5919 let mut revert_changes = HashMap::default();
5920 self.buffer.update(cx, |multi_buffer, cx| {
5921 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5922 for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
5923 Self::prepare_revert_change(&mut revert_changes, &multi_buffer, &hunk, cx);
5924 }
5925 });
5926 revert_changes
5927 }
5928
5929 fn prepare_revert_change(
5930 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
5931 multi_buffer: &MultiBuffer,
5932 hunk: &DiffHunk<MultiBufferRow>,
5933 cx: &mut AppContext,
5934 ) -> Option<()> {
5935 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
5936 let buffer = buffer.read(cx);
5937 let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
5938 let buffer_snapshot = buffer.snapshot();
5939 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
5940 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
5941 probe
5942 .0
5943 .start
5944 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
5945 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
5946 }) {
5947 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
5948 Some(())
5949 } else {
5950 None
5951 }
5952 }
5953
5954 pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
5955 self.manipulate_lines(cx, |lines| lines.reverse())
5956 }
5957
5958 pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
5959 self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
5960 }
5961
5962 fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
5963 where
5964 Fn: FnMut(&mut Vec<&str>),
5965 {
5966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5967 let buffer = self.buffer.read(cx).snapshot(cx);
5968
5969 let mut edits = Vec::new();
5970
5971 let selections = self.selections.all::<Point>(cx);
5972 let mut selections = selections.iter().peekable();
5973 let mut contiguous_row_selections = Vec::new();
5974 let mut new_selections = Vec::new();
5975 let mut added_lines = 0;
5976 let mut removed_lines = 0;
5977
5978 while let Some(selection) = selections.next() {
5979 let (start_row, end_row) = consume_contiguous_rows(
5980 &mut contiguous_row_selections,
5981 selection,
5982 &display_map,
5983 &mut selections,
5984 );
5985
5986 let start_point = Point::new(start_row.0, 0);
5987 let end_point = Point::new(
5988 end_row.previous_row().0,
5989 buffer.line_len(end_row.previous_row()),
5990 );
5991 let text = buffer
5992 .text_for_range(start_point..end_point)
5993 .collect::<String>();
5994
5995 let mut lines = text.split('\n').collect_vec();
5996
5997 let lines_before = lines.len();
5998 callback(&mut lines);
5999 let lines_after = lines.len();
6000
6001 edits.push((start_point..end_point, lines.join("\n")));
6002
6003 // Selections must change based on added and removed line count
6004 let start_row =
6005 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
6006 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
6007 new_selections.push(Selection {
6008 id: selection.id,
6009 start: start_row,
6010 end: end_row,
6011 goal: SelectionGoal::None,
6012 reversed: selection.reversed,
6013 });
6014
6015 if lines_after > lines_before {
6016 added_lines += lines_after - lines_before;
6017 } else if lines_before > lines_after {
6018 removed_lines += lines_before - lines_after;
6019 }
6020 }
6021
6022 self.transact(cx, |this, cx| {
6023 let buffer = this.buffer.update(cx, |buffer, cx| {
6024 buffer.edit(edits, None, cx);
6025 buffer.snapshot(cx)
6026 });
6027
6028 // Recalculate offsets on newly edited buffer
6029 let new_selections = new_selections
6030 .iter()
6031 .map(|s| {
6032 let start_point = Point::new(s.start.0, 0);
6033 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
6034 Selection {
6035 id: s.id,
6036 start: buffer.point_to_offset(start_point),
6037 end: buffer.point_to_offset(end_point),
6038 goal: s.goal,
6039 reversed: s.reversed,
6040 }
6041 })
6042 .collect();
6043
6044 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6045 s.select(new_selections);
6046 });
6047
6048 this.request_autoscroll(Autoscroll::fit(), cx);
6049 });
6050 }
6051
6052 pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
6053 self.manipulate_text(cx, |text| text.to_uppercase())
6054 }
6055
6056 pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
6057 self.manipulate_text(cx, |text| text.to_lowercase())
6058 }
6059
6060 pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
6061 self.manipulate_text(cx, |text| {
6062 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
6063 // https://github.com/rutrum/convert-case/issues/16
6064 text.split('\n')
6065 .map(|line| line.to_case(Case::Title))
6066 .join("\n")
6067 })
6068 }
6069
6070 pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
6071 self.manipulate_text(cx, |text| text.to_case(Case::Snake))
6072 }
6073
6074 pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
6075 self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
6076 }
6077
6078 pub fn convert_to_upper_camel_case(
6079 &mut self,
6080 _: &ConvertToUpperCamelCase,
6081 cx: &mut ViewContext<Self>,
6082 ) {
6083 self.manipulate_text(cx, |text| {
6084 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
6085 // https://github.com/rutrum/convert-case/issues/16
6086 text.split('\n')
6087 .map(|line| line.to_case(Case::UpperCamel))
6088 .join("\n")
6089 })
6090 }
6091
6092 pub fn convert_to_lower_camel_case(
6093 &mut self,
6094 _: &ConvertToLowerCamelCase,
6095 cx: &mut ViewContext<Self>,
6096 ) {
6097 self.manipulate_text(cx, |text| text.to_case(Case::Camel))
6098 }
6099
6100 pub fn convert_to_opposite_case(
6101 &mut self,
6102 _: &ConvertToOppositeCase,
6103 cx: &mut ViewContext<Self>,
6104 ) {
6105 self.manipulate_text(cx, |text| {
6106 text.chars()
6107 .fold(String::with_capacity(text.len()), |mut t, c| {
6108 if c.is_uppercase() {
6109 t.extend(c.to_lowercase());
6110 } else {
6111 t.extend(c.to_uppercase());
6112 }
6113 t
6114 })
6115 })
6116 }
6117
6118 fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
6119 where
6120 Fn: FnMut(&str) -> String,
6121 {
6122 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6123 let buffer = self.buffer.read(cx).snapshot(cx);
6124
6125 let mut new_selections = Vec::new();
6126 let mut edits = Vec::new();
6127 let mut selection_adjustment = 0i32;
6128
6129 for selection in self.selections.all::<usize>(cx) {
6130 let selection_is_empty = selection.is_empty();
6131
6132 let (start, end) = if selection_is_empty {
6133 let word_range = movement::surrounding_word(
6134 &display_map,
6135 selection.start.to_display_point(&display_map),
6136 );
6137 let start = word_range.start.to_offset(&display_map, Bias::Left);
6138 let end = word_range.end.to_offset(&display_map, Bias::Left);
6139 (start, end)
6140 } else {
6141 (selection.start, selection.end)
6142 };
6143
6144 let text = buffer.text_for_range(start..end).collect::<String>();
6145 let old_length = text.len() as i32;
6146 let text = callback(&text);
6147
6148 new_selections.push(Selection {
6149 start: (start as i32 - selection_adjustment) as usize,
6150 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
6151 goal: SelectionGoal::None,
6152 ..selection
6153 });
6154
6155 selection_adjustment += old_length - text.len() as i32;
6156
6157 edits.push((start..end, text));
6158 }
6159
6160 self.transact(cx, |this, cx| {
6161 this.buffer.update(cx, |buffer, cx| {
6162 buffer.edit(edits, None, cx);
6163 });
6164
6165 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6166 s.select(new_selections);
6167 });
6168
6169 this.request_autoscroll(Autoscroll::fit(), cx);
6170 });
6171 }
6172
6173 pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
6174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6175 let buffer = &display_map.buffer_snapshot;
6176 let selections = self.selections.all::<Point>(cx);
6177
6178 let mut edits = Vec::new();
6179 let mut selections_iter = selections.iter().peekable();
6180 while let Some(selection) = selections_iter.next() {
6181 // Avoid duplicating the same lines twice.
6182 let mut rows = selection.spanned_rows(false, &display_map);
6183
6184 while let Some(next_selection) = selections_iter.peek() {
6185 let next_rows = next_selection.spanned_rows(false, &display_map);
6186 if next_rows.start < rows.end {
6187 rows.end = next_rows.end;
6188 selections_iter.next().unwrap();
6189 } else {
6190 break;
6191 }
6192 }
6193
6194 // Copy the text from the selected row region and splice it either at the start
6195 // or end of the region.
6196 let start = Point::new(rows.start.0, 0);
6197 let end = Point::new(
6198 rows.end.previous_row().0,
6199 buffer.line_len(rows.end.previous_row()),
6200 );
6201 let text = buffer
6202 .text_for_range(start..end)
6203 .chain(Some("\n"))
6204 .collect::<String>();
6205 let insert_location = if upwards {
6206 Point::new(rows.end.0, 0)
6207 } else {
6208 start
6209 };
6210 edits.push((insert_location..insert_location, text));
6211 }
6212
6213 self.transact(cx, |this, cx| {
6214 this.buffer.update(cx, |buffer, cx| {
6215 buffer.edit(edits, None, cx);
6216 });
6217
6218 this.request_autoscroll(Autoscroll::fit(), cx);
6219 });
6220 }
6221
6222 pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
6223 self.duplicate_line(true, cx);
6224 }
6225
6226 pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
6227 self.duplicate_line(false, cx);
6228 }
6229
6230 pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
6231 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6232 let buffer = self.buffer.read(cx).snapshot(cx);
6233
6234 let mut edits = Vec::new();
6235 let mut unfold_ranges = Vec::new();
6236 let mut refold_ranges = Vec::new();
6237
6238 let selections = self.selections.all::<Point>(cx);
6239 let mut selections = selections.iter().peekable();
6240 let mut contiguous_row_selections = Vec::new();
6241 let mut new_selections = Vec::new();
6242
6243 while let Some(selection) = selections.next() {
6244 // Find all the selections that span a contiguous row range
6245 let (start_row, end_row) = consume_contiguous_rows(
6246 &mut contiguous_row_selections,
6247 selection,
6248 &display_map,
6249 &mut selections,
6250 );
6251
6252 // Move the text spanned by the row range to be before the line preceding the row range
6253 if start_row.0 > 0 {
6254 let range_to_move = Point::new(
6255 start_row.previous_row().0,
6256 buffer.line_len(start_row.previous_row()),
6257 )
6258 ..Point::new(
6259 end_row.previous_row().0,
6260 buffer.line_len(end_row.previous_row()),
6261 );
6262 let insertion_point = display_map
6263 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
6264 .0;
6265
6266 // Don't move lines across excerpts
6267 if buffer
6268 .excerpt_boundaries_in_range((
6269 Bound::Excluded(insertion_point),
6270 Bound::Included(range_to_move.end),
6271 ))
6272 .next()
6273 .is_none()
6274 {
6275 let text = buffer
6276 .text_for_range(range_to_move.clone())
6277 .flat_map(|s| s.chars())
6278 .skip(1)
6279 .chain(['\n'])
6280 .collect::<String>();
6281
6282 edits.push((
6283 buffer.anchor_after(range_to_move.start)
6284 ..buffer.anchor_before(range_to_move.end),
6285 String::new(),
6286 ));
6287 let insertion_anchor = buffer.anchor_after(insertion_point);
6288 edits.push((insertion_anchor..insertion_anchor, text));
6289
6290 let row_delta = range_to_move.start.row - insertion_point.row + 1;
6291
6292 // Move selections up
6293 new_selections.extend(contiguous_row_selections.drain(..).map(
6294 |mut selection| {
6295 selection.start.row -= row_delta;
6296 selection.end.row -= row_delta;
6297 selection
6298 },
6299 ));
6300
6301 // Move folds up
6302 unfold_ranges.push(range_to_move.clone());
6303 for fold in display_map.folds_in_range(
6304 buffer.anchor_before(range_to_move.start)
6305 ..buffer.anchor_after(range_to_move.end),
6306 ) {
6307 let mut start = fold.range.start.to_point(&buffer);
6308 let mut end = fold.range.end.to_point(&buffer);
6309 start.row -= row_delta;
6310 end.row -= row_delta;
6311 refold_ranges.push((start..end, fold.placeholder.clone()));
6312 }
6313 }
6314 }
6315
6316 // If we didn't move line(s), preserve the existing selections
6317 new_selections.append(&mut contiguous_row_selections);
6318 }
6319
6320 self.transact(cx, |this, cx| {
6321 this.unfold_ranges(unfold_ranges, true, true, cx);
6322 this.buffer.update(cx, |buffer, cx| {
6323 for (range, text) in edits {
6324 buffer.edit([(range, text)], None, cx);
6325 }
6326 });
6327 this.fold_ranges(refold_ranges, true, cx);
6328 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6329 s.select(new_selections);
6330 })
6331 });
6332 }
6333
6334 pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
6335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6336 let buffer = self.buffer.read(cx).snapshot(cx);
6337
6338 let mut edits = Vec::new();
6339 let mut unfold_ranges = Vec::new();
6340 let mut refold_ranges = Vec::new();
6341
6342 let selections = self.selections.all::<Point>(cx);
6343 let mut selections = selections.iter().peekable();
6344 let mut contiguous_row_selections = Vec::new();
6345 let mut new_selections = Vec::new();
6346
6347 while let Some(selection) = selections.next() {
6348 // Find all the selections that span a contiguous row range
6349 let (start_row, end_row) = consume_contiguous_rows(
6350 &mut contiguous_row_selections,
6351 selection,
6352 &display_map,
6353 &mut selections,
6354 );
6355
6356 // Move the text spanned by the row range to be after the last line of the row range
6357 if end_row.0 <= buffer.max_point().row {
6358 let range_to_move =
6359 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
6360 let insertion_point = display_map
6361 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
6362 .0;
6363
6364 // Don't move lines across excerpt boundaries
6365 if buffer
6366 .excerpt_boundaries_in_range((
6367 Bound::Excluded(range_to_move.start),
6368 Bound::Included(insertion_point),
6369 ))
6370 .next()
6371 .is_none()
6372 {
6373 let mut text = String::from("\n");
6374 text.extend(buffer.text_for_range(range_to_move.clone()));
6375 text.pop(); // Drop trailing newline
6376 edits.push((
6377 buffer.anchor_after(range_to_move.start)
6378 ..buffer.anchor_before(range_to_move.end),
6379 String::new(),
6380 ));
6381 let insertion_anchor = buffer.anchor_after(insertion_point);
6382 edits.push((insertion_anchor..insertion_anchor, text));
6383
6384 let row_delta = insertion_point.row - range_to_move.end.row + 1;
6385
6386 // Move selections down
6387 new_selections.extend(contiguous_row_selections.drain(..).map(
6388 |mut selection| {
6389 selection.start.row += row_delta;
6390 selection.end.row += row_delta;
6391 selection
6392 },
6393 ));
6394
6395 // Move folds down
6396 unfold_ranges.push(range_to_move.clone());
6397 for fold in display_map.folds_in_range(
6398 buffer.anchor_before(range_to_move.start)
6399 ..buffer.anchor_after(range_to_move.end),
6400 ) {
6401 let mut start = fold.range.start.to_point(&buffer);
6402 let mut end = fold.range.end.to_point(&buffer);
6403 start.row += row_delta;
6404 end.row += row_delta;
6405 refold_ranges.push((start..end, fold.placeholder.clone()));
6406 }
6407 }
6408 }
6409
6410 // If we didn't move line(s), preserve the existing selections
6411 new_selections.append(&mut contiguous_row_selections);
6412 }
6413
6414 self.transact(cx, |this, cx| {
6415 this.unfold_ranges(unfold_ranges, true, true, cx);
6416 this.buffer.update(cx, |buffer, cx| {
6417 for (range, text) in edits {
6418 buffer.edit([(range, text)], None, cx);
6419 }
6420 });
6421 this.fold_ranges(refold_ranges, true, cx);
6422 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
6423 });
6424 }
6425
6426 pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext<Self>) {
6427 let text_layout_details = &self.text_layout_details(cx);
6428 self.transact(cx, |this, cx| {
6429 let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6430 let mut edits: Vec<(Range<usize>, String)> = Default::default();
6431 let line_mode = s.line_mode;
6432 s.move_with(|display_map, selection| {
6433 if !selection.is_empty() || line_mode {
6434 return;
6435 }
6436
6437 let mut head = selection.head();
6438 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
6439 if head.column() == display_map.line_len(head.row()) {
6440 transpose_offset = display_map
6441 .buffer_snapshot
6442 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
6443 }
6444
6445 if transpose_offset == 0 {
6446 return;
6447 }
6448
6449 *head.column_mut() += 1;
6450 head = display_map.clip_point(head, Bias::Right);
6451 let goal = SelectionGoal::HorizontalPosition(
6452 display_map
6453 .x_for_display_point(head, &text_layout_details)
6454 .into(),
6455 );
6456 selection.collapse_to(head, goal);
6457
6458 let transpose_start = display_map
6459 .buffer_snapshot
6460 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
6461 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
6462 let transpose_end = display_map
6463 .buffer_snapshot
6464 .clip_offset(transpose_offset + 1, Bias::Right);
6465 if let Some(ch) =
6466 display_map.buffer_snapshot.chars_at(transpose_start).next()
6467 {
6468 edits.push((transpose_start..transpose_offset, String::new()));
6469 edits.push((transpose_end..transpose_end, ch.to_string()));
6470 }
6471 }
6472 });
6473 edits
6474 });
6475 this.buffer
6476 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
6477 let selections = this.selections.all::<usize>(cx);
6478 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6479 s.select(selections);
6480 });
6481 });
6482 }
6483
6484 pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
6485 let mut text = String::new();
6486 let buffer = self.buffer.read(cx).snapshot(cx);
6487 let mut selections = self.selections.all::<Point>(cx);
6488 let mut clipboard_selections = Vec::with_capacity(selections.len());
6489 {
6490 let max_point = buffer.max_point();
6491 let mut is_first = true;
6492 for selection in &mut selections {
6493 let is_entire_line = selection.is_empty() || self.selections.line_mode;
6494 if is_entire_line {
6495 selection.start = Point::new(selection.start.row, 0);
6496 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
6497 selection.goal = SelectionGoal::None;
6498 }
6499 if is_first {
6500 is_first = false;
6501 } else {
6502 text += "\n";
6503 }
6504 let mut len = 0;
6505 for chunk in buffer.text_for_range(selection.start..selection.end) {
6506 text.push_str(chunk);
6507 len += chunk.len();
6508 }
6509 clipboard_selections.push(ClipboardSelection {
6510 len,
6511 is_entire_line,
6512 first_line_indent: buffer
6513 .indent_size_for_line(MultiBufferRow(selection.start.row))
6514 .len,
6515 });
6516 }
6517 }
6518
6519 self.transact(cx, |this, cx| {
6520 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6521 s.select(selections);
6522 });
6523 this.insert("", cx);
6524 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
6525 });
6526 }
6527
6528 pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
6529 let selections = self.selections.all::<Point>(cx);
6530 let buffer = self.buffer.read(cx).read(cx);
6531 let mut text = String::new();
6532
6533 let mut clipboard_selections = Vec::with_capacity(selections.len());
6534 {
6535 let max_point = buffer.max_point();
6536 let mut is_first = true;
6537 for selection in selections.iter() {
6538 let mut start = selection.start;
6539 let mut end = selection.end;
6540 let is_entire_line = selection.is_empty() || self.selections.line_mode;
6541 if is_entire_line {
6542 start = Point::new(start.row, 0);
6543 end = cmp::min(max_point, Point::new(end.row + 1, 0));
6544 }
6545 if is_first {
6546 is_first = false;
6547 } else {
6548 text += "\n";
6549 }
6550 let mut len = 0;
6551 for chunk in buffer.text_for_range(start..end) {
6552 text.push_str(chunk);
6553 len += chunk.len();
6554 }
6555 clipboard_selections.push(ClipboardSelection {
6556 len,
6557 is_entire_line,
6558 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
6559 });
6560 }
6561 }
6562
6563 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
6564 }
6565
6566 pub fn do_paste(
6567 &mut self,
6568 text: &String,
6569 clipboard_selections: Option<Vec<ClipboardSelection>>,
6570 handle_entire_lines: bool,
6571 cx: &mut ViewContext<Self>,
6572 ) {
6573 if self.read_only(cx) {
6574 return;
6575 }
6576
6577 let clipboard_text = Cow::Borrowed(text);
6578
6579 self.transact(cx, |this, cx| {
6580 if let Some(mut clipboard_selections) = clipboard_selections {
6581 let old_selections = this.selections.all::<usize>(cx);
6582 let all_selections_were_entire_line =
6583 clipboard_selections.iter().all(|s| s.is_entire_line);
6584 let first_selection_indent_column =
6585 clipboard_selections.first().map(|s| s.first_line_indent);
6586 if clipboard_selections.len() != old_selections.len() {
6587 clipboard_selections.drain(..);
6588 }
6589
6590 this.buffer.update(cx, |buffer, cx| {
6591 let snapshot = buffer.read(cx);
6592 let mut start_offset = 0;
6593 let mut edits = Vec::new();
6594 let mut original_indent_columns = Vec::new();
6595 for (ix, selection) in old_selections.iter().enumerate() {
6596 let to_insert;
6597 let entire_line;
6598 let original_indent_column;
6599 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
6600 let end_offset = start_offset + clipboard_selection.len;
6601 to_insert = &clipboard_text[start_offset..end_offset];
6602 entire_line = clipboard_selection.is_entire_line;
6603 start_offset = end_offset + 1;
6604 original_indent_column = Some(clipboard_selection.first_line_indent);
6605 } else {
6606 to_insert = clipboard_text.as_str();
6607 entire_line = all_selections_were_entire_line;
6608 original_indent_column = first_selection_indent_column
6609 }
6610
6611 // If the corresponding selection was empty when this slice of the
6612 // clipboard text was written, then the entire line containing the
6613 // selection was copied. If this selection is also currently empty,
6614 // then paste the line before the current line of the buffer.
6615 let range = if selection.is_empty() && handle_entire_lines && entire_line {
6616 let column = selection.start.to_point(&snapshot).column as usize;
6617 let line_start = selection.start - column;
6618 line_start..line_start
6619 } else {
6620 selection.range()
6621 };
6622
6623 edits.push((range, to_insert));
6624 original_indent_columns.extend(original_indent_column);
6625 }
6626 drop(snapshot);
6627
6628 buffer.edit(
6629 edits,
6630 Some(AutoindentMode::Block {
6631 original_indent_columns,
6632 }),
6633 cx,
6634 );
6635 });
6636
6637 let selections = this.selections.all::<usize>(cx);
6638 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
6639 } else {
6640 this.insert(&clipboard_text, cx);
6641 }
6642 });
6643 }
6644
6645 pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
6646 if let Some(item) = cx.read_from_clipboard() {
6647 self.do_paste(
6648 item.text(),
6649 item.metadata::<Vec<ClipboardSelection>>(),
6650 true,
6651 cx,
6652 )
6653 };
6654 }
6655
6656 pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
6657 if self.read_only(cx) {
6658 return;
6659 }
6660
6661 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
6662 if let Some((selections, _)) =
6663 self.selection_history.transaction(transaction_id).cloned()
6664 {
6665 self.change_selections(None, cx, |s| {
6666 s.select_anchors(selections.to_vec());
6667 });
6668 }
6669 self.request_autoscroll(Autoscroll::fit(), cx);
6670 self.unmark_text(cx);
6671 self.refresh_inline_completion(true, cx);
6672 cx.emit(EditorEvent::Edited { transaction_id });
6673 cx.emit(EditorEvent::TransactionUndone { transaction_id });
6674 }
6675 }
6676
6677 pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
6678 if self.read_only(cx) {
6679 return;
6680 }
6681
6682 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
6683 if let Some((_, Some(selections))) =
6684 self.selection_history.transaction(transaction_id).cloned()
6685 {
6686 self.change_selections(None, cx, |s| {
6687 s.select_anchors(selections.to_vec());
6688 });
6689 }
6690 self.request_autoscroll(Autoscroll::fit(), cx);
6691 self.unmark_text(cx);
6692 self.refresh_inline_completion(true, cx);
6693 cx.emit(EditorEvent::Edited { transaction_id });
6694 }
6695 }
6696
6697 pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext<Self>) {
6698 self.buffer
6699 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
6700 }
6701
6702 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut ViewContext<Self>) {
6703 self.buffer
6704 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
6705 }
6706
6707 pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
6708 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6709 let line_mode = s.line_mode;
6710 s.move_with(|map, selection| {
6711 let cursor = if selection.is_empty() && !line_mode {
6712 movement::left(map, selection.start)
6713 } else {
6714 selection.start
6715 };
6716 selection.collapse_to(cursor, SelectionGoal::None);
6717 });
6718 })
6719 }
6720
6721 pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
6722 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6723 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
6724 })
6725 }
6726
6727 pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
6728 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6729 let line_mode = s.line_mode;
6730 s.move_with(|map, selection| {
6731 let cursor = if selection.is_empty() && !line_mode {
6732 movement::right(map, selection.end)
6733 } else {
6734 selection.end
6735 };
6736 selection.collapse_to(cursor, SelectionGoal::None)
6737 });
6738 })
6739 }
6740
6741 pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
6742 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6743 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
6744 })
6745 }
6746
6747 pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
6748 if self.take_rename(true, cx).is_some() {
6749 return;
6750 }
6751
6752 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6753 cx.propagate();
6754 return;
6755 }
6756
6757 let text_layout_details = &self.text_layout_details(cx);
6758 let selection_count = self.selections.count();
6759 let first_selection = self.selections.first_anchor();
6760
6761 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6762 let line_mode = s.line_mode;
6763 s.move_with(|map, selection| {
6764 if !selection.is_empty() && !line_mode {
6765 selection.goal = SelectionGoal::None;
6766 }
6767 let (cursor, goal) = movement::up(
6768 map,
6769 selection.start,
6770 selection.goal,
6771 false,
6772 &text_layout_details,
6773 );
6774 selection.collapse_to(cursor, goal);
6775 });
6776 });
6777
6778 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
6779 {
6780 cx.propagate();
6781 }
6782 }
6783
6784 pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) {
6785 if self.take_rename(true, cx).is_some() {
6786 return;
6787 }
6788
6789 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6790 cx.propagate();
6791 return;
6792 }
6793
6794 let text_layout_details = &self.text_layout_details(cx);
6795
6796 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6797 let line_mode = s.line_mode;
6798 s.move_with(|map, selection| {
6799 if !selection.is_empty() && !line_mode {
6800 selection.goal = SelectionGoal::None;
6801 }
6802 let (cursor, goal) = movement::up_by_rows(
6803 map,
6804 selection.start,
6805 action.lines,
6806 selection.goal,
6807 false,
6808 &text_layout_details,
6809 );
6810 selection.collapse_to(cursor, goal);
6811 });
6812 })
6813 }
6814
6815 pub fn move_down_by_lines(&mut self, action: &MoveDownByLines, cx: &mut ViewContext<Self>) {
6816 if self.take_rename(true, cx).is_some() {
6817 return;
6818 }
6819
6820 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6821 cx.propagate();
6822 return;
6823 }
6824
6825 let text_layout_details = &self.text_layout_details(cx);
6826
6827 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6828 let line_mode = s.line_mode;
6829 s.move_with(|map, selection| {
6830 if !selection.is_empty() && !line_mode {
6831 selection.goal = SelectionGoal::None;
6832 }
6833 let (cursor, goal) = movement::down_by_rows(
6834 map,
6835 selection.start,
6836 action.lines,
6837 selection.goal,
6838 false,
6839 &text_layout_details,
6840 );
6841 selection.collapse_to(cursor, goal);
6842 });
6843 })
6844 }
6845
6846 pub fn select_down_by_lines(&mut self, action: &SelectDownByLines, cx: &mut ViewContext<Self>) {
6847 let text_layout_details = &self.text_layout_details(cx);
6848 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6849 s.move_heads_with(|map, head, goal| {
6850 movement::down_by_rows(map, head, action.lines, goal, false, &text_layout_details)
6851 })
6852 })
6853 }
6854
6855 pub fn select_up_by_lines(&mut self, action: &SelectUpByLines, cx: &mut ViewContext<Self>) {
6856 let text_layout_details = &self.text_layout_details(cx);
6857 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6858 s.move_heads_with(|map, head, goal| {
6859 movement::up_by_rows(map, head, action.lines, goal, false, &text_layout_details)
6860 })
6861 })
6862 }
6863
6864 pub fn select_page_up(&mut self, _: &SelectPageUp, cx: &mut ViewContext<Self>) {
6865 let Some(row_count) = self.visible_row_count() else {
6866 return;
6867 };
6868
6869 let text_layout_details = &self.text_layout_details(cx);
6870
6871 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6872 s.move_heads_with(|map, head, goal| {
6873 movement::up_by_rows(map, head, row_count, goal, false, &text_layout_details)
6874 })
6875 })
6876 }
6877
6878 pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
6879 if self.take_rename(true, cx).is_some() {
6880 return;
6881 }
6882
6883 if self
6884 .context_menu
6885 .write()
6886 .as_mut()
6887 .map(|menu| menu.select_first(self.project.as_ref(), cx))
6888 .unwrap_or(false)
6889 {
6890 return;
6891 }
6892
6893 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6894 cx.propagate();
6895 return;
6896 }
6897
6898 let Some(row_count) = self.visible_row_count() else {
6899 return;
6900 };
6901
6902 let autoscroll = if action.center_cursor {
6903 Autoscroll::center()
6904 } else {
6905 Autoscroll::fit()
6906 };
6907
6908 let text_layout_details = &self.text_layout_details(cx);
6909
6910 self.change_selections(Some(autoscroll), cx, |s| {
6911 let line_mode = s.line_mode;
6912 s.move_with(|map, selection| {
6913 if !selection.is_empty() && !line_mode {
6914 selection.goal = SelectionGoal::None;
6915 }
6916 let (cursor, goal) = movement::up_by_rows(
6917 map,
6918 selection.end,
6919 row_count,
6920 selection.goal,
6921 false,
6922 &text_layout_details,
6923 );
6924 selection.collapse_to(cursor, goal);
6925 });
6926 });
6927 }
6928
6929 pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
6930 let text_layout_details = &self.text_layout_details(cx);
6931 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6932 s.move_heads_with(|map, head, goal| {
6933 movement::up(map, head, goal, false, &text_layout_details)
6934 })
6935 })
6936 }
6937
6938 pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
6939 self.take_rename(true, cx);
6940
6941 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6942 cx.propagate();
6943 return;
6944 }
6945
6946 let text_layout_details = &self.text_layout_details(cx);
6947 let selection_count = self.selections.count();
6948 let first_selection = self.selections.first_anchor();
6949
6950 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6951 let line_mode = s.line_mode;
6952 s.move_with(|map, selection| {
6953 if !selection.is_empty() && !line_mode {
6954 selection.goal = SelectionGoal::None;
6955 }
6956 let (cursor, goal) = movement::down(
6957 map,
6958 selection.end,
6959 selection.goal,
6960 false,
6961 &text_layout_details,
6962 );
6963 selection.collapse_to(cursor, goal);
6964 });
6965 });
6966
6967 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
6968 {
6969 cx.propagate();
6970 }
6971 }
6972
6973 pub fn select_page_down(&mut self, _: &SelectPageDown, cx: &mut ViewContext<Self>) {
6974 let Some(row_count) = self.visible_row_count() else {
6975 return;
6976 };
6977
6978 let text_layout_details = &self.text_layout_details(cx);
6979
6980 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6981 s.move_heads_with(|map, head, goal| {
6982 movement::down_by_rows(map, head, row_count, goal, false, &text_layout_details)
6983 })
6984 })
6985 }
6986
6987 pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
6988 if self.take_rename(true, cx).is_some() {
6989 return;
6990 }
6991
6992 if self
6993 .context_menu
6994 .write()
6995 .as_mut()
6996 .map(|menu| menu.select_last(self.project.as_ref(), cx))
6997 .unwrap_or(false)
6998 {
6999 return;
7000 }
7001
7002 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7003 cx.propagate();
7004 return;
7005 }
7006
7007 let Some(row_count) = self.visible_row_count() else {
7008 return;
7009 };
7010
7011 let autoscroll = if action.center_cursor {
7012 Autoscroll::center()
7013 } else {
7014 Autoscroll::fit()
7015 };
7016
7017 let text_layout_details = &self.text_layout_details(cx);
7018 self.change_selections(Some(autoscroll), cx, |s| {
7019 let line_mode = s.line_mode;
7020 s.move_with(|map, selection| {
7021 if !selection.is_empty() && !line_mode {
7022 selection.goal = SelectionGoal::None;
7023 }
7024 let (cursor, goal) = movement::down_by_rows(
7025 map,
7026 selection.end,
7027 row_count,
7028 selection.goal,
7029 false,
7030 &text_layout_details,
7031 );
7032 selection.collapse_to(cursor, goal);
7033 });
7034 });
7035 }
7036
7037 pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
7038 let text_layout_details = &self.text_layout_details(cx);
7039 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7040 s.move_heads_with(|map, head, goal| {
7041 movement::down(map, head, goal, false, &text_layout_details)
7042 })
7043 });
7044 }
7045
7046 pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
7047 if let Some(context_menu) = self.context_menu.write().as_mut() {
7048 context_menu.select_first(self.project.as_ref(), cx);
7049 }
7050 }
7051
7052 pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
7053 if let Some(context_menu) = self.context_menu.write().as_mut() {
7054 context_menu.select_prev(self.project.as_ref(), cx);
7055 }
7056 }
7057
7058 pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
7059 if let Some(context_menu) = self.context_menu.write().as_mut() {
7060 context_menu.select_next(self.project.as_ref(), cx);
7061 }
7062 }
7063
7064 pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
7065 if let Some(context_menu) = self.context_menu.write().as_mut() {
7066 context_menu.select_last(self.project.as_ref(), cx);
7067 }
7068 }
7069
7070 pub fn move_to_previous_word_start(
7071 &mut self,
7072 _: &MoveToPreviousWordStart,
7073 cx: &mut ViewContext<Self>,
7074 ) {
7075 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7076 s.move_cursors_with(|map, head, _| {
7077 (
7078 movement::previous_word_start(map, head),
7079 SelectionGoal::None,
7080 )
7081 });
7082 })
7083 }
7084
7085 pub fn move_to_previous_subword_start(
7086 &mut self,
7087 _: &MoveToPreviousSubwordStart,
7088 cx: &mut ViewContext<Self>,
7089 ) {
7090 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7091 s.move_cursors_with(|map, head, _| {
7092 (
7093 movement::previous_subword_start(map, head),
7094 SelectionGoal::None,
7095 )
7096 });
7097 })
7098 }
7099
7100 pub fn select_to_previous_word_start(
7101 &mut self,
7102 _: &SelectToPreviousWordStart,
7103 cx: &mut ViewContext<Self>,
7104 ) {
7105 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7106 s.move_heads_with(|map, head, _| {
7107 (
7108 movement::previous_word_start(map, head),
7109 SelectionGoal::None,
7110 )
7111 });
7112 })
7113 }
7114
7115 pub fn select_to_previous_subword_start(
7116 &mut self,
7117 _: &SelectToPreviousSubwordStart,
7118 cx: &mut ViewContext<Self>,
7119 ) {
7120 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7121 s.move_heads_with(|map, head, _| {
7122 (
7123 movement::previous_subword_start(map, head),
7124 SelectionGoal::None,
7125 )
7126 });
7127 })
7128 }
7129
7130 pub fn delete_to_previous_word_start(
7131 &mut self,
7132 _: &DeleteToPreviousWordStart,
7133 cx: &mut ViewContext<Self>,
7134 ) {
7135 self.transact(cx, |this, cx| {
7136 this.select_autoclose_pair(cx);
7137 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7138 let line_mode = s.line_mode;
7139 s.move_with(|map, selection| {
7140 if selection.is_empty() && !line_mode {
7141 let cursor = movement::previous_word_start(map, selection.head());
7142 selection.set_head(cursor, SelectionGoal::None);
7143 }
7144 });
7145 });
7146 this.insert("", cx);
7147 });
7148 }
7149
7150 pub fn delete_to_previous_subword_start(
7151 &mut self,
7152 _: &DeleteToPreviousSubwordStart,
7153 cx: &mut ViewContext<Self>,
7154 ) {
7155 self.transact(cx, |this, cx| {
7156 this.select_autoclose_pair(cx);
7157 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7158 let line_mode = s.line_mode;
7159 s.move_with(|map, selection| {
7160 if selection.is_empty() && !line_mode {
7161 let cursor = movement::previous_subword_start(map, selection.head());
7162 selection.set_head(cursor, SelectionGoal::None);
7163 }
7164 });
7165 });
7166 this.insert("", cx);
7167 });
7168 }
7169
7170 pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
7171 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7172 s.move_cursors_with(|map, head, _| {
7173 (movement::next_word_end(map, head), SelectionGoal::None)
7174 });
7175 })
7176 }
7177
7178 pub fn move_to_next_subword_end(
7179 &mut self,
7180 _: &MoveToNextSubwordEnd,
7181 cx: &mut ViewContext<Self>,
7182 ) {
7183 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7184 s.move_cursors_with(|map, head, _| {
7185 (movement::next_subword_end(map, head), SelectionGoal::None)
7186 });
7187 })
7188 }
7189
7190 pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext<Self>) {
7191 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7192 s.move_heads_with(|map, head, _| {
7193 (movement::next_word_end(map, head), SelectionGoal::None)
7194 });
7195 })
7196 }
7197
7198 pub fn select_to_next_subword_end(
7199 &mut self,
7200 _: &SelectToNextSubwordEnd,
7201 cx: &mut ViewContext<Self>,
7202 ) {
7203 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7204 s.move_heads_with(|map, head, _| {
7205 (movement::next_subword_end(map, head), SelectionGoal::None)
7206 });
7207 })
7208 }
7209
7210 pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
7211 self.transact(cx, |this, cx| {
7212 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7213 let line_mode = s.line_mode;
7214 s.move_with(|map, selection| {
7215 if selection.is_empty() && !line_mode {
7216 let cursor = movement::next_word_end(map, selection.head());
7217 selection.set_head(cursor, SelectionGoal::None);
7218 }
7219 });
7220 });
7221 this.insert("", cx);
7222 });
7223 }
7224
7225 pub fn delete_to_next_subword_end(
7226 &mut self,
7227 _: &DeleteToNextSubwordEnd,
7228 cx: &mut ViewContext<Self>,
7229 ) {
7230 self.transact(cx, |this, cx| {
7231 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7232 s.move_with(|map, selection| {
7233 if selection.is_empty() {
7234 let cursor = movement::next_subword_end(map, selection.head());
7235 selection.set_head(cursor, SelectionGoal::None);
7236 }
7237 });
7238 });
7239 this.insert("", cx);
7240 });
7241 }
7242
7243 pub fn move_to_beginning_of_line(
7244 &mut self,
7245 action: &MoveToBeginningOfLine,
7246 cx: &mut ViewContext<Self>,
7247 ) {
7248 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7249 s.move_cursors_with(|map, head, _| {
7250 (
7251 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
7252 SelectionGoal::None,
7253 )
7254 });
7255 })
7256 }
7257
7258 pub fn select_to_beginning_of_line(
7259 &mut self,
7260 action: &SelectToBeginningOfLine,
7261 cx: &mut ViewContext<Self>,
7262 ) {
7263 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7264 s.move_heads_with(|map, head, _| {
7265 (
7266 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
7267 SelectionGoal::None,
7268 )
7269 });
7270 });
7271 }
7272
7273 pub fn delete_to_beginning_of_line(
7274 &mut self,
7275 _: &DeleteToBeginningOfLine,
7276 cx: &mut ViewContext<Self>,
7277 ) {
7278 self.transact(cx, |this, cx| {
7279 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7280 s.move_with(|_, selection| {
7281 selection.reversed = true;
7282 });
7283 });
7284
7285 this.select_to_beginning_of_line(
7286 &SelectToBeginningOfLine {
7287 stop_at_soft_wraps: false,
7288 },
7289 cx,
7290 );
7291 this.backspace(&Backspace, cx);
7292 });
7293 }
7294
7295 pub fn move_to_end_of_line(&mut self, action: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
7296 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7297 s.move_cursors_with(|map, head, _| {
7298 (
7299 movement::line_end(map, head, action.stop_at_soft_wraps),
7300 SelectionGoal::None,
7301 )
7302 });
7303 })
7304 }
7305
7306 pub fn select_to_end_of_line(
7307 &mut self,
7308 action: &SelectToEndOfLine,
7309 cx: &mut ViewContext<Self>,
7310 ) {
7311 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7312 s.move_heads_with(|map, head, _| {
7313 (
7314 movement::line_end(map, head, action.stop_at_soft_wraps),
7315 SelectionGoal::None,
7316 )
7317 });
7318 })
7319 }
7320
7321 pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
7322 self.transact(cx, |this, cx| {
7323 this.select_to_end_of_line(
7324 &SelectToEndOfLine {
7325 stop_at_soft_wraps: false,
7326 },
7327 cx,
7328 );
7329 this.delete(&Delete, cx);
7330 });
7331 }
7332
7333 pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
7334 self.transact(cx, |this, cx| {
7335 this.select_to_end_of_line(
7336 &SelectToEndOfLine {
7337 stop_at_soft_wraps: false,
7338 },
7339 cx,
7340 );
7341 this.cut(&Cut, cx);
7342 });
7343 }
7344
7345 pub fn move_to_start_of_paragraph(
7346 &mut self,
7347 _: &MoveToStartOfParagraph,
7348 cx: &mut ViewContext<Self>,
7349 ) {
7350 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7351 cx.propagate();
7352 return;
7353 }
7354
7355 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7356 s.move_with(|map, selection| {
7357 selection.collapse_to(
7358 movement::start_of_paragraph(map, selection.head(), 1),
7359 SelectionGoal::None,
7360 )
7361 });
7362 })
7363 }
7364
7365 pub fn move_to_end_of_paragraph(
7366 &mut self,
7367 _: &MoveToEndOfParagraph,
7368 cx: &mut ViewContext<Self>,
7369 ) {
7370 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7371 cx.propagate();
7372 return;
7373 }
7374
7375 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7376 s.move_with(|map, selection| {
7377 selection.collapse_to(
7378 movement::end_of_paragraph(map, selection.head(), 1),
7379 SelectionGoal::None,
7380 )
7381 });
7382 })
7383 }
7384
7385 pub fn select_to_start_of_paragraph(
7386 &mut self,
7387 _: &SelectToStartOfParagraph,
7388 cx: &mut ViewContext<Self>,
7389 ) {
7390 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7391 cx.propagate();
7392 return;
7393 }
7394
7395 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7396 s.move_heads_with(|map, head, _| {
7397 (
7398 movement::start_of_paragraph(map, head, 1),
7399 SelectionGoal::None,
7400 )
7401 });
7402 })
7403 }
7404
7405 pub fn select_to_end_of_paragraph(
7406 &mut self,
7407 _: &SelectToEndOfParagraph,
7408 cx: &mut ViewContext<Self>,
7409 ) {
7410 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7411 cx.propagate();
7412 return;
7413 }
7414
7415 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7416 s.move_heads_with(|map, head, _| {
7417 (
7418 movement::end_of_paragraph(map, head, 1),
7419 SelectionGoal::None,
7420 )
7421 });
7422 })
7423 }
7424
7425 pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
7426 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7427 cx.propagate();
7428 return;
7429 }
7430
7431 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7432 s.select_ranges(vec![0..0]);
7433 });
7434 }
7435
7436 pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
7437 let mut selection = self.selections.last::<Point>(cx);
7438 selection.set_head(Point::zero(), SelectionGoal::None);
7439
7440 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7441 s.select(vec![selection]);
7442 });
7443 }
7444
7445 pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
7446 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7447 cx.propagate();
7448 return;
7449 }
7450
7451 let cursor = self.buffer.read(cx).read(cx).len();
7452 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7453 s.select_ranges(vec![cursor..cursor])
7454 });
7455 }
7456
7457 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
7458 self.nav_history = nav_history;
7459 }
7460
7461 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
7462 self.nav_history.as_ref()
7463 }
7464
7465 fn push_to_nav_history(
7466 &mut self,
7467 cursor_anchor: Anchor,
7468 new_position: Option<Point>,
7469 cx: &mut ViewContext<Self>,
7470 ) {
7471 if let Some(nav_history) = self.nav_history.as_mut() {
7472 let buffer = self.buffer.read(cx).read(cx);
7473 let cursor_position = cursor_anchor.to_point(&buffer);
7474 let scroll_state = self.scroll_manager.anchor();
7475 let scroll_top_row = scroll_state.top_row(&buffer);
7476 drop(buffer);
7477
7478 if let Some(new_position) = new_position {
7479 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
7480 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
7481 return;
7482 }
7483 }
7484
7485 nav_history.push(
7486 Some(NavigationData {
7487 cursor_anchor,
7488 cursor_position,
7489 scroll_anchor: scroll_state,
7490 scroll_top_row,
7491 }),
7492 cx,
7493 );
7494 }
7495 }
7496
7497 pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
7498 let buffer = self.buffer.read(cx).snapshot(cx);
7499 let mut selection = self.selections.first::<usize>(cx);
7500 selection.set_head(buffer.len(), SelectionGoal::None);
7501 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7502 s.select(vec![selection]);
7503 });
7504 }
7505
7506 pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
7507 let end = self.buffer.read(cx).read(cx).len();
7508 self.change_selections(None, cx, |s| {
7509 s.select_ranges(vec![0..end]);
7510 });
7511 }
7512
7513 pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
7514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7515 let mut selections = self.selections.all::<Point>(cx);
7516 let max_point = display_map.buffer_snapshot.max_point();
7517 for selection in &mut selections {
7518 let rows = selection.spanned_rows(true, &display_map);
7519 selection.start = Point::new(rows.start.0, 0);
7520 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
7521 selection.reversed = false;
7522 }
7523 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7524 s.select(selections);
7525 });
7526 }
7527
7528 pub fn split_selection_into_lines(
7529 &mut self,
7530 _: &SplitSelectionIntoLines,
7531 cx: &mut ViewContext<Self>,
7532 ) {
7533 let mut to_unfold = Vec::new();
7534 let mut new_selection_ranges = Vec::new();
7535 {
7536 let selections = self.selections.all::<Point>(cx);
7537 let buffer = self.buffer.read(cx).read(cx);
7538 for selection in selections {
7539 for row in selection.start.row..selection.end.row {
7540 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
7541 new_selection_ranges.push(cursor..cursor);
7542 }
7543 new_selection_ranges.push(selection.end..selection.end);
7544 to_unfold.push(selection.start..selection.end);
7545 }
7546 }
7547 self.unfold_ranges(to_unfold, true, true, cx);
7548 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7549 s.select_ranges(new_selection_ranges);
7550 });
7551 }
7552
7553 pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
7554 self.add_selection(true, cx);
7555 }
7556
7557 pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
7558 self.add_selection(false, cx);
7559 }
7560
7561 fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
7562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7563 let mut selections = self.selections.all::<Point>(cx);
7564 let text_layout_details = self.text_layout_details(cx);
7565 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
7566 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
7567 let range = oldest_selection.display_range(&display_map).sorted();
7568
7569 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
7570 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
7571 let positions = start_x.min(end_x)..start_x.max(end_x);
7572
7573 selections.clear();
7574 let mut stack = Vec::new();
7575 for row in range.start.row().0..=range.end.row().0 {
7576 if let Some(selection) = self.selections.build_columnar_selection(
7577 &display_map,
7578 DisplayRow(row),
7579 &positions,
7580 oldest_selection.reversed,
7581 &text_layout_details,
7582 ) {
7583 stack.push(selection.id);
7584 selections.push(selection);
7585 }
7586 }
7587
7588 if above {
7589 stack.reverse();
7590 }
7591
7592 AddSelectionsState { above, stack }
7593 });
7594
7595 let last_added_selection = *state.stack.last().unwrap();
7596 let mut new_selections = Vec::new();
7597 if above == state.above {
7598 let end_row = if above {
7599 DisplayRow(0)
7600 } else {
7601 display_map.max_point().row()
7602 };
7603
7604 'outer: for selection in selections {
7605 if selection.id == last_added_selection {
7606 let range = selection.display_range(&display_map).sorted();
7607 debug_assert_eq!(range.start.row(), range.end.row());
7608 let mut row = range.start.row();
7609 let positions =
7610 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
7611 px(start)..px(end)
7612 } else {
7613 let start_x =
7614 display_map.x_for_display_point(range.start, &text_layout_details);
7615 let end_x =
7616 display_map.x_for_display_point(range.end, &text_layout_details);
7617 start_x.min(end_x)..start_x.max(end_x)
7618 };
7619
7620 while row != end_row {
7621 if above {
7622 row.0 -= 1;
7623 } else {
7624 row.0 += 1;
7625 }
7626
7627 if let Some(new_selection) = self.selections.build_columnar_selection(
7628 &display_map,
7629 row,
7630 &positions,
7631 selection.reversed,
7632 &text_layout_details,
7633 ) {
7634 state.stack.push(new_selection.id);
7635 if above {
7636 new_selections.push(new_selection);
7637 new_selections.push(selection);
7638 } else {
7639 new_selections.push(selection);
7640 new_selections.push(new_selection);
7641 }
7642
7643 continue 'outer;
7644 }
7645 }
7646 }
7647
7648 new_selections.push(selection);
7649 }
7650 } else {
7651 new_selections = selections;
7652 new_selections.retain(|s| s.id != last_added_selection);
7653 state.stack.pop();
7654 }
7655
7656 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7657 s.select(new_selections);
7658 });
7659 if state.stack.len() > 1 {
7660 self.add_selections_state = Some(state);
7661 }
7662 }
7663
7664 pub fn select_next_match_internal(
7665 &mut self,
7666 display_map: &DisplaySnapshot,
7667 replace_newest: bool,
7668 autoscroll: Option<Autoscroll>,
7669 cx: &mut ViewContext<Self>,
7670 ) -> Result<()> {
7671 fn select_next_match_ranges(
7672 this: &mut Editor,
7673 range: Range<usize>,
7674 replace_newest: bool,
7675 auto_scroll: Option<Autoscroll>,
7676 cx: &mut ViewContext<Editor>,
7677 ) {
7678 this.unfold_ranges([range.clone()], false, true, cx);
7679 this.change_selections(auto_scroll, cx, |s| {
7680 if replace_newest {
7681 s.delete(s.newest_anchor().id);
7682 }
7683 s.insert_range(range.clone());
7684 });
7685 }
7686
7687 let buffer = &display_map.buffer_snapshot;
7688 let mut selections = self.selections.all::<usize>(cx);
7689 if let Some(mut select_next_state) = self.select_next_state.take() {
7690 let query = &select_next_state.query;
7691 if !select_next_state.done {
7692 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
7693 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
7694 let mut next_selected_range = None;
7695
7696 let bytes_after_last_selection =
7697 buffer.bytes_in_range(last_selection.end..buffer.len());
7698 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
7699 let query_matches = query
7700 .stream_find_iter(bytes_after_last_selection)
7701 .map(|result| (last_selection.end, result))
7702 .chain(
7703 query
7704 .stream_find_iter(bytes_before_first_selection)
7705 .map(|result| (0, result)),
7706 );
7707
7708 for (start_offset, query_match) in query_matches {
7709 let query_match = query_match.unwrap(); // can only fail due to I/O
7710 let offset_range =
7711 start_offset + query_match.start()..start_offset + query_match.end();
7712 let display_range = offset_range.start.to_display_point(&display_map)
7713 ..offset_range.end.to_display_point(&display_map);
7714
7715 if !select_next_state.wordwise
7716 || (!movement::is_inside_word(&display_map, display_range.start)
7717 && !movement::is_inside_word(&display_map, display_range.end))
7718 {
7719 // TODO: This is n^2, because we might check all the selections
7720 if !selections
7721 .iter()
7722 .any(|selection| selection.range().overlaps(&offset_range))
7723 {
7724 next_selected_range = Some(offset_range);
7725 break;
7726 }
7727 }
7728 }
7729
7730 if let Some(next_selected_range) = next_selected_range {
7731 select_next_match_ranges(
7732 self,
7733 next_selected_range,
7734 replace_newest,
7735 autoscroll,
7736 cx,
7737 );
7738 } else {
7739 select_next_state.done = true;
7740 }
7741 }
7742
7743 self.select_next_state = Some(select_next_state);
7744 } else {
7745 let mut only_carets = true;
7746 let mut same_text_selected = true;
7747 let mut selected_text = None;
7748
7749 let mut selections_iter = selections.iter().peekable();
7750 while let Some(selection) = selections_iter.next() {
7751 if selection.start != selection.end {
7752 only_carets = false;
7753 }
7754
7755 if same_text_selected {
7756 if selected_text.is_none() {
7757 selected_text =
7758 Some(buffer.text_for_range(selection.range()).collect::<String>());
7759 }
7760
7761 if let Some(next_selection) = selections_iter.peek() {
7762 if next_selection.range().len() == selection.range().len() {
7763 let next_selected_text = buffer
7764 .text_for_range(next_selection.range())
7765 .collect::<String>();
7766 if Some(next_selected_text) != selected_text {
7767 same_text_selected = false;
7768 selected_text = None;
7769 }
7770 } else {
7771 same_text_selected = false;
7772 selected_text = None;
7773 }
7774 }
7775 }
7776 }
7777
7778 if only_carets {
7779 for selection in &mut selections {
7780 let word_range = movement::surrounding_word(
7781 &display_map,
7782 selection.start.to_display_point(&display_map),
7783 );
7784 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
7785 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
7786 selection.goal = SelectionGoal::None;
7787 selection.reversed = false;
7788 select_next_match_ranges(
7789 self,
7790 selection.start..selection.end,
7791 replace_newest,
7792 autoscroll,
7793 cx,
7794 );
7795 }
7796
7797 if selections.len() == 1 {
7798 let selection = selections
7799 .last()
7800 .expect("ensured that there's only one selection");
7801 let query = buffer
7802 .text_for_range(selection.start..selection.end)
7803 .collect::<String>();
7804 let is_empty = query.is_empty();
7805 let select_state = SelectNextState {
7806 query: AhoCorasick::new(&[query])?,
7807 wordwise: true,
7808 done: is_empty,
7809 };
7810 self.select_next_state = Some(select_state);
7811 } else {
7812 self.select_next_state = None;
7813 }
7814 } else if let Some(selected_text) = selected_text {
7815 self.select_next_state = Some(SelectNextState {
7816 query: AhoCorasick::new(&[selected_text])?,
7817 wordwise: false,
7818 done: false,
7819 });
7820 self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?;
7821 }
7822 }
7823 Ok(())
7824 }
7825
7826 pub fn select_all_matches(
7827 &mut self,
7828 _action: &SelectAllMatches,
7829 cx: &mut ViewContext<Self>,
7830 ) -> Result<()> {
7831 self.push_to_selection_history();
7832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7833
7834 self.select_next_match_internal(&display_map, false, None, cx)?;
7835 let Some(select_next_state) = self.select_next_state.as_mut() else {
7836 return Ok(());
7837 };
7838 if select_next_state.done {
7839 return Ok(());
7840 }
7841
7842 let mut new_selections = self.selections.all::<usize>(cx);
7843
7844 let buffer = &display_map.buffer_snapshot;
7845 let query_matches = select_next_state
7846 .query
7847 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
7848
7849 for query_match in query_matches {
7850 let query_match = query_match.unwrap(); // can only fail due to I/O
7851 let offset_range = query_match.start()..query_match.end();
7852 let display_range = offset_range.start.to_display_point(&display_map)
7853 ..offset_range.end.to_display_point(&display_map);
7854
7855 if !select_next_state.wordwise
7856 || (!movement::is_inside_word(&display_map, display_range.start)
7857 && !movement::is_inside_word(&display_map, display_range.end))
7858 {
7859 self.selections.change_with(cx, |selections| {
7860 new_selections.push(Selection {
7861 id: selections.new_selection_id(),
7862 start: offset_range.start,
7863 end: offset_range.end,
7864 reversed: false,
7865 goal: SelectionGoal::None,
7866 });
7867 });
7868 }
7869 }
7870
7871 new_selections.sort_by_key(|selection| selection.start);
7872 let mut ix = 0;
7873 while ix + 1 < new_selections.len() {
7874 let current_selection = &new_selections[ix];
7875 let next_selection = &new_selections[ix + 1];
7876 if current_selection.range().overlaps(&next_selection.range()) {
7877 if current_selection.id < next_selection.id {
7878 new_selections.remove(ix + 1);
7879 } else {
7880 new_selections.remove(ix);
7881 }
7882 } else {
7883 ix += 1;
7884 }
7885 }
7886
7887 select_next_state.done = true;
7888 self.unfold_ranges(
7889 new_selections.iter().map(|selection| selection.range()),
7890 false,
7891 false,
7892 cx,
7893 );
7894 self.change_selections(Some(Autoscroll::fit()), cx, |selections| {
7895 selections.select(new_selections)
7896 });
7897
7898 Ok(())
7899 }
7900
7901 pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) -> Result<()> {
7902 self.push_to_selection_history();
7903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7904 self.select_next_match_internal(
7905 &display_map,
7906 action.replace_newest,
7907 Some(Autoscroll::newest()),
7908 cx,
7909 )?;
7910 Ok(())
7911 }
7912
7913 pub fn select_previous(
7914 &mut self,
7915 action: &SelectPrevious,
7916 cx: &mut ViewContext<Self>,
7917 ) -> Result<()> {
7918 self.push_to_selection_history();
7919 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7920 let buffer = &display_map.buffer_snapshot;
7921 let mut selections = self.selections.all::<usize>(cx);
7922 if let Some(mut select_prev_state) = self.select_prev_state.take() {
7923 let query = &select_prev_state.query;
7924 if !select_prev_state.done {
7925 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
7926 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
7927 let mut next_selected_range = None;
7928 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
7929 let bytes_before_last_selection =
7930 buffer.reversed_bytes_in_range(0..last_selection.start);
7931 let bytes_after_first_selection =
7932 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
7933 let query_matches = query
7934 .stream_find_iter(bytes_before_last_selection)
7935 .map(|result| (last_selection.start, result))
7936 .chain(
7937 query
7938 .stream_find_iter(bytes_after_first_selection)
7939 .map(|result| (buffer.len(), result)),
7940 );
7941 for (end_offset, query_match) in query_matches {
7942 let query_match = query_match.unwrap(); // can only fail due to I/O
7943 let offset_range =
7944 end_offset - query_match.end()..end_offset - query_match.start();
7945 let display_range = offset_range.start.to_display_point(&display_map)
7946 ..offset_range.end.to_display_point(&display_map);
7947
7948 if !select_prev_state.wordwise
7949 || (!movement::is_inside_word(&display_map, display_range.start)
7950 && !movement::is_inside_word(&display_map, display_range.end))
7951 {
7952 next_selected_range = Some(offset_range);
7953 break;
7954 }
7955 }
7956
7957 if let Some(next_selected_range) = next_selected_range {
7958 self.unfold_ranges([next_selected_range.clone()], false, true, cx);
7959 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
7960 if action.replace_newest {
7961 s.delete(s.newest_anchor().id);
7962 }
7963 s.insert_range(next_selected_range);
7964 });
7965 } else {
7966 select_prev_state.done = true;
7967 }
7968 }
7969
7970 self.select_prev_state = Some(select_prev_state);
7971 } else {
7972 let mut only_carets = true;
7973 let mut same_text_selected = true;
7974 let mut selected_text = None;
7975
7976 let mut selections_iter = selections.iter().peekable();
7977 while let Some(selection) = selections_iter.next() {
7978 if selection.start != selection.end {
7979 only_carets = false;
7980 }
7981
7982 if same_text_selected {
7983 if selected_text.is_none() {
7984 selected_text =
7985 Some(buffer.text_for_range(selection.range()).collect::<String>());
7986 }
7987
7988 if let Some(next_selection) = selections_iter.peek() {
7989 if next_selection.range().len() == selection.range().len() {
7990 let next_selected_text = buffer
7991 .text_for_range(next_selection.range())
7992 .collect::<String>();
7993 if Some(next_selected_text) != selected_text {
7994 same_text_selected = false;
7995 selected_text = None;
7996 }
7997 } else {
7998 same_text_selected = false;
7999 selected_text = None;
8000 }
8001 }
8002 }
8003 }
8004
8005 if only_carets {
8006 for selection in &mut selections {
8007 let word_range = movement::surrounding_word(
8008 &display_map,
8009 selection.start.to_display_point(&display_map),
8010 );
8011 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
8012 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
8013 selection.goal = SelectionGoal::None;
8014 selection.reversed = false;
8015 }
8016 if selections.len() == 1 {
8017 let selection = selections
8018 .last()
8019 .expect("ensured that there's only one selection");
8020 let query = buffer
8021 .text_for_range(selection.start..selection.end)
8022 .collect::<String>();
8023 let is_empty = query.is_empty();
8024 let select_state = SelectNextState {
8025 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
8026 wordwise: true,
8027 done: is_empty,
8028 };
8029 self.select_prev_state = Some(select_state);
8030 } else {
8031 self.select_prev_state = None;
8032 }
8033
8034 self.unfold_ranges(
8035 selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
8036 false,
8037 true,
8038 cx,
8039 );
8040 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
8041 s.select(selections);
8042 });
8043 } else if let Some(selected_text) = selected_text {
8044 self.select_prev_state = Some(SelectNextState {
8045 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
8046 wordwise: false,
8047 done: false,
8048 });
8049 self.select_previous(action, cx)?;
8050 }
8051 }
8052 Ok(())
8053 }
8054
8055 pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
8056 let text_layout_details = &self.text_layout_details(cx);
8057 self.transact(cx, |this, cx| {
8058 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8059 let mut edits = Vec::new();
8060 let mut selection_edit_ranges = Vec::new();
8061 let mut last_toggled_row = None;
8062 let snapshot = this.buffer.read(cx).read(cx);
8063 let empty_str: Arc<str> = "".into();
8064 let mut suffixes_inserted = Vec::new();
8065
8066 fn comment_prefix_range(
8067 snapshot: &MultiBufferSnapshot,
8068 row: MultiBufferRow,
8069 comment_prefix: &str,
8070 comment_prefix_whitespace: &str,
8071 ) -> Range<Point> {
8072 let start = Point::new(row.0, snapshot.indent_size_for_line(row).len);
8073
8074 let mut line_bytes = snapshot
8075 .bytes_in_range(start..snapshot.max_point())
8076 .flatten()
8077 .copied();
8078
8079 // If this line currently begins with the line comment prefix, then record
8080 // the range containing the prefix.
8081 if line_bytes
8082 .by_ref()
8083 .take(comment_prefix.len())
8084 .eq(comment_prefix.bytes())
8085 {
8086 // Include any whitespace that matches the comment prefix.
8087 let matching_whitespace_len = line_bytes
8088 .zip(comment_prefix_whitespace.bytes())
8089 .take_while(|(a, b)| a == b)
8090 .count() as u32;
8091 let end = Point::new(
8092 start.row,
8093 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
8094 );
8095 start..end
8096 } else {
8097 start..start
8098 }
8099 }
8100
8101 fn comment_suffix_range(
8102 snapshot: &MultiBufferSnapshot,
8103 row: MultiBufferRow,
8104 comment_suffix: &str,
8105 comment_suffix_has_leading_space: bool,
8106 ) -> Range<Point> {
8107 let end = Point::new(row.0, snapshot.line_len(row));
8108 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
8109
8110 let mut line_end_bytes = snapshot
8111 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
8112 .flatten()
8113 .copied();
8114
8115 let leading_space_len = if suffix_start_column > 0
8116 && line_end_bytes.next() == Some(b' ')
8117 && comment_suffix_has_leading_space
8118 {
8119 1
8120 } else {
8121 0
8122 };
8123
8124 // If this line currently begins with the line comment prefix, then record
8125 // the range containing the prefix.
8126 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
8127 let start = Point::new(end.row, suffix_start_column - leading_space_len);
8128 start..end
8129 } else {
8130 end..end
8131 }
8132 }
8133
8134 // TODO: Handle selections that cross excerpts
8135 for selection in &mut selections {
8136 let start_column = snapshot
8137 .indent_size_for_line(MultiBufferRow(selection.start.row))
8138 .len;
8139 let language = if let Some(language) =
8140 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
8141 {
8142 language
8143 } else {
8144 continue;
8145 };
8146
8147 selection_edit_ranges.clear();
8148
8149 // If multiple selections contain a given row, avoid processing that
8150 // row more than once.
8151 let mut start_row = MultiBufferRow(selection.start.row);
8152 if last_toggled_row == Some(start_row) {
8153 start_row = start_row.next_row();
8154 }
8155 let end_row =
8156 if selection.end.row > selection.start.row && selection.end.column == 0 {
8157 MultiBufferRow(selection.end.row - 1)
8158 } else {
8159 MultiBufferRow(selection.end.row)
8160 };
8161 last_toggled_row = Some(end_row);
8162
8163 if start_row > end_row {
8164 continue;
8165 }
8166
8167 // If the language has line comments, toggle those.
8168 let full_comment_prefixes = language.line_comment_prefixes();
8169 if !full_comment_prefixes.is_empty() {
8170 let first_prefix = full_comment_prefixes
8171 .first()
8172 .expect("prefixes is non-empty");
8173 let prefix_trimmed_lengths = full_comment_prefixes
8174 .iter()
8175 .map(|p| p.trim_end_matches(' ').len())
8176 .collect::<SmallVec<[usize; 4]>>();
8177
8178 let mut all_selection_lines_are_comments = true;
8179
8180 for row in start_row.0..=end_row.0 {
8181 let row = MultiBufferRow(row);
8182 if start_row < end_row && snapshot.is_line_blank(row) {
8183 continue;
8184 }
8185
8186 let prefix_range = full_comment_prefixes
8187 .iter()
8188 .zip(prefix_trimmed_lengths.iter().copied())
8189 .map(|(prefix, trimmed_prefix_len)| {
8190 comment_prefix_range(
8191 snapshot.deref(),
8192 row,
8193 &prefix[..trimmed_prefix_len],
8194 &prefix[trimmed_prefix_len..],
8195 )
8196 })
8197 .max_by_key(|range| range.end.column - range.start.column)
8198 .expect("prefixes is non-empty");
8199
8200 if prefix_range.is_empty() {
8201 all_selection_lines_are_comments = false;
8202 }
8203
8204 selection_edit_ranges.push(prefix_range);
8205 }
8206
8207 if all_selection_lines_are_comments {
8208 edits.extend(
8209 selection_edit_ranges
8210 .iter()
8211 .cloned()
8212 .map(|range| (range, empty_str.clone())),
8213 );
8214 } else {
8215 let min_column = selection_edit_ranges
8216 .iter()
8217 .map(|range| range.start.column)
8218 .min()
8219 .unwrap_or(0);
8220 edits.extend(selection_edit_ranges.iter().map(|range| {
8221 let position = Point::new(range.start.row, min_column);
8222 (position..position, first_prefix.clone())
8223 }));
8224 }
8225 } else if let Some((full_comment_prefix, comment_suffix)) =
8226 language.block_comment_delimiters()
8227 {
8228 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
8229 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
8230 let prefix_range = comment_prefix_range(
8231 snapshot.deref(),
8232 start_row,
8233 comment_prefix,
8234 comment_prefix_whitespace,
8235 );
8236 let suffix_range = comment_suffix_range(
8237 snapshot.deref(),
8238 end_row,
8239 comment_suffix.trim_start_matches(' '),
8240 comment_suffix.starts_with(' '),
8241 );
8242
8243 if prefix_range.is_empty() || suffix_range.is_empty() {
8244 edits.push((
8245 prefix_range.start..prefix_range.start,
8246 full_comment_prefix.clone(),
8247 ));
8248 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
8249 suffixes_inserted.push((end_row, comment_suffix.len()));
8250 } else {
8251 edits.push((prefix_range, empty_str.clone()));
8252 edits.push((suffix_range, empty_str.clone()));
8253 }
8254 } else {
8255 continue;
8256 }
8257 }
8258
8259 drop(snapshot);
8260 this.buffer.update(cx, |buffer, cx| {
8261 buffer.edit(edits, None, cx);
8262 });
8263
8264 // Adjust selections so that they end before any comment suffixes that
8265 // were inserted.
8266 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
8267 let mut selections = this.selections.all::<Point>(cx);
8268 let snapshot = this.buffer.read(cx).read(cx);
8269 for selection in &mut selections {
8270 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
8271 match row.cmp(&MultiBufferRow(selection.end.row)) {
8272 Ordering::Less => {
8273 suffixes_inserted.next();
8274 continue;
8275 }
8276 Ordering::Greater => break,
8277 Ordering::Equal => {
8278 if selection.end.column == snapshot.line_len(row) {
8279 if selection.is_empty() {
8280 selection.start.column -= suffix_len as u32;
8281 }
8282 selection.end.column -= suffix_len as u32;
8283 }
8284 break;
8285 }
8286 }
8287 }
8288 }
8289
8290 drop(snapshot);
8291 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
8292
8293 let selections = this.selections.all::<Point>(cx);
8294 let selections_on_single_row = selections.windows(2).all(|selections| {
8295 selections[0].start.row == selections[1].start.row
8296 && selections[0].end.row == selections[1].end.row
8297 && selections[0].start.row == selections[0].end.row
8298 });
8299 let selections_selecting = selections
8300 .iter()
8301 .any(|selection| selection.start != selection.end);
8302 let advance_downwards = action.advance_downwards
8303 && selections_on_single_row
8304 && !selections_selecting
8305 && !matches!(this.mode, EditorMode::SingleLine { .. });
8306
8307 if advance_downwards {
8308 let snapshot = this.buffer.read(cx).snapshot(cx);
8309
8310 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
8311 s.move_cursors_with(|display_snapshot, display_point, _| {
8312 let mut point = display_point.to_point(display_snapshot);
8313 point.row += 1;
8314 point = snapshot.clip_point(point, Bias::Left);
8315 let display_point = point.to_display_point(display_snapshot);
8316 let goal = SelectionGoal::HorizontalPosition(
8317 display_snapshot
8318 .x_for_display_point(display_point, &text_layout_details)
8319 .into(),
8320 );
8321 (display_point, goal)
8322 })
8323 });
8324 }
8325 });
8326 }
8327
8328 pub fn select_enclosing_symbol(
8329 &mut self,
8330 _: &SelectEnclosingSymbol,
8331 cx: &mut ViewContext<Self>,
8332 ) {
8333 let buffer = self.buffer.read(cx).snapshot(cx);
8334 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
8335
8336 fn update_selection(
8337 selection: &Selection<usize>,
8338 buffer_snap: &MultiBufferSnapshot,
8339 ) -> Option<Selection<usize>> {
8340 let cursor = selection.head();
8341 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
8342 for symbol in symbols.iter().rev() {
8343 let start = symbol.range.start.to_offset(&buffer_snap);
8344 let end = symbol.range.end.to_offset(&buffer_snap);
8345 let new_range = start..end;
8346 if start < selection.start || end > selection.end {
8347 return Some(Selection {
8348 id: selection.id,
8349 start: new_range.start,
8350 end: new_range.end,
8351 goal: SelectionGoal::None,
8352 reversed: selection.reversed,
8353 });
8354 }
8355 }
8356 None
8357 }
8358
8359 let mut selected_larger_symbol = false;
8360 let new_selections = old_selections
8361 .iter()
8362 .map(|selection| match update_selection(selection, &buffer) {
8363 Some(new_selection) => {
8364 if new_selection.range() != selection.range() {
8365 selected_larger_symbol = true;
8366 }
8367 new_selection
8368 }
8369 None => selection.clone(),
8370 })
8371 .collect::<Vec<_>>();
8372
8373 if selected_larger_symbol {
8374 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8375 s.select(new_selections);
8376 });
8377 }
8378 }
8379
8380 pub fn select_larger_syntax_node(
8381 &mut self,
8382 _: &SelectLargerSyntaxNode,
8383 cx: &mut ViewContext<Self>,
8384 ) {
8385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8386 let buffer = self.buffer.read(cx).snapshot(cx);
8387 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
8388
8389 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
8390 let mut selected_larger_node = false;
8391 let new_selections = old_selections
8392 .iter()
8393 .map(|selection| {
8394 let old_range = selection.start..selection.end;
8395 let mut new_range = old_range.clone();
8396 while let Some(containing_range) =
8397 buffer.range_for_syntax_ancestor(new_range.clone())
8398 {
8399 new_range = containing_range;
8400 if !display_map.intersects_fold(new_range.start)
8401 && !display_map.intersects_fold(new_range.end)
8402 {
8403 break;
8404 }
8405 }
8406
8407 selected_larger_node |= new_range != old_range;
8408 Selection {
8409 id: selection.id,
8410 start: new_range.start,
8411 end: new_range.end,
8412 goal: SelectionGoal::None,
8413 reversed: selection.reversed,
8414 }
8415 })
8416 .collect::<Vec<_>>();
8417
8418 if selected_larger_node {
8419 stack.push(old_selections);
8420 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8421 s.select(new_selections);
8422 });
8423 }
8424 self.select_larger_syntax_node_stack = stack;
8425 }
8426
8427 pub fn select_smaller_syntax_node(
8428 &mut self,
8429 _: &SelectSmallerSyntaxNode,
8430 cx: &mut ViewContext<Self>,
8431 ) {
8432 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
8433 if let Some(selections) = stack.pop() {
8434 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8435 s.select(selections.to_vec());
8436 });
8437 }
8438 self.select_larger_syntax_node_stack = stack;
8439 }
8440
8441 fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
8442 if !EditorSettings::get_global(cx).gutter.runnables {
8443 self.clear_tasks();
8444 return Task::ready(());
8445 }
8446 let project = self.project.clone();
8447 cx.spawn(|this, mut cx| async move {
8448 let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
8449 this.display_map.update(cx, |map, cx| map.snapshot(cx))
8450 }) else {
8451 return;
8452 };
8453
8454 let Some(project) = project else {
8455 return;
8456 };
8457
8458 let hide_runnables = project
8459 .update(&mut cx, |project, cx| {
8460 // Do not display any test indicators in non-dev server remote projects.
8461 project.is_remote() && project.ssh_connection_string(cx).is_none()
8462 })
8463 .unwrap_or(true);
8464 if hide_runnables {
8465 return;
8466 }
8467 let new_rows =
8468 cx.background_executor()
8469 .spawn({
8470 let snapshot = display_snapshot.clone();
8471 async move {
8472 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
8473 }
8474 })
8475 .await;
8476 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
8477
8478 this.update(&mut cx, |this, _| {
8479 this.clear_tasks();
8480 for (key, value) in rows {
8481 this.insert_tasks(key, value);
8482 }
8483 })
8484 .ok();
8485 })
8486 }
8487 fn fetch_runnable_ranges(
8488 snapshot: &DisplaySnapshot,
8489 range: Range<Anchor>,
8490 ) -> Vec<language::RunnableRange> {
8491 snapshot.buffer_snapshot.runnable_ranges(range).collect()
8492 }
8493
8494 fn runnable_rows(
8495 project: Model<Project>,
8496 snapshot: DisplaySnapshot,
8497 runnable_ranges: Vec<RunnableRange>,
8498 mut cx: AsyncWindowContext,
8499 ) -> Vec<((BufferId, u32), RunnableTasks)> {
8500 runnable_ranges
8501 .into_iter()
8502 .filter_map(|mut runnable| {
8503 let tasks = cx
8504 .update(|cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
8505 .ok()?;
8506 if tasks.is_empty() {
8507 return None;
8508 }
8509
8510 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
8511
8512 let row = snapshot
8513 .buffer_snapshot
8514 .buffer_line_for_row(MultiBufferRow(point.row))?
8515 .1
8516 .start
8517 .row;
8518
8519 let context_range =
8520 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
8521 Some((
8522 (runnable.buffer_id, row),
8523 RunnableTasks {
8524 templates: tasks,
8525 offset: MultiBufferOffset(runnable.run_range.start),
8526 context_range,
8527 column: point.column,
8528 extra_variables: runnable.extra_captures,
8529 },
8530 ))
8531 })
8532 .collect()
8533 }
8534
8535 fn templates_with_tags(
8536 project: &Model<Project>,
8537 runnable: &mut Runnable,
8538 cx: &WindowContext<'_>,
8539 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
8540 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
8541 let (worktree_id, file) = project
8542 .buffer_for_id(runnable.buffer, cx)
8543 .and_then(|buffer| buffer.read(cx).file())
8544 .map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
8545 .unzip();
8546
8547 (project.task_inventory().clone(), worktree_id, file)
8548 });
8549
8550 let inventory = inventory.read(cx);
8551 let tags = mem::take(&mut runnable.tags);
8552 let mut tags: Vec<_> = tags
8553 .into_iter()
8554 .flat_map(|tag| {
8555 let tag = tag.0.clone();
8556 inventory
8557 .list_tasks(
8558 file.clone(),
8559 Some(runnable.language.clone()),
8560 worktree_id,
8561 cx,
8562 )
8563 .into_iter()
8564 .filter(move |(_, template)| {
8565 template.tags.iter().any(|source_tag| source_tag == &tag)
8566 })
8567 })
8568 .sorted_by_key(|(kind, _)| kind.to_owned())
8569 .collect();
8570 if let Some((leading_tag_source, _)) = tags.first() {
8571 // Strongest source wins; if we have worktree tag binding, prefer that to
8572 // global and language bindings;
8573 // if we have a global binding, prefer that to language binding.
8574 let first_mismatch = tags
8575 .iter()
8576 .position(|(tag_source, _)| tag_source != leading_tag_source);
8577 if let Some(index) = first_mismatch {
8578 tags.truncate(index);
8579 }
8580 }
8581
8582 tags
8583 }
8584
8585 pub fn move_to_enclosing_bracket(
8586 &mut self,
8587 _: &MoveToEnclosingBracket,
8588 cx: &mut ViewContext<Self>,
8589 ) {
8590 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8591 s.move_offsets_with(|snapshot, selection| {
8592 let Some(enclosing_bracket_ranges) =
8593 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
8594 else {
8595 return;
8596 };
8597
8598 let mut best_length = usize::MAX;
8599 let mut best_inside = false;
8600 let mut best_in_bracket_range = false;
8601 let mut best_destination = None;
8602 for (open, close) in enclosing_bracket_ranges {
8603 let close = close.to_inclusive();
8604 let length = close.end() - open.start;
8605 let inside = selection.start >= open.end && selection.end <= *close.start();
8606 let in_bracket_range = open.to_inclusive().contains(&selection.head())
8607 || close.contains(&selection.head());
8608
8609 // If best is next to a bracket and current isn't, skip
8610 if !in_bracket_range && best_in_bracket_range {
8611 continue;
8612 }
8613
8614 // Prefer smaller lengths unless best is inside and current isn't
8615 if length > best_length && (best_inside || !inside) {
8616 continue;
8617 }
8618
8619 best_length = length;
8620 best_inside = inside;
8621 best_in_bracket_range = in_bracket_range;
8622 best_destination = Some(
8623 if close.contains(&selection.start) && close.contains(&selection.end) {
8624 if inside {
8625 open.end
8626 } else {
8627 open.start
8628 }
8629 } else {
8630 if inside {
8631 *close.start()
8632 } else {
8633 *close.end()
8634 }
8635 },
8636 );
8637 }
8638
8639 if let Some(destination) = best_destination {
8640 selection.collapse_to(destination, SelectionGoal::None);
8641 }
8642 })
8643 });
8644 }
8645
8646 pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext<Self>) {
8647 self.end_selection(cx);
8648 self.selection_history.mode = SelectionHistoryMode::Undoing;
8649 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
8650 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
8651 self.select_next_state = entry.select_next_state;
8652 self.select_prev_state = entry.select_prev_state;
8653 self.add_selections_state = entry.add_selections_state;
8654 self.request_autoscroll(Autoscroll::newest(), cx);
8655 }
8656 self.selection_history.mode = SelectionHistoryMode::Normal;
8657 }
8658
8659 pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext<Self>) {
8660 self.end_selection(cx);
8661 self.selection_history.mode = SelectionHistoryMode::Redoing;
8662 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
8663 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
8664 self.select_next_state = entry.select_next_state;
8665 self.select_prev_state = entry.select_prev_state;
8666 self.add_selections_state = entry.add_selections_state;
8667 self.request_autoscroll(Autoscroll::newest(), cx);
8668 }
8669 self.selection_history.mode = SelectionHistoryMode::Normal;
8670 }
8671
8672 pub fn expand_excerpts(&mut self, action: &ExpandExcerpts, cx: &mut ViewContext<Self>) {
8673 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
8674 }
8675
8676 pub fn expand_excerpts_down(
8677 &mut self,
8678 action: &ExpandExcerptsDown,
8679 cx: &mut ViewContext<Self>,
8680 ) {
8681 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
8682 }
8683
8684 pub fn expand_excerpts_up(&mut self, action: &ExpandExcerptsUp, cx: &mut ViewContext<Self>) {
8685 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
8686 }
8687
8688 pub fn expand_excerpts_for_direction(
8689 &mut self,
8690 lines: u32,
8691 direction: ExpandExcerptDirection,
8692 cx: &mut ViewContext<Self>,
8693 ) {
8694 let selections = self.selections.disjoint_anchors();
8695
8696 let lines = if lines == 0 {
8697 EditorSettings::get_global(cx).expand_excerpt_lines
8698 } else {
8699 lines
8700 };
8701
8702 self.buffer.update(cx, |buffer, cx| {
8703 buffer.expand_excerpts(
8704 selections
8705 .into_iter()
8706 .map(|selection| selection.head().excerpt_id)
8707 .dedup(),
8708 lines,
8709 direction,
8710 cx,
8711 )
8712 })
8713 }
8714
8715 pub fn expand_excerpt(
8716 &mut self,
8717 excerpt: ExcerptId,
8718 direction: ExpandExcerptDirection,
8719 cx: &mut ViewContext<Self>,
8720 ) {
8721 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
8722 self.buffer.update(cx, |buffer, cx| {
8723 buffer.expand_excerpts([excerpt], lines, direction, cx)
8724 })
8725 }
8726
8727 fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
8728 self.go_to_diagnostic_impl(Direction::Next, cx)
8729 }
8730
8731 fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
8732 self.go_to_diagnostic_impl(Direction::Prev, cx)
8733 }
8734
8735 pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
8736 let buffer = self.buffer.read(cx).snapshot(cx);
8737 let selection = self.selections.newest::<usize>(cx);
8738
8739 // If there is an active Diagnostic Popover jump to its diagnostic instead.
8740 if direction == Direction::Next {
8741 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
8742 let (group_id, jump_to) = popover.activation_info();
8743 if self.activate_diagnostics(group_id, cx) {
8744 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8745 let mut new_selection = s.newest_anchor().clone();
8746 new_selection.collapse_to(jump_to, SelectionGoal::None);
8747 s.select_anchors(vec![new_selection.clone()]);
8748 });
8749 }
8750 return;
8751 }
8752 }
8753
8754 let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
8755 active_diagnostics
8756 .primary_range
8757 .to_offset(&buffer)
8758 .to_inclusive()
8759 });
8760 let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
8761 if active_primary_range.contains(&selection.head()) {
8762 *active_primary_range.start()
8763 } else {
8764 selection.head()
8765 }
8766 } else {
8767 selection.head()
8768 };
8769 let snapshot = self.snapshot(cx);
8770 loop {
8771 let diagnostics = if direction == Direction::Prev {
8772 buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
8773 } else {
8774 buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
8775 }
8776 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
8777 let group = diagnostics
8778 // relies on diagnostics_in_range to return diagnostics with the same starting range to
8779 // be sorted in a stable way
8780 // skip until we are at current active diagnostic, if it exists
8781 .skip_while(|entry| {
8782 (match direction {
8783 Direction::Prev => entry.range.start >= search_start,
8784 Direction::Next => entry.range.start <= search_start,
8785 }) && self
8786 .active_diagnostics
8787 .as_ref()
8788 .is_some_and(|a| a.group_id != entry.diagnostic.group_id)
8789 })
8790 .find_map(|entry| {
8791 if entry.diagnostic.is_primary
8792 && entry.diagnostic.severity <= DiagnosticSeverity::WARNING
8793 && !entry.range.is_empty()
8794 // if we match with the active diagnostic, skip it
8795 && Some(entry.diagnostic.group_id)
8796 != self.active_diagnostics.as_ref().map(|d| d.group_id)
8797 {
8798 Some((entry.range, entry.diagnostic.group_id))
8799 } else {
8800 None
8801 }
8802 });
8803
8804 if let Some((primary_range, group_id)) = group {
8805 if self.activate_diagnostics(group_id, cx) {
8806 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8807 s.select(vec![Selection {
8808 id: selection.id,
8809 start: primary_range.start,
8810 end: primary_range.start,
8811 reversed: false,
8812 goal: SelectionGoal::None,
8813 }]);
8814 });
8815 }
8816 break;
8817 } else {
8818 // Cycle around to the start of the buffer, potentially moving back to the start of
8819 // the currently active diagnostic.
8820 active_primary_range.take();
8821 if direction == Direction::Prev {
8822 if search_start == buffer.len() {
8823 break;
8824 } else {
8825 search_start = buffer.len();
8826 }
8827 } else if search_start == 0 {
8828 break;
8829 } else {
8830 search_start = 0;
8831 }
8832 }
8833 }
8834 }
8835
8836 fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
8837 let snapshot = self
8838 .display_map
8839 .update(cx, |display_map, cx| display_map.snapshot(cx));
8840 let selection = self.selections.newest::<Point>(cx);
8841
8842 if !self.seek_in_direction(
8843 &snapshot,
8844 selection.head(),
8845 false,
8846 snapshot.buffer_snapshot.git_diff_hunks_in_range(
8847 MultiBufferRow(selection.head().row + 1)..MultiBufferRow::MAX,
8848 ),
8849 cx,
8850 ) {
8851 let wrapped_point = Point::zero();
8852 self.seek_in_direction(
8853 &snapshot,
8854 wrapped_point,
8855 true,
8856 snapshot.buffer_snapshot.git_diff_hunks_in_range(
8857 MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX,
8858 ),
8859 cx,
8860 );
8861 }
8862 }
8863
8864 fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
8865 let snapshot = self
8866 .display_map
8867 .update(cx, |display_map, cx| display_map.snapshot(cx));
8868 let selection = self.selections.newest::<Point>(cx);
8869
8870 if !self.seek_in_direction(
8871 &snapshot,
8872 selection.head(),
8873 false,
8874 snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
8875 MultiBufferRow(0)..MultiBufferRow(selection.head().row),
8876 ),
8877 cx,
8878 ) {
8879 let wrapped_point = snapshot.buffer_snapshot.max_point();
8880 self.seek_in_direction(
8881 &snapshot,
8882 wrapped_point,
8883 true,
8884 snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
8885 MultiBufferRow(0)..MultiBufferRow(wrapped_point.row),
8886 ),
8887 cx,
8888 );
8889 }
8890 }
8891
8892 fn seek_in_direction(
8893 &mut self,
8894 snapshot: &DisplaySnapshot,
8895 initial_point: Point,
8896 is_wrapped: bool,
8897 hunks: impl Iterator<Item = DiffHunk<MultiBufferRow>>,
8898 cx: &mut ViewContext<Editor>,
8899 ) -> bool {
8900 let display_point = initial_point.to_display_point(snapshot);
8901 let mut hunks = hunks
8902 .map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
8903 .filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row()))
8904 .dedup();
8905
8906 if let Some(hunk) = hunks.next() {
8907 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8908 let row = hunk.start_display_row();
8909 let point = DisplayPoint::new(row, 0);
8910 s.select_display_ranges([point..point]);
8911 });
8912
8913 true
8914 } else {
8915 false
8916 }
8917 }
8918
8919 pub fn go_to_definition(
8920 &mut self,
8921 _: &GoToDefinition,
8922 cx: &mut ViewContext<Self>,
8923 ) -> Task<Result<bool>> {
8924 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx)
8925 }
8926
8927 pub fn go_to_implementation(
8928 &mut self,
8929 _: &GoToImplementation,
8930 cx: &mut ViewContext<Self>,
8931 ) -> Task<Result<bool>> {
8932 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx)
8933 }
8934
8935 pub fn go_to_implementation_split(
8936 &mut self,
8937 _: &GoToImplementationSplit,
8938 cx: &mut ViewContext<Self>,
8939 ) -> Task<Result<bool>> {
8940 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx)
8941 }
8942
8943 pub fn go_to_type_definition(
8944 &mut self,
8945 _: &GoToTypeDefinition,
8946 cx: &mut ViewContext<Self>,
8947 ) -> Task<Result<bool>> {
8948 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx)
8949 }
8950
8951 pub fn go_to_definition_split(
8952 &mut self,
8953 _: &GoToDefinitionSplit,
8954 cx: &mut ViewContext<Self>,
8955 ) -> Task<Result<bool>> {
8956 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx)
8957 }
8958
8959 pub fn go_to_type_definition_split(
8960 &mut self,
8961 _: &GoToTypeDefinitionSplit,
8962 cx: &mut ViewContext<Self>,
8963 ) -> Task<Result<bool>> {
8964 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx)
8965 }
8966
8967 fn go_to_definition_of_kind(
8968 &mut self,
8969 kind: GotoDefinitionKind,
8970 split: bool,
8971 cx: &mut ViewContext<Self>,
8972 ) -> Task<Result<bool>> {
8973 let Some(workspace) = self.workspace() else {
8974 return Task::ready(Ok(false));
8975 };
8976 let buffer = self.buffer.read(cx);
8977 let head = self.selections.newest::<usize>(cx).head();
8978 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
8979 text_anchor
8980 } else {
8981 return Task::ready(Ok(false));
8982 };
8983
8984 let project = workspace.read(cx).project().clone();
8985 let definitions = project.update(cx, |project, cx| match kind {
8986 GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
8987 GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
8988 GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
8989 });
8990
8991 cx.spawn(|editor, mut cx| async move {
8992 let definitions = definitions.await?;
8993 let navigated = editor
8994 .update(&mut cx, |editor, cx| {
8995 editor.navigate_to_hover_links(
8996 Some(kind),
8997 definitions
8998 .into_iter()
8999 .filter(|location| {
9000 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
9001 })
9002 .map(HoverLink::Text)
9003 .collect::<Vec<_>>(),
9004 split,
9005 cx,
9006 )
9007 })?
9008 .await?;
9009 anyhow::Ok(navigated)
9010 })
9011 }
9012
9013 pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
9014 let position = self.selections.newest_anchor().head();
9015 let Some((buffer, buffer_position)) =
9016 self.buffer.read(cx).text_anchor_for_position(position, cx)
9017 else {
9018 return;
9019 };
9020
9021 cx.spawn(|editor, mut cx| async move {
9022 if let Some((_, url)) = find_url(&buffer, buffer_position, cx.clone()) {
9023 editor.update(&mut cx, |_, cx| {
9024 cx.open_url(&url);
9025 })
9026 } else {
9027 Ok(())
9028 }
9029 })
9030 .detach();
9031 }
9032
9033 pub(crate) fn navigate_to_hover_links(
9034 &mut self,
9035 kind: Option<GotoDefinitionKind>,
9036 mut definitions: Vec<HoverLink>,
9037 split: bool,
9038 cx: &mut ViewContext<Editor>,
9039 ) -> Task<Result<bool>> {
9040 // If there is one definition, just open it directly
9041 if definitions.len() == 1 {
9042 let definition = definitions.pop().unwrap();
9043 let target_task = match definition {
9044 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
9045 HoverLink::InlayHint(lsp_location, server_id) => {
9046 self.compute_target_location(lsp_location, server_id, cx)
9047 }
9048 HoverLink::Url(url) => {
9049 cx.open_url(&url);
9050 Task::ready(Ok(None))
9051 }
9052 };
9053 cx.spawn(|editor, mut cx| async move {
9054 let target = target_task.await.context("target resolution task")?;
9055 if let Some(target) = target {
9056 editor.update(&mut cx, |editor, cx| {
9057 let Some(workspace) = editor.workspace() else {
9058 return false;
9059 };
9060 let pane = workspace.read(cx).active_pane().clone();
9061
9062 let range = target.range.to_offset(target.buffer.read(cx));
9063 let range = editor.range_for_match(&range);
9064
9065 /// If select range has more than one line, we
9066 /// just point the cursor to range.start.
9067 fn check_multiline_range(
9068 buffer: &Buffer,
9069 range: Range<usize>,
9070 ) -> Range<usize> {
9071 if buffer.offset_to_point(range.start).row
9072 == buffer.offset_to_point(range.end).row
9073 {
9074 range
9075 } else {
9076 range.start..range.start
9077 }
9078 }
9079
9080 if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
9081 let buffer = target.buffer.read(cx);
9082 let range = check_multiline_range(buffer, range);
9083 editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
9084 s.select_ranges([range]);
9085 });
9086 } else {
9087 cx.window_context().defer(move |cx| {
9088 let target_editor: View<Self> =
9089 workspace.update(cx, |workspace, cx| {
9090 let pane = if split {
9091 workspace.adjacent_pane(cx)
9092 } else {
9093 workspace.active_pane().clone()
9094 };
9095
9096 workspace.open_project_item(pane, target.buffer.clone(), cx)
9097 });
9098 target_editor.update(cx, |target_editor, cx| {
9099 // When selecting a definition in a different buffer, disable the nav history
9100 // to avoid creating a history entry at the previous cursor location.
9101 pane.update(cx, |pane, _| pane.disable_history());
9102 let buffer = target.buffer.read(cx);
9103 let range = check_multiline_range(buffer, range);
9104 target_editor.change_selections(
9105 Some(Autoscroll::focused()),
9106 cx,
9107 |s| {
9108 s.select_ranges([range]);
9109 },
9110 );
9111 pane.update(cx, |pane, _| pane.enable_history());
9112 });
9113 });
9114 }
9115 true
9116 })
9117 } else {
9118 Ok(false)
9119 }
9120 })
9121 } else if !definitions.is_empty() {
9122 let replica_id = self.replica_id(cx);
9123 cx.spawn(|editor, mut cx| async move {
9124 let (title, location_tasks, workspace) = editor
9125 .update(&mut cx, |editor, cx| {
9126 let tab_kind = match kind {
9127 Some(GotoDefinitionKind::Implementation) => "Implementations",
9128 _ => "Definitions",
9129 };
9130 let title = definitions
9131 .iter()
9132 .find_map(|definition| match definition {
9133 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
9134 let buffer = origin.buffer.read(cx);
9135 format!(
9136 "{} for {}",
9137 tab_kind,
9138 buffer
9139 .text_for_range(origin.range.clone())
9140 .collect::<String>()
9141 )
9142 }),
9143 HoverLink::InlayHint(_, _) => None,
9144 HoverLink::Url(_) => None,
9145 })
9146 .unwrap_or(tab_kind.to_string());
9147 let location_tasks = definitions
9148 .into_iter()
9149 .map(|definition| match definition {
9150 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
9151 HoverLink::InlayHint(lsp_location, server_id) => {
9152 editor.compute_target_location(lsp_location, server_id, cx)
9153 }
9154 HoverLink::Url(_) => Task::ready(Ok(None)),
9155 })
9156 .collect::<Vec<_>>();
9157 (title, location_tasks, editor.workspace().clone())
9158 })
9159 .context("location tasks preparation")?;
9160
9161 let locations = futures::future::join_all(location_tasks)
9162 .await
9163 .into_iter()
9164 .filter_map(|location| location.transpose())
9165 .collect::<Result<_>>()
9166 .context("location tasks")?;
9167
9168 let Some(workspace) = workspace else {
9169 return Ok(false);
9170 };
9171 let opened = workspace
9172 .update(&mut cx, |workspace, cx| {
9173 Self::open_locations_in_multibuffer(
9174 workspace, locations, replica_id, title, split, cx,
9175 )
9176 })
9177 .ok();
9178
9179 anyhow::Ok(opened.is_some())
9180 })
9181 } else {
9182 Task::ready(Ok(false))
9183 }
9184 }
9185
9186 fn compute_target_location(
9187 &self,
9188 lsp_location: lsp::Location,
9189 server_id: LanguageServerId,
9190 cx: &mut ViewContext<Editor>,
9191 ) -> Task<anyhow::Result<Option<Location>>> {
9192 let Some(project) = self.project.clone() else {
9193 return Task::Ready(Some(Ok(None)));
9194 };
9195
9196 cx.spawn(move |editor, mut cx| async move {
9197 let location_task = editor.update(&mut cx, |editor, cx| {
9198 project.update(cx, |project, cx| {
9199 let language_server_name =
9200 editor.buffer.read(cx).as_singleton().and_then(|buffer| {
9201 project
9202 .language_server_for_buffer(buffer.read(cx), server_id, cx)
9203 .map(|(lsp_adapter, _)| lsp_adapter.name.clone())
9204 });
9205 language_server_name.map(|language_server_name| {
9206 project.open_local_buffer_via_lsp(
9207 lsp_location.uri.clone(),
9208 server_id,
9209 language_server_name,
9210 cx,
9211 )
9212 })
9213 })
9214 })?;
9215 let location = match location_task {
9216 Some(task) => Some({
9217 let target_buffer_handle = task.await.context("open local buffer")?;
9218 let range = target_buffer_handle.update(&mut cx, |target_buffer, _| {
9219 let target_start = target_buffer
9220 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
9221 let target_end = target_buffer
9222 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
9223 target_buffer.anchor_after(target_start)
9224 ..target_buffer.anchor_before(target_end)
9225 })?;
9226 Location {
9227 buffer: target_buffer_handle,
9228 range,
9229 }
9230 }),
9231 None => None,
9232 };
9233 Ok(location)
9234 })
9235 }
9236
9237 pub fn find_all_references(
9238 &mut self,
9239 _: &FindAllReferences,
9240 cx: &mut ViewContext<Self>,
9241 ) -> Option<Task<Result<()>>> {
9242 let multi_buffer = self.buffer.read(cx);
9243 let selection = self.selections.newest::<usize>(cx);
9244 let head = selection.head();
9245
9246 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
9247 let head_anchor = multi_buffer_snapshot.anchor_at(
9248 head,
9249 if head < selection.tail() {
9250 Bias::Right
9251 } else {
9252 Bias::Left
9253 },
9254 );
9255
9256 match self
9257 .find_all_references_task_sources
9258 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
9259 {
9260 Ok(_) => {
9261 log::info!(
9262 "Ignoring repeated FindAllReferences invocation with the position of already running task"
9263 );
9264 return None;
9265 }
9266 Err(i) => {
9267 self.find_all_references_task_sources.insert(i, head_anchor);
9268 }
9269 }
9270
9271 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
9272 let replica_id = self.replica_id(cx);
9273 let workspace = self.workspace()?;
9274 let project = workspace.read(cx).project().clone();
9275 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
9276 Some(cx.spawn(|editor, mut cx| async move {
9277 let _cleanup = defer({
9278 let mut cx = cx.clone();
9279 move || {
9280 let _ = editor.update(&mut cx, |editor, _| {
9281 if let Ok(i) =
9282 editor
9283 .find_all_references_task_sources
9284 .binary_search_by(|anchor| {
9285 anchor.cmp(&head_anchor, &multi_buffer_snapshot)
9286 })
9287 {
9288 editor.find_all_references_task_sources.remove(i);
9289 }
9290 });
9291 }
9292 });
9293
9294 let locations = references.await?;
9295 if locations.is_empty() {
9296 return anyhow::Ok(());
9297 }
9298
9299 workspace.update(&mut cx, |workspace, cx| {
9300 let title = locations
9301 .first()
9302 .as_ref()
9303 .map(|location| {
9304 let buffer = location.buffer.read(cx);
9305 format!(
9306 "References to `{}`",
9307 buffer
9308 .text_for_range(location.range.clone())
9309 .collect::<String>()
9310 )
9311 })
9312 .unwrap();
9313 Self::open_locations_in_multibuffer(
9314 workspace, locations, replica_id, title, false, cx,
9315 );
9316 })
9317 }))
9318 }
9319
9320 /// Opens a multibuffer with the given project locations in it
9321 pub fn open_locations_in_multibuffer(
9322 workspace: &mut Workspace,
9323 mut locations: Vec<Location>,
9324 replica_id: ReplicaId,
9325 title: String,
9326 split: bool,
9327 cx: &mut ViewContext<Workspace>,
9328 ) {
9329 // If there are multiple definitions, open them in a multibuffer
9330 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
9331 let mut locations = locations.into_iter().peekable();
9332 let mut ranges_to_highlight = Vec::new();
9333 let capability = workspace.project().read(cx).capability();
9334
9335 let excerpt_buffer = cx.new_model(|cx| {
9336 let mut multibuffer = MultiBuffer::new(replica_id, capability);
9337 while let Some(location) = locations.next() {
9338 let buffer = location.buffer.read(cx);
9339 let mut ranges_for_buffer = Vec::new();
9340 let range = location.range.to_offset(buffer);
9341 ranges_for_buffer.push(range.clone());
9342
9343 while let Some(next_location) = locations.peek() {
9344 if next_location.buffer == location.buffer {
9345 ranges_for_buffer.push(next_location.range.to_offset(buffer));
9346 locations.next();
9347 } else {
9348 break;
9349 }
9350 }
9351
9352 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
9353 ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
9354 location.buffer.clone(),
9355 ranges_for_buffer,
9356 DEFAULT_MULTIBUFFER_CONTEXT,
9357 cx,
9358 ))
9359 }
9360
9361 multibuffer.with_title(title)
9362 });
9363
9364 let editor = cx.new_view(|cx| {
9365 Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), true, cx)
9366 });
9367 editor.update(cx, |editor, cx| {
9368 if let Some(first_range) = ranges_to_highlight.first() {
9369 editor.change_selections(None, cx, |selections| {
9370 selections.clear_disjoint();
9371 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
9372 });
9373 }
9374 editor.highlight_background::<Self>(
9375 &ranges_to_highlight,
9376 |theme| theme.editor_highlighted_line_background,
9377 cx,
9378 );
9379 });
9380
9381 let item = Box::new(editor);
9382 let item_id = item.item_id();
9383
9384 if split {
9385 workspace.split_item(SplitDirection::Right, item.clone(), cx);
9386 } else {
9387 let destination_index = workspace.active_pane().update(cx, |pane, cx| {
9388 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
9389 pane.close_current_preview_item(cx)
9390 } else {
9391 None
9392 }
9393 });
9394 workspace.add_item_to_active_pane(item.clone(), destination_index, cx);
9395 }
9396 workspace.active_pane().update(cx, |pane, cx| {
9397 pane.set_preview_item_id(Some(item_id), cx);
9398 });
9399 }
9400
9401 pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
9402 use language::ToOffset as _;
9403
9404 let project = self.project.clone()?;
9405 let selection = self.selections.newest_anchor().clone();
9406 let (cursor_buffer, cursor_buffer_position) = self
9407 .buffer
9408 .read(cx)
9409 .text_anchor_for_position(selection.head(), cx)?;
9410 let (tail_buffer, cursor_buffer_position_end) = self
9411 .buffer
9412 .read(cx)
9413 .text_anchor_for_position(selection.tail(), cx)?;
9414 if tail_buffer != cursor_buffer {
9415 return None;
9416 }
9417
9418 let snapshot = cursor_buffer.read(cx).snapshot();
9419 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
9420 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
9421 let prepare_rename = project.update(cx, |project, cx| {
9422 project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
9423 });
9424 drop(snapshot);
9425
9426 Some(cx.spawn(|this, mut cx| async move {
9427 let rename_range = if let Some(range) = prepare_rename.await? {
9428 Some(range)
9429 } else {
9430 this.update(&mut cx, |this, cx| {
9431 let buffer = this.buffer.read(cx).snapshot(cx);
9432 let mut buffer_highlights = this
9433 .document_highlights_for_position(selection.head(), &buffer)
9434 .filter(|highlight| {
9435 highlight.start.excerpt_id == selection.head().excerpt_id
9436 && highlight.end.excerpt_id == selection.head().excerpt_id
9437 });
9438 buffer_highlights
9439 .next()
9440 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
9441 })?
9442 };
9443 if let Some(rename_range) = rename_range {
9444 this.update(&mut cx, |this, cx| {
9445 let snapshot = cursor_buffer.read(cx).snapshot();
9446 let rename_buffer_range = rename_range.to_offset(&snapshot);
9447 let cursor_offset_in_rename_range =
9448 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
9449 let cursor_offset_in_rename_range_end =
9450 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
9451
9452 this.take_rename(false, cx);
9453 let buffer = this.buffer.read(cx).read(cx);
9454 let cursor_offset = selection.head().to_offset(&buffer);
9455 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
9456 let rename_end = rename_start + rename_buffer_range.len();
9457 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
9458 let mut old_highlight_id = None;
9459 let old_name: Arc<str> = buffer
9460 .chunks(rename_start..rename_end, true)
9461 .map(|chunk| {
9462 if old_highlight_id.is_none() {
9463 old_highlight_id = chunk.syntax_highlight_id;
9464 }
9465 chunk.text
9466 })
9467 .collect::<String>()
9468 .into();
9469
9470 drop(buffer);
9471
9472 // Position the selection in the rename editor so that it matches the current selection.
9473 this.show_local_selections = false;
9474 let rename_editor = cx.new_view(|cx| {
9475 let mut editor = Editor::single_line(cx);
9476 editor.buffer.update(cx, |buffer, cx| {
9477 buffer.edit([(0..0, old_name.clone())], None, cx)
9478 });
9479 let rename_selection_range = match cursor_offset_in_rename_range
9480 .cmp(&cursor_offset_in_rename_range_end)
9481 {
9482 Ordering::Equal => {
9483 editor.select_all(&SelectAll, cx);
9484 return editor;
9485 }
9486 Ordering::Less => {
9487 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
9488 }
9489 Ordering::Greater => {
9490 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
9491 }
9492 };
9493 if rename_selection_range.end > old_name.len() {
9494 editor.select_all(&SelectAll, cx);
9495 } else {
9496 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
9497 s.select_ranges([rename_selection_range]);
9498 });
9499 }
9500 editor
9501 });
9502 cx.subscribe(&rename_editor, |_, _, e, cx| match e {
9503 EditorEvent::Focused => cx.emit(EditorEvent::FocusedIn),
9504 _ => {}
9505 })
9506 .detach();
9507
9508 let write_highlights =
9509 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
9510 let read_highlights =
9511 this.clear_background_highlights::<DocumentHighlightRead>(cx);
9512 let ranges = write_highlights
9513 .iter()
9514 .flat_map(|(_, ranges)| ranges.iter())
9515 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
9516 .cloned()
9517 .collect();
9518
9519 this.highlight_text::<Rename>(
9520 ranges,
9521 HighlightStyle {
9522 fade_out: Some(0.6),
9523 ..Default::default()
9524 },
9525 cx,
9526 );
9527 let rename_focus_handle = rename_editor.focus_handle(cx);
9528 cx.focus(&rename_focus_handle);
9529 let block_id = this.insert_blocks(
9530 [BlockProperties {
9531 style: BlockStyle::Flex,
9532 position: range.start,
9533 height: 1,
9534 render: Box::new({
9535 let rename_editor = rename_editor.clone();
9536 move |cx: &mut BlockContext| {
9537 let mut text_style = cx.editor_style.text.clone();
9538 if let Some(highlight_style) = old_highlight_id
9539 .and_then(|h| h.style(&cx.editor_style.syntax))
9540 {
9541 text_style = text_style.highlight(highlight_style);
9542 }
9543 div()
9544 .pl(cx.anchor_x)
9545 .child(EditorElement::new(
9546 &rename_editor,
9547 EditorStyle {
9548 background: cx.theme().system().transparent,
9549 local_player: cx.editor_style.local_player,
9550 text: text_style,
9551 scrollbar_width: cx.editor_style.scrollbar_width,
9552 syntax: cx.editor_style.syntax.clone(),
9553 status: cx.editor_style.status.clone(),
9554 inlay_hints_style: HighlightStyle {
9555 color: Some(cx.theme().status().hint),
9556 font_weight: Some(FontWeight::BOLD),
9557 ..HighlightStyle::default()
9558 },
9559 suggestions_style: HighlightStyle {
9560 color: Some(cx.theme().status().predictive),
9561 ..HighlightStyle::default()
9562 },
9563 },
9564 ))
9565 .into_any_element()
9566 }
9567 }),
9568 disposition: BlockDisposition::Below,
9569 }],
9570 Some(Autoscroll::fit()),
9571 cx,
9572 )[0];
9573 this.pending_rename = Some(RenameState {
9574 range,
9575 old_name,
9576 editor: rename_editor,
9577 block_id,
9578 });
9579 })?;
9580 }
9581
9582 Ok(())
9583 }))
9584 }
9585
9586 pub fn confirm_rename(
9587 &mut self,
9588 _: &ConfirmRename,
9589 cx: &mut ViewContext<Self>,
9590 ) -> Option<Task<Result<()>>> {
9591 let rename = self.take_rename(false, cx)?;
9592 let workspace = self.workspace()?;
9593 let (start_buffer, start) = self
9594 .buffer
9595 .read(cx)
9596 .text_anchor_for_position(rename.range.start, cx)?;
9597 let (end_buffer, end) = self
9598 .buffer
9599 .read(cx)
9600 .text_anchor_for_position(rename.range.end, cx)?;
9601 if start_buffer != end_buffer {
9602 return None;
9603 }
9604
9605 let buffer = start_buffer;
9606 let range = start..end;
9607 let old_name = rename.old_name;
9608 let new_name = rename.editor.read(cx).text(cx);
9609
9610 let rename = workspace
9611 .read(cx)
9612 .project()
9613 .clone()
9614 .update(cx, |project, cx| {
9615 project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
9616 });
9617 let workspace = workspace.downgrade();
9618
9619 Some(cx.spawn(|editor, mut cx| async move {
9620 let project_transaction = rename.await?;
9621 Self::open_project_transaction(
9622 &editor,
9623 workspace,
9624 project_transaction,
9625 format!("Rename: {} → {}", old_name, new_name),
9626 cx.clone(),
9627 )
9628 .await?;
9629
9630 editor.update(&mut cx, |editor, cx| {
9631 editor.refresh_document_highlights(cx);
9632 })?;
9633 Ok(())
9634 }))
9635 }
9636
9637 fn take_rename(
9638 &mut self,
9639 moving_cursor: bool,
9640 cx: &mut ViewContext<Self>,
9641 ) -> Option<RenameState> {
9642 let rename = self.pending_rename.take()?;
9643 if rename.editor.focus_handle(cx).is_focused(cx) {
9644 cx.focus(&self.focus_handle);
9645 }
9646
9647 self.remove_blocks(
9648 [rename.block_id].into_iter().collect(),
9649 Some(Autoscroll::fit()),
9650 cx,
9651 );
9652 self.clear_highlights::<Rename>(cx);
9653 self.show_local_selections = true;
9654
9655 if moving_cursor {
9656 let rename_editor = rename.editor.read(cx);
9657 let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
9658
9659 // Update the selection to match the position of the selection inside
9660 // the rename editor.
9661 let snapshot = self.buffer.read(cx).read(cx);
9662 let rename_range = rename.range.to_offset(&snapshot);
9663 let cursor_in_editor = snapshot
9664 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
9665 .min(rename_range.end);
9666 drop(snapshot);
9667
9668 self.change_selections(None, cx, |s| {
9669 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
9670 });
9671 } else {
9672 self.refresh_document_highlights(cx);
9673 }
9674
9675 Some(rename)
9676 }
9677
9678 pub fn pending_rename(&self) -> Option<&RenameState> {
9679 self.pending_rename.as_ref()
9680 }
9681
9682 fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
9683 let project = match &self.project {
9684 Some(project) => project.clone(),
9685 None => return None,
9686 };
9687
9688 Some(self.perform_format(project, FormatTrigger::Manual, cx))
9689 }
9690
9691 fn perform_format(
9692 &mut self,
9693 project: Model<Project>,
9694 trigger: FormatTrigger,
9695 cx: &mut ViewContext<Self>,
9696 ) -> Task<Result<()>> {
9697 let buffer = self.buffer().clone();
9698 let mut buffers = buffer.read(cx).all_buffers();
9699 if trigger == FormatTrigger::Save {
9700 buffers.retain(|buffer| buffer.read(cx).is_dirty());
9701 }
9702
9703 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
9704 let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
9705
9706 cx.spawn(|_, mut cx| async move {
9707 let transaction = futures::select_biased! {
9708 () = timeout => {
9709 log::warn!("timed out waiting for formatting");
9710 None
9711 }
9712 transaction = format.log_err().fuse() => transaction,
9713 };
9714
9715 buffer
9716 .update(&mut cx, |buffer, cx| {
9717 if let Some(transaction) = transaction {
9718 if !buffer.is_singleton() {
9719 buffer.push_transaction(&transaction.0, cx);
9720 }
9721 }
9722
9723 cx.notify();
9724 })
9725 .ok();
9726
9727 Ok(())
9728 })
9729 }
9730
9731 fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
9732 if let Some(project) = self.project.clone() {
9733 self.buffer.update(cx, |multi_buffer, cx| {
9734 project.update(cx, |project, cx| {
9735 project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
9736 });
9737 })
9738 }
9739 }
9740
9741 fn cancel_language_server_work(
9742 &mut self,
9743 _: &CancelLanguageServerWork,
9744 cx: &mut ViewContext<Self>,
9745 ) {
9746 if let Some(project) = self.project.clone() {
9747 self.buffer.update(cx, |multi_buffer, cx| {
9748 project.update(cx, |project, cx| {
9749 project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
9750 });
9751 })
9752 }
9753 }
9754
9755 fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
9756 cx.show_character_palette();
9757 }
9758
9759 fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
9760 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
9761 let buffer = self.buffer.read(cx).snapshot(cx);
9762 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
9763 let is_valid = buffer
9764 .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
9765 .any(|entry| {
9766 entry.diagnostic.is_primary
9767 && !entry.range.is_empty()
9768 && entry.range.start == primary_range_start
9769 && entry.diagnostic.message == active_diagnostics.primary_message
9770 });
9771
9772 if is_valid != active_diagnostics.is_valid {
9773 active_diagnostics.is_valid = is_valid;
9774 let mut new_styles = HashMap::default();
9775 for (block_id, diagnostic) in &active_diagnostics.blocks {
9776 new_styles.insert(
9777 *block_id,
9778 (
9779 None,
9780 diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid),
9781 ),
9782 );
9783 }
9784 self.display_map.update(cx, |display_map, cx| {
9785 display_map.replace_blocks(new_styles, cx)
9786 });
9787 }
9788 }
9789 }
9790
9791 fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
9792 self.dismiss_diagnostics(cx);
9793 let snapshot = self.snapshot(cx);
9794 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
9795 let buffer = self.buffer.read(cx).snapshot(cx);
9796
9797 let mut primary_range = None;
9798 let mut primary_message = None;
9799 let mut group_end = Point::zero();
9800 let diagnostic_group = buffer
9801 .diagnostic_group::<MultiBufferPoint>(group_id)
9802 .filter_map(|entry| {
9803 if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
9804 && (entry.range.start.row == entry.range.end.row
9805 || snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
9806 {
9807 return None;
9808 }
9809 if entry.range.end > group_end {
9810 group_end = entry.range.end;
9811 }
9812 if entry.diagnostic.is_primary {
9813 primary_range = Some(entry.range.clone());
9814 primary_message = Some(entry.diagnostic.message.clone());
9815 }
9816 Some(entry)
9817 })
9818 .collect::<Vec<_>>();
9819 let primary_range = primary_range?;
9820 let primary_message = primary_message?;
9821 let primary_range =
9822 buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
9823
9824 let blocks = display_map
9825 .insert_blocks(
9826 diagnostic_group.iter().map(|entry| {
9827 let diagnostic = entry.diagnostic.clone();
9828 let message_height = diagnostic.message.matches('\n').count() as u8 + 1;
9829 BlockProperties {
9830 style: BlockStyle::Fixed,
9831 position: buffer.anchor_after(entry.range.start),
9832 height: message_height,
9833 render: diagnostic_block_renderer(diagnostic, None, true, true),
9834 disposition: BlockDisposition::Below,
9835 }
9836 }),
9837 cx,
9838 )
9839 .into_iter()
9840 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
9841 .collect();
9842
9843 Some(ActiveDiagnosticGroup {
9844 primary_range,
9845 primary_message,
9846 group_id,
9847 blocks,
9848 is_valid: true,
9849 })
9850 });
9851 self.active_diagnostics.is_some()
9852 }
9853
9854 fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
9855 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
9856 self.display_map.update(cx, |display_map, cx| {
9857 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
9858 });
9859 cx.notify();
9860 }
9861 }
9862
9863 pub fn set_selections_from_remote(
9864 &mut self,
9865 selections: Vec<Selection<Anchor>>,
9866 pending_selection: Option<Selection<Anchor>>,
9867 cx: &mut ViewContext<Self>,
9868 ) {
9869 let old_cursor_position = self.selections.newest_anchor().head();
9870 self.selections.change_with(cx, |s| {
9871 s.select_anchors(selections);
9872 if let Some(pending_selection) = pending_selection {
9873 s.set_pending(pending_selection, SelectMode::Character);
9874 } else {
9875 s.clear_pending();
9876 }
9877 });
9878 self.selections_did_change(false, &old_cursor_position, true, cx);
9879 }
9880
9881 fn push_to_selection_history(&mut self) {
9882 self.selection_history.push(SelectionHistoryEntry {
9883 selections: self.selections.disjoint_anchors(),
9884 select_next_state: self.select_next_state.clone(),
9885 select_prev_state: self.select_prev_state.clone(),
9886 add_selections_state: self.add_selections_state.clone(),
9887 });
9888 }
9889
9890 pub fn transact(
9891 &mut self,
9892 cx: &mut ViewContext<Self>,
9893 update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
9894 ) -> Option<TransactionId> {
9895 self.start_transaction_at(Instant::now(), cx);
9896 update(self, cx);
9897 self.end_transaction_at(Instant::now(), cx)
9898 }
9899
9900 fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
9901 self.end_selection(cx);
9902 if let Some(tx_id) = self
9903 .buffer
9904 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
9905 {
9906 self.selection_history
9907 .insert_transaction(tx_id, self.selections.disjoint_anchors());
9908 cx.emit(EditorEvent::TransactionBegun {
9909 transaction_id: tx_id,
9910 })
9911 }
9912 }
9913
9914 fn end_transaction_at(
9915 &mut self,
9916 now: Instant,
9917 cx: &mut ViewContext<Self>,
9918 ) -> Option<TransactionId> {
9919 if let Some(transaction_id) = self
9920 .buffer
9921 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
9922 {
9923 if let Some((_, end_selections)) =
9924 self.selection_history.transaction_mut(transaction_id)
9925 {
9926 *end_selections = Some(self.selections.disjoint_anchors());
9927 } else {
9928 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
9929 }
9930
9931 cx.emit(EditorEvent::Edited { transaction_id });
9932 Some(transaction_id)
9933 } else {
9934 None
9935 }
9936 }
9937
9938 pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
9939 let mut fold_ranges = Vec::new();
9940
9941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9942
9943 let selections = self.selections.all_adjusted(cx);
9944 for selection in selections {
9945 let range = selection.range().sorted();
9946 let buffer_start_row = range.start.row;
9947
9948 for row in (0..=range.end.row).rev() {
9949 if let Some((foldable_range, fold_text)) =
9950 display_map.foldable_range(MultiBufferRow(row))
9951 {
9952 if foldable_range.end.row >= buffer_start_row {
9953 fold_ranges.push((foldable_range, fold_text));
9954 if row <= range.start.row {
9955 break;
9956 }
9957 }
9958 }
9959 }
9960 }
9961
9962 self.fold_ranges(fold_ranges, true, cx);
9963 }
9964
9965 pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
9966 let buffer_row = fold_at.buffer_row;
9967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9968
9969 if let Some((fold_range, placeholder)) = display_map.foldable_range(buffer_row) {
9970 let autoscroll = self
9971 .selections
9972 .all::<Point>(cx)
9973 .iter()
9974 .any(|selection| fold_range.overlaps(&selection.range()));
9975
9976 self.fold_ranges([(fold_range, placeholder)], autoscroll, cx);
9977 }
9978 }
9979
9980 pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
9981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9982 let buffer = &display_map.buffer_snapshot;
9983 let selections = self.selections.all::<Point>(cx);
9984 let ranges = selections
9985 .iter()
9986 .map(|s| {
9987 let range = s.display_range(&display_map).sorted();
9988 let mut start = range.start.to_point(&display_map);
9989 let mut end = range.end.to_point(&display_map);
9990 start.column = 0;
9991 end.column = buffer.line_len(MultiBufferRow(end.row));
9992 start..end
9993 })
9994 .collect::<Vec<_>>();
9995
9996 self.unfold_ranges(ranges, true, true, cx);
9997 }
9998
9999 pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
10000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10001
10002 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
10003 ..Point::new(
10004 unfold_at.buffer_row.0,
10005 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
10006 );
10007
10008 let autoscroll = self
10009 .selections
10010 .all::<Point>(cx)
10011 .iter()
10012 .any(|selection| selection.range().overlaps(&intersection_range));
10013
10014 self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
10015 }
10016
10017 pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
10018 let selections = self.selections.all::<Point>(cx);
10019 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10020 let line_mode = self.selections.line_mode;
10021 let ranges = selections.into_iter().map(|s| {
10022 if line_mode {
10023 let start = Point::new(s.start.row, 0);
10024 let end = Point::new(
10025 s.end.row,
10026 display_map
10027 .buffer_snapshot
10028 .line_len(MultiBufferRow(s.end.row)),
10029 );
10030 (start..end, display_map.fold_placeholder.clone())
10031 } else {
10032 (s.start..s.end, display_map.fold_placeholder.clone())
10033 }
10034 });
10035 self.fold_ranges(ranges, true, cx);
10036 }
10037
10038 pub fn fold_ranges<T: ToOffset + Clone>(
10039 &mut self,
10040 ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
10041 auto_scroll: bool,
10042 cx: &mut ViewContext<Self>,
10043 ) {
10044 let mut fold_ranges = Vec::new();
10045 let mut buffers_affected = HashMap::default();
10046 let multi_buffer = self.buffer().read(cx);
10047 for (fold_range, fold_text) in ranges {
10048 if let Some((_, buffer, _)) =
10049 multi_buffer.excerpt_containing(fold_range.start.clone(), cx)
10050 {
10051 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
10052 };
10053 fold_ranges.push((fold_range, fold_text));
10054 }
10055
10056 let mut ranges = fold_ranges.into_iter().peekable();
10057 if ranges.peek().is_some() {
10058 self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
10059
10060 if auto_scroll {
10061 self.request_autoscroll(Autoscroll::fit(), cx);
10062 }
10063
10064 for buffer in buffers_affected.into_values() {
10065 self.sync_expanded_diff_hunks(buffer, cx);
10066 }
10067
10068 cx.notify();
10069
10070 if let Some(active_diagnostics) = self.active_diagnostics.take() {
10071 // Clear diagnostics block when folding a range that contains it.
10072 let snapshot = self.snapshot(cx);
10073 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
10074 drop(snapshot);
10075 self.active_diagnostics = Some(active_diagnostics);
10076 self.dismiss_diagnostics(cx);
10077 } else {
10078 self.active_diagnostics = Some(active_diagnostics);
10079 }
10080 }
10081
10082 self.scrollbar_marker_state.dirty = true;
10083 }
10084 }
10085
10086 pub fn unfold_ranges<T: ToOffset + Clone>(
10087 &mut self,
10088 ranges: impl IntoIterator<Item = Range<T>>,
10089 inclusive: bool,
10090 auto_scroll: bool,
10091 cx: &mut ViewContext<Self>,
10092 ) {
10093 let mut unfold_ranges = Vec::new();
10094 let mut buffers_affected = HashMap::default();
10095 let multi_buffer = self.buffer().read(cx);
10096 for range in ranges {
10097 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
10098 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
10099 };
10100 unfold_ranges.push(range);
10101 }
10102
10103 let mut ranges = unfold_ranges.into_iter().peekable();
10104 if ranges.peek().is_some() {
10105 self.display_map
10106 .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
10107 if auto_scroll {
10108 self.request_autoscroll(Autoscroll::fit(), cx);
10109 }
10110
10111 for buffer in buffers_affected.into_values() {
10112 self.sync_expanded_diff_hunks(buffer, cx);
10113 }
10114
10115 cx.notify();
10116 self.scrollbar_marker_state.dirty = true;
10117 self.active_indent_guides_state.dirty = true;
10118 }
10119 }
10120
10121 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
10122 if hovered != self.gutter_hovered {
10123 self.gutter_hovered = hovered;
10124 cx.notify();
10125 }
10126 }
10127
10128 pub fn insert_blocks(
10129 &mut self,
10130 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
10131 autoscroll: Option<Autoscroll>,
10132 cx: &mut ViewContext<Self>,
10133 ) -> Vec<BlockId> {
10134 let blocks = self
10135 .display_map
10136 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
10137 if let Some(autoscroll) = autoscroll {
10138 self.request_autoscroll(autoscroll, cx);
10139 }
10140 blocks
10141 }
10142
10143 pub fn replace_blocks(
10144 &mut self,
10145 blocks: HashMap<BlockId, (Option<u8>, RenderBlock)>,
10146 autoscroll: Option<Autoscroll>,
10147 cx: &mut ViewContext<Self>,
10148 ) {
10149 self.display_map
10150 .update(cx, |display_map, cx| display_map.replace_blocks(blocks, cx));
10151 if let Some(autoscroll) = autoscroll {
10152 self.request_autoscroll(autoscroll, cx);
10153 }
10154 }
10155
10156 pub fn remove_blocks(
10157 &mut self,
10158 block_ids: HashSet<BlockId>,
10159 autoscroll: Option<Autoscroll>,
10160 cx: &mut ViewContext<Self>,
10161 ) {
10162 self.display_map.update(cx, |display_map, cx| {
10163 display_map.remove_blocks(block_ids, cx)
10164 });
10165 if let Some(autoscroll) = autoscroll {
10166 self.request_autoscroll(autoscroll, cx);
10167 }
10168 }
10169
10170 pub fn row_for_block(
10171 &self,
10172 block_id: BlockId,
10173 cx: &mut ViewContext<Self>,
10174 ) -> Option<DisplayRow> {
10175 self.display_map
10176 .update(cx, |map, cx| map.row_for_block(block_id, cx))
10177 }
10178
10179 pub fn insert_creases(
10180 &mut self,
10181 creases: impl IntoIterator<Item = Crease>,
10182 cx: &mut ViewContext<Self>,
10183 ) -> Vec<CreaseId> {
10184 self.display_map
10185 .update(cx, |map, cx| map.insert_creases(creases, cx))
10186 }
10187
10188 pub fn remove_creases(
10189 &mut self,
10190 ids: impl IntoIterator<Item = CreaseId>,
10191 cx: &mut ViewContext<Self>,
10192 ) {
10193 self.display_map
10194 .update(cx, |map, cx| map.remove_creases(ids, cx));
10195 }
10196
10197 pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
10198 self.display_map
10199 .update(cx, |map, cx| map.snapshot(cx))
10200 .longest_row()
10201 }
10202
10203 pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
10204 self.display_map
10205 .update(cx, |map, cx| map.snapshot(cx))
10206 .max_point()
10207 }
10208
10209 pub fn text(&self, cx: &AppContext) -> String {
10210 self.buffer.read(cx).read(cx).text()
10211 }
10212
10213 pub fn text_option(&self, cx: &AppContext) -> Option<String> {
10214 let text = self.text(cx);
10215 let text = text.trim();
10216
10217 if text.is_empty() {
10218 return None;
10219 }
10220
10221 Some(text.to_string())
10222 }
10223
10224 pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
10225 self.transact(cx, |this, cx| {
10226 this.buffer
10227 .read(cx)
10228 .as_singleton()
10229 .expect("you can only call set_text on editors for singleton buffers")
10230 .update(cx, |buffer, cx| buffer.set_text(text, cx));
10231 });
10232 }
10233
10234 pub fn display_text(&self, cx: &mut AppContext) -> String {
10235 self.display_map
10236 .update(cx, |map, cx| map.snapshot(cx))
10237 .text()
10238 }
10239
10240 pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
10241 let mut wrap_guides = smallvec::smallvec![];
10242
10243 if self.show_wrap_guides == Some(false) {
10244 return wrap_guides;
10245 }
10246
10247 let settings = self.buffer.read(cx).settings_at(0, cx);
10248 if settings.show_wrap_guides {
10249 if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
10250 wrap_guides.push((soft_wrap as usize, true));
10251 }
10252 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
10253 }
10254
10255 wrap_guides
10256 }
10257
10258 pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
10259 let settings = self.buffer.read(cx).settings_at(0, cx);
10260 let mode = self
10261 .soft_wrap_mode_override
10262 .unwrap_or_else(|| settings.soft_wrap);
10263 match mode {
10264 language_settings::SoftWrap::None => SoftWrap::None,
10265 language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine,
10266 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
10267 language_settings::SoftWrap::PreferredLineLength => {
10268 SoftWrap::Column(settings.preferred_line_length)
10269 }
10270 }
10271 }
10272
10273 pub fn set_soft_wrap_mode(
10274 &mut self,
10275 mode: language_settings::SoftWrap,
10276 cx: &mut ViewContext<Self>,
10277 ) {
10278 self.soft_wrap_mode_override = Some(mode);
10279 cx.notify();
10280 }
10281
10282 pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
10283 let rem_size = cx.rem_size();
10284 self.display_map.update(cx, |map, cx| {
10285 map.set_font(
10286 style.text.font(),
10287 style.text.font_size.to_pixels(rem_size),
10288 cx,
10289 )
10290 });
10291 self.style = Some(style);
10292 }
10293
10294 pub fn style(&self) -> Option<&EditorStyle> {
10295 self.style.as_ref()
10296 }
10297
10298 // Called by the element. This method is not designed to be called outside of the editor
10299 // element's layout code because it does not notify when rewrapping is computed synchronously.
10300 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
10301 self.display_map
10302 .update(cx, |map, cx| map.set_wrap_width(width, cx))
10303 }
10304
10305 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
10306 if self.soft_wrap_mode_override.is_some() {
10307 self.soft_wrap_mode_override.take();
10308 } else {
10309 let soft_wrap = match self.soft_wrap_mode(cx) {
10310 SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
10311 SoftWrap::EditorWidth | SoftWrap::Column(_) => {
10312 language_settings::SoftWrap::PreferLine
10313 }
10314 };
10315 self.soft_wrap_mode_override = Some(soft_wrap);
10316 }
10317 cx.notify();
10318 }
10319
10320 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, cx: &mut ViewContext<Self>) {
10321 let Some(workspace) = self.workspace() else {
10322 return;
10323 };
10324 let fs = workspace.read(cx).app_state().fs.clone();
10325 let current_show = TabBarSettings::get_global(cx).show;
10326 update_settings_file::<TabBarSettings>(fs, cx, move |setting| {
10327 setting.show = Some(!current_show);
10328 });
10329 }
10330
10331 pub fn toggle_indent_guides(&mut self, _: &ToggleIndentGuides, cx: &mut ViewContext<Self>) {
10332 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
10333 self.buffer
10334 .read(cx)
10335 .settings_at(0, cx)
10336 .indent_guides
10337 .enabled
10338 });
10339 self.show_indent_guides = Some(!currently_enabled);
10340 cx.notify();
10341 }
10342
10343 fn should_show_indent_guides(&self) -> Option<bool> {
10344 self.show_indent_guides
10345 }
10346
10347 pub fn toggle_line_numbers(&mut self, _: &ToggleLineNumbers, cx: &mut ViewContext<Self>) {
10348 let mut editor_settings = EditorSettings::get_global(cx).clone();
10349 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
10350 EditorSettings::override_global(editor_settings, cx);
10351 }
10352
10353 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
10354 self.show_gutter = show_gutter;
10355 cx.notify();
10356 }
10357
10358 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
10359 self.show_line_numbers = Some(show_line_numbers);
10360 cx.notify();
10361 }
10362
10363 pub fn set_show_git_diff_gutter(
10364 &mut self,
10365 show_git_diff_gutter: bool,
10366 cx: &mut ViewContext<Self>,
10367 ) {
10368 self.show_git_diff_gutter = Some(show_git_diff_gutter);
10369 cx.notify();
10370 }
10371
10372 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut ViewContext<Self>) {
10373 self.show_code_actions = Some(show_code_actions);
10374 cx.notify();
10375 }
10376
10377 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut ViewContext<Self>) {
10378 self.show_runnables = Some(show_runnables);
10379 cx.notify();
10380 }
10381
10382 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
10383 self.show_wrap_guides = Some(show_wrap_guides);
10384 cx.notify();
10385 }
10386
10387 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut ViewContext<Self>) {
10388 self.show_indent_guides = Some(show_indent_guides);
10389 cx.notify();
10390 }
10391
10392 pub fn working_directory(&self, cx: &WindowContext) -> Option<PathBuf> {
10393 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10394 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10395 if let Some(dir) = file.abs_path(cx).parent() {
10396 return Some(dir.to_owned());
10397 }
10398 }
10399
10400 if let Some(project_path) = buffer.read(cx).project_path(cx) {
10401 return Some(project_path.path.to_path_buf());
10402 }
10403 }
10404
10405 None
10406 }
10407
10408 pub fn reveal_in_finder(&mut self, _: &RevealInFileManager, cx: &mut ViewContext<Self>) {
10409 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10410 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10411 cx.reveal_path(&file.abs_path(cx));
10412 }
10413 }
10414 }
10415
10416 pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
10417 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10418 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10419 if let Some(path) = file.abs_path(cx).to_str() {
10420 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
10421 }
10422 }
10423 }
10424 }
10425
10426 pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
10427 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10428 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10429 if let Some(path) = file.path().to_str() {
10430 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
10431 }
10432 }
10433 }
10434 }
10435
10436 pub fn toggle_git_blame(&mut self, _: &ToggleGitBlame, cx: &mut ViewContext<Self>) {
10437 self.show_git_blame_gutter = !self.show_git_blame_gutter;
10438
10439 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
10440 self.start_git_blame(true, cx);
10441 }
10442
10443 cx.notify();
10444 }
10445
10446 pub fn toggle_git_blame_inline(
10447 &mut self,
10448 _: &ToggleGitBlameInline,
10449 cx: &mut ViewContext<Self>,
10450 ) {
10451 self.toggle_git_blame_inline_internal(true, cx);
10452 cx.notify();
10453 }
10454
10455 pub fn git_blame_inline_enabled(&self) -> bool {
10456 self.git_blame_inline_enabled
10457 }
10458
10459 pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
10460 self.show_selection_menu = self
10461 .show_selection_menu
10462 .map(|show_selections_menu| !show_selections_menu)
10463 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
10464
10465 cx.notify();
10466 }
10467
10468 pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool {
10469 self.show_selection_menu
10470 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
10471 }
10472
10473 fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
10474 if let Some(project) = self.project.as_ref() {
10475 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
10476 return;
10477 };
10478
10479 if buffer.read(cx).file().is_none() {
10480 return;
10481 }
10482
10483 let focused = self.focus_handle(cx).contains_focused(cx);
10484
10485 let project = project.clone();
10486 let blame =
10487 cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
10488 self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
10489 self.blame = Some(blame);
10490 }
10491 }
10492
10493 fn toggle_git_blame_inline_internal(
10494 &mut self,
10495 user_triggered: bool,
10496 cx: &mut ViewContext<Self>,
10497 ) {
10498 if self.git_blame_inline_enabled {
10499 self.git_blame_inline_enabled = false;
10500 self.show_git_blame_inline = false;
10501 self.show_git_blame_inline_delay_task.take();
10502 } else {
10503 self.git_blame_inline_enabled = true;
10504 self.start_git_blame_inline(user_triggered, cx);
10505 }
10506
10507 cx.notify();
10508 }
10509
10510 fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
10511 self.start_git_blame(user_triggered, cx);
10512
10513 if ProjectSettings::get_global(cx)
10514 .git
10515 .inline_blame_delay()
10516 .is_some()
10517 {
10518 self.start_inline_blame_timer(cx);
10519 } else {
10520 self.show_git_blame_inline = true
10521 }
10522 }
10523
10524 pub fn blame(&self) -> Option<&Model<GitBlame>> {
10525 self.blame.as_ref()
10526 }
10527
10528 pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
10529 self.show_git_blame_gutter && self.has_blame_entries(cx)
10530 }
10531
10532 pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
10533 self.show_git_blame_inline
10534 && self.focus_handle.is_focused(cx)
10535 && !self.newest_selection_head_on_empty_line(cx)
10536 && self.has_blame_entries(cx)
10537 }
10538
10539 fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
10540 self.blame()
10541 .map_or(false, |blame| blame.read(cx).has_generated_entries())
10542 }
10543
10544 fn newest_selection_head_on_empty_line(&mut self, cx: &mut WindowContext) -> bool {
10545 let cursor_anchor = self.selections.newest_anchor().head();
10546
10547 let snapshot = self.buffer.read(cx).snapshot(cx);
10548 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
10549
10550 snapshot.line_len(buffer_row) == 0
10551 }
10552
10553 fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Result<url::Url> {
10554 let (path, selection, repo) = maybe!({
10555 let project_handle = self.project.as_ref()?.clone();
10556 let project = project_handle.read(cx);
10557
10558 let selection = self.selections.newest::<Point>(cx);
10559 let selection_range = selection.range();
10560
10561 let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10562 (buffer, selection_range.start.row..selection_range.end.row)
10563 } else {
10564 let buffer_ranges = self
10565 .buffer()
10566 .read(cx)
10567 .range_to_buffer_ranges(selection_range, cx);
10568
10569 let (buffer, range, _) = if selection.reversed {
10570 buffer_ranges.first()
10571 } else {
10572 buffer_ranges.last()
10573 }?;
10574
10575 let snapshot = buffer.read(cx).snapshot();
10576 let selection = text::ToPoint::to_point(&range.start, &snapshot).row
10577 ..text::ToPoint::to_point(&range.end, &snapshot).row;
10578 (buffer.clone(), selection)
10579 };
10580
10581 let path = buffer
10582 .read(cx)
10583 .file()?
10584 .as_local()?
10585 .path()
10586 .to_str()?
10587 .to_string();
10588 let repo = project.get_repo(&buffer.read(cx).project_path(cx)?, cx)?;
10589 Some((path, selection, repo))
10590 })
10591 .ok_or_else(|| anyhow!("unable to open git repository"))?;
10592
10593 const REMOTE_NAME: &str = "origin";
10594 let origin_url = repo
10595 .remote_url(REMOTE_NAME)
10596 .ok_or_else(|| anyhow!("remote \"{REMOTE_NAME}\" not found"))?;
10597 let sha = repo
10598 .head_sha()
10599 .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
10600
10601 let (provider, remote) =
10602 parse_git_remote_url(GitHostingProviderRegistry::default_global(cx), &origin_url)
10603 .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
10604
10605 Ok(provider.build_permalink(
10606 remote,
10607 BuildPermalinkParams {
10608 sha: &sha,
10609 path: &path,
10610 selection: Some(selection),
10611 },
10612 ))
10613 }
10614
10615 pub fn copy_permalink_to_line(&mut self, _: &CopyPermalinkToLine, cx: &mut ViewContext<Self>) {
10616 let permalink = self.get_permalink_to_line(cx);
10617
10618 match permalink {
10619 Ok(permalink) => {
10620 cx.write_to_clipboard(ClipboardItem::new(permalink.to_string()));
10621 }
10622 Err(err) => {
10623 let message = format!("Failed to copy permalink: {err}");
10624
10625 Err::<(), anyhow::Error>(err).log_err();
10626
10627 if let Some(workspace) = self.workspace() {
10628 workspace.update(cx, |workspace, cx| {
10629 struct CopyPermalinkToLine;
10630
10631 workspace.show_toast(
10632 Toast::new(NotificationId::unique::<CopyPermalinkToLine>(), message),
10633 cx,
10634 )
10635 })
10636 }
10637 }
10638 }
10639 }
10640
10641 pub fn open_permalink_to_line(&mut self, _: &OpenPermalinkToLine, cx: &mut ViewContext<Self>) {
10642 let permalink = self.get_permalink_to_line(cx);
10643
10644 match permalink {
10645 Ok(permalink) => {
10646 cx.open_url(permalink.as_ref());
10647 }
10648 Err(err) => {
10649 let message = format!("Failed to open permalink: {err}");
10650
10651 Err::<(), anyhow::Error>(err).log_err();
10652
10653 if let Some(workspace) = self.workspace() {
10654 workspace.update(cx, |workspace, cx| {
10655 struct OpenPermalinkToLine;
10656
10657 workspace.show_toast(
10658 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
10659 cx,
10660 )
10661 })
10662 }
10663 }
10664 }
10665 }
10666
10667 /// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
10668 /// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
10669 /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
10670 pub fn highlight_rows<T: 'static>(
10671 &mut self,
10672 rows: RangeInclusive<Anchor>,
10673 color: Option<Hsla>,
10674 should_autoscroll: bool,
10675 cx: &mut ViewContext<Self>,
10676 ) {
10677 let snapshot = self.buffer().read(cx).snapshot(cx);
10678 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
10679 let existing_highlight_index = row_highlights.binary_search_by(|highlight| {
10680 highlight
10681 .range
10682 .start()
10683 .cmp(&rows.start(), &snapshot)
10684 .then(highlight.range.end().cmp(&rows.end(), &snapshot))
10685 });
10686 match (color, existing_highlight_index) {
10687 (Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert(
10688 ix,
10689 RowHighlight {
10690 index: post_inc(&mut self.highlight_order),
10691 range: rows,
10692 should_autoscroll,
10693 color,
10694 },
10695 ),
10696 (None, Ok(i)) => {
10697 row_highlights.remove(i);
10698 }
10699 }
10700 }
10701
10702 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
10703 pub fn clear_row_highlights<T: 'static>(&mut self) {
10704 self.highlighted_rows.remove(&TypeId::of::<T>());
10705 }
10706
10707 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
10708 pub fn highlighted_rows<T: 'static>(
10709 &self,
10710 ) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
10711 Some(
10712 self.highlighted_rows
10713 .get(&TypeId::of::<T>())?
10714 .iter()
10715 .map(|highlight| (&highlight.range, highlight.color.as_ref())),
10716 )
10717 }
10718
10719 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
10720 /// Rerturns a map of display rows that are highlighted and their corresponding highlight color.
10721 /// Allows to ignore certain kinds of highlights.
10722 pub fn highlighted_display_rows(
10723 &mut self,
10724 cx: &mut WindowContext,
10725 ) -> BTreeMap<DisplayRow, Hsla> {
10726 let snapshot = self.snapshot(cx);
10727 let mut used_highlight_orders = HashMap::default();
10728 self.highlighted_rows
10729 .iter()
10730 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
10731 .fold(
10732 BTreeMap::<DisplayRow, Hsla>::new(),
10733 |mut unique_rows, highlight| {
10734 let start_row = highlight.range.start().to_display_point(&snapshot).row();
10735 let end_row = highlight.range.end().to_display_point(&snapshot).row();
10736 for row in start_row.0..=end_row.0 {
10737 let used_index =
10738 used_highlight_orders.entry(row).or_insert(highlight.index);
10739 if highlight.index >= *used_index {
10740 *used_index = highlight.index;
10741 match highlight.color {
10742 Some(hsla) => unique_rows.insert(DisplayRow(row), hsla),
10743 None => unique_rows.remove(&DisplayRow(row)),
10744 };
10745 }
10746 }
10747 unique_rows
10748 },
10749 )
10750 }
10751
10752 pub fn highlighted_display_row_for_autoscroll(
10753 &self,
10754 snapshot: &DisplaySnapshot,
10755 ) -> Option<DisplayRow> {
10756 self.highlighted_rows
10757 .values()
10758 .flat_map(|highlighted_rows| highlighted_rows.iter())
10759 .filter_map(|highlight| {
10760 if highlight.color.is_none() || !highlight.should_autoscroll {
10761 return None;
10762 }
10763 Some(highlight.range.start().to_display_point(&snapshot).row())
10764 })
10765 .min()
10766 }
10767
10768 pub fn set_search_within_ranges(
10769 &mut self,
10770 ranges: &[Range<Anchor>],
10771 cx: &mut ViewContext<Self>,
10772 ) {
10773 self.highlight_background::<SearchWithinRange>(
10774 ranges,
10775 |colors| colors.editor_document_highlight_read_background,
10776 cx,
10777 )
10778 }
10779
10780 pub fn set_breadcrumb_header(&mut self, new_header: String) {
10781 self.breadcrumb_header = Some(new_header);
10782 }
10783
10784 pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
10785 self.clear_background_highlights::<SearchWithinRange>(cx);
10786 }
10787
10788 pub fn highlight_background<T: 'static>(
10789 &mut self,
10790 ranges: &[Range<Anchor>],
10791 color_fetcher: fn(&ThemeColors) -> Hsla,
10792 cx: &mut ViewContext<Self>,
10793 ) {
10794 let snapshot = self.snapshot(cx);
10795 // this is to try and catch a panic sooner
10796 for range in ranges {
10797 snapshot
10798 .buffer_snapshot
10799 .summary_for_anchor::<usize>(&range.start);
10800 snapshot
10801 .buffer_snapshot
10802 .summary_for_anchor::<usize>(&range.end);
10803 }
10804
10805 self.background_highlights
10806 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
10807 self.scrollbar_marker_state.dirty = true;
10808 cx.notify();
10809 }
10810
10811 pub fn clear_background_highlights<T: 'static>(
10812 &mut self,
10813 cx: &mut ViewContext<Self>,
10814 ) -> Option<BackgroundHighlight> {
10815 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
10816 if !text_highlights.1.is_empty() {
10817 self.scrollbar_marker_state.dirty = true;
10818 cx.notify();
10819 }
10820 Some(text_highlights)
10821 }
10822
10823 pub fn highlight_gutter<T: 'static>(
10824 &mut self,
10825 ranges: &[Range<Anchor>],
10826 color_fetcher: fn(&AppContext) -> Hsla,
10827 cx: &mut ViewContext<Self>,
10828 ) {
10829 self.gutter_highlights
10830 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
10831 cx.notify();
10832 }
10833
10834 pub fn clear_gutter_highlights<T: 'static>(
10835 &mut self,
10836 cx: &mut ViewContext<Self>,
10837 ) -> Option<GutterHighlight> {
10838 cx.notify();
10839 self.gutter_highlights.remove(&TypeId::of::<T>())
10840 }
10841
10842 #[cfg(feature = "test-support")]
10843 pub fn all_text_background_highlights(
10844 &mut self,
10845 cx: &mut ViewContext<Self>,
10846 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
10847 let snapshot = self.snapshot(cx);
10848 let buffer = &snapshot.buffer_snapshot;
10849 let start = buffer.anchor_before(0);
10850 let end = buffer.anchor_after(buffer.len());
10851 let theme = cx.theme().colors();
10852 self.background_highlights_in_range(start..end, &snapshot, theme)
10853 }
10854
10855 #[cfg(feature = "test-support")]
10856 pub fn search_background_highlights(
10857 &mut self,
10858 cx: &mut ViewContext<Self>,
10859 ) -> Vec<Range<Point>> {
10860 let snapshot = self.buffer().read(cx).snapshot(cx);
10861
10862 let highlights = self
10863 .background_highlights
10864 .get(&TypeId::of::<items::BufferSearchHighlights>());
10865
10866 if let Some((_color, ranges)) = highlights {
10867 ranges
10868 .iter()
10869 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
10870 .collect_vec()
10871 } else {
10872 vec![]
10873 }
10874 }
10875
10876 fn document_highlights_for_position<'a>(
10877 &'a self,
10878 position: Anchor,
10879 buffer: &'a MultiBufferSnapshot,
10880 ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
10881 let read_highlights = self
10882 .background_highlights
10883 .get(&TypeId::of::<DocumentHighlightRead>())
10884 .map(|h| &h.1);
10885 let write_highlights = self
10886 .background_highlights
10887 .get(&TypeId::of::<DocumentHighlightWrite>())
10888 .map(|h| &h.1);
10889 let left_position = position.bias_left(buffer);
10890 let right_position = position.bias_right(buffer);
10891 read_highlights
10892 .into_iter()
10893 .chain(write_highlights)
10894 .flat_map(move |ranges| {
10895 let start_ix = match ranges.binary_search_by(|probe| {
10896 let cmp = probe.end.cmp(&left_position, buffer);
10897 if cmp.is_ge() {
10898 Ordering::Greater
10899 } else {
10900 Ordering::Less
10901 }
10902 }) {
10903 Ok(i) | Err(i) => i,
10904 };
10905
10906 ranges[start_ix..]
10907 .iter()
10908 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
10909 })
10910 }
10911
10912 pub fn has_background_highlights<T: 'static>(&self) -> bool {
10913 self.background_highlights
10914 .get(&TypeId::of::<T>())
10915 .map_or(false, |(_, highlights)| !highlights.is_empty())
10916 }
10917
10918 pub fn background_highlights_in_range(
10919 &self,
10920 search_range: Range<Anchor>,
10921 display_snapshot: &DisplaySnapshot,
10922 theme: &ThemeColors,
10923 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
10924 let mut results = Vec::new();
10925 for (color_fetcher, ranges) in self.background_highlights.values() {
10926 let color = color_fetcher(theme);
10927 let start_ix = match ranges.binary_search_by(|probe| {
10928 let cmp = probe
10929 .end
10930 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
10931 if cmp.is_gt() {
10932 Ordering::Greater
10933 } else {
10934 Ordering::Less
10935 }
10936 }) {
10937 Ok(i) | Err(i) => i,
10938 };
10939 for range in &ranges[start_ix..] {
10940 if range
10941 .start
10942 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
10943 .is_ge()
10944 {
10945 break;
10946 }
10947
10948 let start = range.start.to_display_point(&display_snapshot);
10949 let end = range.end.to_display_point(&display_snapshot);
10950 results.push((start..end, color))
10951 }
10952 }
10953 results
10954 }
10955
10956 pub fn background_highlight_row_ranges<T: 'static>(
10957 &self,
10958 search_range: Range<Anchor>,
10959 display_snapshot: &DisplaySnapshot,
10960 count: usize,
10961 ) -> Vec<RangeInclusive<DisplayPoint>> {
10962 let mut results = Vec::new();
10963 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
10964 return vec![];
10965 };
10966
10967 let start_ix = match ranges.binary_search_by(|probe| {
10968 let cmp = probe
10969 .end
10970 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
10971 if cmp.is_gt() {
10972 Ordering::Greater
10973 } else {
10974 Ordering::Less
10975 }
10976 }) {
10977 Ok(i) | Err(i) => i,
10978 };
10979 let mut push_region = |start: Option<Point>, end: Option<Point>| {
10980 if let (Some(start_display), Some(end_display)) = (start, end) {
10981 results.push(
10982 start_display.to_display_point(display_snapshot)
10983 ..=end_display.to_display_point(display_snapshot),
10984 );
10985 }
10986 };
10987 let mut start_row: Option<Point> = None;
10988 let mut end_row: Option<Point> = None;
10989 if ranges.len() > count {
10990 return Vec::new();
10991 }
10992 for range in &ranges[start_ix..] {
10993 if range
10994 .start
10995 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
10996 .is_ge()
10997 {
10998 break;
10999 }
11000 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
11001 if let Some(current_row) = &end_row {
11002 if end.row == current_row.row {
11003 continue;
11004 }
11005 }
11006 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
11007 if start_row.is_none() {
11008 assert_eq!(end_row, None);
11009 start_row = Some(start);
11010 end_row = Some(end);
11011 continue;
11012 }
11013 if let Some(current_end) = end_row.as_mut() {
11014 if start.row > current_end.row + 1 {
11015 push_region(start_row, end_row);
11016 start_row = Some(start);
11017 end_row = Some(end);
11018 } else {
11019 // Merge two hunks.
11020 *current_end = end;
11021 }
11022 } else {
11023 unreachable!();
11024 }
11025 }
11026 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
11027 push_region(start_row, end_row);
11028 results
11029 }
11030
11031 pub fn gutter_highlights_in_range(
11032 &self,
11033 search_range: Range<Anchor>,
11034 display_snapshot: &DisplaySnapshot,
11035 cx: &AppContext,
11036 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
11037 let mut results = Vec::new();
11038 for (color_fetcher, ranges) in self.gutter_highlights.values() {
11039 let color = color_fetcher(cx);
11040 let start_ix = match ranges.binary_search_by(|probe| {
11041 let cmp = probe
11042 .end
11043 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
11044 if cmp.is_gt() {
11045 Ordering::Greater
11046 } else {
11047 Ordering::Less
11048 }
11049 }) {
11050 Ok(i) | Err(i) => i,
11051 };
11052 for range in &ranges[start_ix..] {
11053 if range
11054 .start
11055 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
11056 .is_ge()
11057 {
11058 break;
11059 }
11060
11061 let start = range.start.to_display_point(&display_snapshot);
11062 let end = range.end.to_display_point(&display_snapshot);
11063 results.push((start..end, color))
11064 }
11065 }
11066 results
11067 }
11068
11069 /// Get the text ranges corresponding to the redaction query
11070 pub fn redacted_ranges(
11071 &self,
11072 search_range: Range<Anchor>,
11073 display_snapshot: &DisplaySnapshot,
11074 cx: &WindowContext,
11075 ) -> Vec<Range<DisplayPoint>> {
11076 display_snapshot
11077 .buffer_snapshot
11078 .redacted_ranges(search_range, |file| {
11079 if let Some(file) = file {
11080 file.is_private()
11081 && EditorSettings::get(Some(file.as_ref().into()), cx).redact_private_values
11082 } else {
11083 false
11084 }
11085 })
11086 .map(|range| {
11087 range.start.to_display_point(display_snapshot)
11088 ..range.end.to_display_point(display_snapshot)
11089 })
11090 .collect()
11091 }
11092
11093 pub fn highlight_text<T: 'static>(
11094 &mut self,
11095 ranges: Vec<Range<Anchor>>,
11096 style: HighlightStyle,
11097 cx: &mut ViewContext<Self>,
11098 ) {
11099 self.display_map.update(cx, |map, _| {
11100 map.highlight_text(TypeId::of::<T>(), ranges, style)
11101 });
11102 cx.notify();
11103 }
11104
11105 pub(crate) fn highlight_inlays<T: 'static>(
11106 &mut self,
11107 highlights: Vec<InlayHighlight>,
11108 style: HighlightStyle,
11109 cx: &mut ViewContext<Self>,
11110 ) {
11111 self.display_map.update(cx, |map, _| {
11112 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
11113 });
11114 cx.notify();
11115 }
11116
11117 pub fn text_highlights<'a, T: 'static>(
11118 &'a self,
11119 cx: &'a AppContext,
11120 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
11121 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
11122 }
11123
11124 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
11125 let cleared = self
11126 .display_map
11127 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
11128 if cleared {
11129 cx.notify();
11130 }
11131 }
11132
11133 pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
11134 (self.read_only(cx) || self.blink_manager.read(cx).visible())
11135 && self.focus_handle.is_focused(cx)
11136 }
11137
11138 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut ViewContext<Self>) {
11139 self.show_cursor_when_unfocused = is_enabled;
11140 cx.notify();
11141 }
11142
11143 fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
11144 cx.notify();
11145 }
11146
11147 fn on_buffer_event(
11148 &mut self,
11149 multibuffer: Model<MultiBuffer>,
11150 event: &multi_buffer::Event,
11151 cx: &mut ViewContext<Self>,
11152 ) {
11153 match event {
11154 multi_buffer::Event::Edited {
11155 singleton_buffer_edited,
11156 } => {
11157 self.scrollbar_marker_state.dirty = true;
11158 self.active_indent_guides_state.dirty = true;
11159 self.refresh_active_diagnostics(cx);
11160 self.refresh_code_actions(cx);
11161 if self.has_active_inline_completion(cx) {
11162 self.update_visible_inline_completion(cx);
11163 }
11164 cx.emit(EditorEvent::BufferEdited);
11165 cx.emit(SearchEvent::MatchesInvalidated);
11166 if *singleton_buffer_edited {
11167 if let Some(project) = &self.project {
11168 let project = project.read(cx);
11169 #[allow(clippy::mutable_key_type)]
11170 let languages_affected = multibuffer
11171 .read(cx)
11172 .all_buffers()
11173 .into_iter()
11174 .filter_map(|buffer| {
11175 let buffer = buffer.read(cx);
11176 let language = buffer.language()?;
11177 if project.is_local()
11178 && project.language_servers_for_buffer(buffer, cx).count() == 0
11179 {
11180 None
11181 } else {
11182 Some(language)
11183 }
11184 })
11185 .cloned()
11186 .collect::<HashSet<_>>();
11187 if !languages_affected.is_empty() {
11188 self.refresh_inlay_hints(
11189 InlayHintRefreshReason::BufferEdited(languages_affected),
11190 cx,
11191 );
11192 }
11193 }
11194 }
11195
11196 let Some(project) = &self.project else { return };
11197 let telemetry = project.read(cx).client().telemetry().clone();
11198 refresh_linked_ranges(self, cx);
11199 telemetry.log_edit_event("editor");
11200 }
11201 multi_buffer::Event::ExcerptsAdded {
11202 buffer,
11203 predecessor,
11204 excerpts,
11205 } => {
11206 self.tasks_update_task = Some(self.refresh_runnables(cx));
11207 cx.emit(EditorEvent::ExcerptsAdded {
11208 buffer: buffer.clone(),
11209 predecessor: *predecessor,
11210 excerpts: excerpts.clone(),
11211 });
11212 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
11213 }
11214 multi_buffer::Event::ExcerptsRemoved { ids } => {
11215 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
11216 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
11217 }
11218 multi_buffer::Event::ExcerptsEdited { ids } => {
11219 cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
11220 }
11221 multi_buffer::Event::ExcerptsExpanded { ids } => {
11222 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
11223 }
11224 multi_buffer::Event::Reparsed(buffer_id) => {
11225 self.tasks_update_task = Some(self.refresh_runnables(cx));
11226
11227 cx.emit(EditorEvent::Reparsed(*buffer_id));
11228 }
11229 multi_buffer::Event::LanguageChanged(buffer_id) => {
11230 linked_editing_ranges::refresh_linked_ranges(self, cx);
11231 cx.emit(EditorEvent::Reparsed(*buffer_id));
11232 cx.notify();
11233 }
11234 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
11235 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
11236 multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
11237 cx.emit(EditorEvent::TitleChanged)
11238 }
11239 multi_buffer::Event::DiffBaseChanged => {
11240 self.scrollbar_marker_state.dirty = true;
11241 cx.emit(EditorEvent::DiffBaseChanged);
11242 cx.notify();
11243 }
11244 multi_buffer::Event::DiffUpdated { buffer } => {
11245 self.sync_expanded_diff_hunks(buffer.clone(), cx);
11246 cx.notify();
11247 }
11248 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
11249 multi_buffer::Event::DiagnosticsUpdated => {
11250 self.refresh_active_diagnostics(cx);
11251 self.scrollbar_marker_state.dirty = true;
11252 cx.notify();
11253 }
11254 _ => {}
11255 };
11256 }
11257
11258 fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
11259 cx.notify();
11260 }
11261
11262 fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
11263 self.tasks_update_task = Some(self.refresh_runnables(cx));
11264 self.refresh_inline_completion(true, cx);
11265 self.refresh_inlay_hints(
11266 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
11267 self.selections.newest_anchor().head(),
11268 &self.buffer.read(cx).snapshot(cx),
11269 cx,
11270 )),
11271 cx,
11272 );
11273 let editor_settings = EditorSettings::get_global(cx);
11274 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
11275 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
11276
11277 let project_settings = ProjectSettings::get_global(cx);
11278 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
11279
11280 if self.mode == EditorMode::Full {
11281 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
11282 if self.git_blame_inline_enabled != inline_blame_enabled {
11283 self.toggle_git_blame_inline_internal(false, cx);
11284 }
11285 }
11286
11287 cx.notify();
11288 }
11289
11290 pub fn set_searchable(&mut self, searchable: bool) {
11291 self.searchable = searchable;
11292 }
11293
11294 pub fn searchable(&self) -> bool {
11295 self.searchable
11296 }
11297
11298 fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
11299 self.open_excerpts_common(true, cx)
11300 }
11301
11302 fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
11303 self.open_excerpts_common(false, cx)
11304 }
11305
11306 fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
11307 let buffer = self.buffer.read(cx);
11308 if buffer.is_singleton() {
11309 cx.propagate();
11310 return;
11311 }
11312
11313 let Some(workspace) = self.workspace() else {
11314 cx.propagate();
11315 return;
11316 };
11317
11318 let mut new_selections_by_buffer = HashMap::default();
11319 for selection in self.selections.all::<usize>(cx) {
11320 for (buffer, mut range, _) in
11321 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
11322 {
11323 if selection.reversed {
11324 mem::swap(&mut range.start, &mut range.end);
11325 }
11326 new_selections_by_buffer
11327 .entry(buffer)
11328 .or_insert(Vec::new())
11329 .push(range)
11330 }
11331 }
11332
11333 // We defer the pane interaction because we ourselves are a workspace item
11334 // and activating a new item causes the pane to call a method on us reentrantly,
11335 // which panics if we're on the stack.
11336 cx.window_context().defer(move |cx| {
11337 workspace.update(cx, |workspace, cx| {
11338 let pane = if split {
11339 workspace.adjacent_pane(cx)
11340 } else {
11341 workspace.active_pane().clone()
11342 };
11343
11344 for (buffer, ranges) in new_selections_by_buffer {
11345 let editor = workspace.open_project_item::<Self>(pane.clone(), buffer, cx);
11346 editor.update(cx, |editor, cx| {
11347 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
11348 s.select_ranges(ranges);
11349 });
11350 });
11351 }
11352 })
11353 });
11354 }
11355
11356 fn jump(
11357 &mut self,
11358 path: ProjectPath,
11359 position: Point,
11360 anchor: language::Anchor,
11361 offset_from_top: u32,
11362 cx: &mut ViewContext<Self>,
11363 ) {
11364 let workspace = self.workspace();
11365 cx.spawn(|_, mut cx| async move {
11366 let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
11367 let editor = workspace.update(&mut cx, |workspace, cx| {
11368 // Reset the preview item id before opening the new item
11369 workspace.active_pane().update(cx, |pane, cx| {
11370 pane.set_preview_item_id(None, cx);
11371 });
11372 workspace.open_path_preview(path, None, true, true, cx)
11373 })?;
11374 let editor = editor
11375 .await?
11376 .downcast::<Editor>()
11377 .ok_or_else(|| anyhow!("opened item was not an editor"))?
11378 .downgrade();
11379 editor.update(&mut cx, |editor, cx| {
11380 let buffer = editor
11381 .buffer()
11382 .read(cx)
11383 .as_singleton()
11384 .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
11385 let buffer = buffer.read(cx);
11386 let cursor = if buffer.can_resolve(&anchor) {
11387 language::ToPoint::to_point(&anchor, buffer)
11388 } else {
11389 buffer.clip_point(position, Bias::Left)
11390 };
11391
11392 let nav_history = editor.nav_history.take();
11393 editor.change_selections(
11394 Some(Autoscroll::top_relative(offset_from_top as usize)),
11395 cx,
11396 |s| {
11397 s.select_ranges([cursor..cursor]);
11398 },
11399 );
11400 editor.nav_history = nav_history;
11401
11402 anyhow::Ok(())
11403 })??;
11404
11405 anyhow::Ok(())
11406 })
11407 .detach_and_log_err(cx);
11408 }
11409
11410 fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
11411 let snapshot = self.buffer.read(cx).read(cx);
11412 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
11413 Some(
11414 ranges
11415 .iter()
11416 .map(move |range| {
11417 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
11418 })
11419 .collect(),
11420 )
11421 }
11422
11423 fn selection_replacement_ranges(
11424 &self,
11425 range: Range<OffsetUtf16>,
11426 cx: &AppContext,
11427 ) -> Vec<Range<OffsetUtf16>> {
11428 let selections = self.selections.all::<OffsetUtf16>(cx);
11429 let newest_selection = selections
11430 .iter()
11431 .max_by_key(|selection| selection.id)
11432 .unwrap();
11433 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
11434 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
11435 let snapshot = self.buffer.read(cx).read(cx);
11436 selections
11437 .into_iter()
11438 .map(|mut selection| {
11439 selection.start.0 =
11440 (selection.start.0 as isize).saturating_add(start_delta) as usize;
11441 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
11442 snapshot.clip_offset_utf16(selection.start, Bias::Left)
11443 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
11444 })
11445 .collect()
11446 }
11447
11448 fn report_editor_event(
11449 &self,
11450 operation: &'static str,
11451 file_extension: Option<String>,
11452 cx: &AppContext,
11453 ) {
11454 if cfg!(any(test, feature = "test-support")) {
11455 return;
11456 }
11457
11458 let Some(project) = &self.project else { return };
11459
11460 // If None, we are in a file without an extension
11461 let file = self
11462 .buffer
11463 .read(cx)
11464 .as_singleton()
11465 .and_then(|b| b.read(cx).file());
11466 let file_extension = file_extension.or(file
11467 .as_ref()
11468 .and_then(|file| Path::new(file.file_name(cx)).extension())
11469 .and_then(|e| e.to_str())
11470 .map(|a| a.to_string()));
11471
11472 let vim_mode = cx
11473 .global::<SettingsStore>()
11474 .raw_user_settings()
11475 .get("vim_mode")
11476 == Some(&serde_json::Value::Bool(true));
11477
11478 let copilot_enabled = all_language_settings(file, cx).inline_completions.provider
11479 == language::language_settings::InlineCompletionProvider::Copilot;
11480 let copilot_enabled_for_language = self
11481 .buffer
11482 .read(cx)
11483 .settings_at(0, cx)
11484 .show_inline_completions;
11485
11486 let telemetry = project.read(cx).client().telemetry().clone();
11487 telemetry.report_editor_event(
11488 file_extension,
11489 vim_mode,
11490 operation,
11491 copilot_enabled,
11492 copilot_enabled_for_language,
11493 )
11494 }
11495
11496 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
11497 /// with each line being an array of {text, highlight} objects.
11498 fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
11499 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
11500 return;
11501 };
11502
11503 #[derive(Serialize)]
11504 struct Chunk<'a> {
11505 text: String,
11506 highlight: Option<&'a str>,
11507 }
11508
11509 let snapshot = buffer.read(cx).snapshot();
11510 let range = self
11511 .selected_text_range(cx)
11512 .and_then(|selected_range| {
11513 if selected_range.is_empty() {
11514 None
11515 } else {
11516 Some(selected_range)
11517 }
11518 })
11519 .unwrap_or_else(|| 0..snapshot.len());
11520
11521 let chunks = snapshot.chunks(range, true);
11522 let mut lines = Vec::new();
11523 let mut line: VecDeque<Chunk> = VecDeque::new();
11524
11525 let Some(style) = self.style.as_ref() else {
11526 return;
11527 };
11528
11529 for chunk in chunks {
11530 let highlight = chunk
11531 .syntax_highlight_id
11532 .and_then(|id| id.name(&style.syntax));
11533 let mut chunk_lines = chunk.text.split('\n').peekable();
11534 while let Some(text) = chunk_lines.next() {
11535 let mut merged_with_last_token = false;
11536 if let Some(last_token) = line.back_mut() {
11537 if last_token.highlight == highlight {
11538 last_token.text.push_str(text);
11539 merged_with_last_token = true;
11540 }
11541 }
11542
11543 if !merged_with_last_token {
11544 line.push_back(Chunk {
11545 text: text.into(),
11546 highlight,
11547 });
11548 }
11549
11550 if chunk_lines.peek().is_some() {
11551 if line.len() > 1 && line.front().unwrap().text.is_empty() {
11552 line.pop_front();
11553 }
11554 if line.len() > 1 && line.back().unwrap().text.is_empty() {
11555 line.pop_back();
11556 }
11557
11558 lines.push(mem::take(&mut line));
11559 }
11560 }
11561 }
11562
11563 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
11564 return;
11565 };
11566 cx.write_to_clipboard(ClipboardItem::new(lines));
11567 }
11568
11569 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
11570 &self.inlay_hint_cache
11571 }
11572
11573 pub fn replay_insert_event(
11574 &mut self,
11575 text: &str,
11576 relative_utf16_range: Option<Range<isize>>,
11577 cx: &mut ViewContext<Self>,
11578 ) {
11579 if !self.input_enabled {
11580 cx.emit(EditorEvent::InputIgnored { text: text.into() });
11581 return;
11582 }
11583 if let Some(relative_utf16_range) = relative_utf16_range {
11584 let selections = self.selections.all::<OffsetUtf16>(cx);
11585 self.change_selections(None, cx, |s| {
11586 let new_ranges = selections.into_iter().map(|range| {
11587 let start = OffsetUtf16(
11588 range
11589 .head()
11590 .0
11591 .saturating_add_signed(relative_utf16_range.start),
11592 );
11593 let end = OffsetUtf16(
11594 range
11595 .head()
11596 .0
11597 .saturating_add_signed(relative_utf16_range.end),
11598 );
11599 start..end
11600 });
11601 s.select_ranges(new_ranges);
11602 });
11603 }
11604
11605 self.handle_input(text, cx);
11606 }
11607
11608 pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
11609 let Some(project) = self.project.as_ref() else {
11610 return false;
11611 };
11612 let project = project.read(cx);
11613
11614 let mut supports = false;
11615 self.buffer().read(cx).for_each_buffer(|buffer| {
11616 if !supports {
11617 supports = project
11618 .language_servers_for_buffer(buffer.read(cx), cx)
11619 .any(
11620 |(_, server)| match server.capabilities().inlay_hint_provider {
11621 Some(lsp::OneOf::Left(enabled)) => enabled,
11622 Some(lsp::OneOf::Right(_)) => true,
11623 None => false,
11624 },
11625 )
11626 }
11627 });
11628 supports
11629 }
11630
11631 pub fn focus(&self, cx: &mut WindowContext) {
11632 cx.focus(&self.focus_handle)
11633 }
11634
11635 pub fn is_focused(&self, cx: &WindowContext) -> bool {
11636 self.focus_handle.is_focused(cx)
11637 }
11638
11639 fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
11640 cx.emit(EditorEvent::Focused);
11641
11642 if let Some(descendant) = self
11643 .last_focused_descendant
11644 .take()
11645 .and_then(|descendant| descendant.upgrade())
11646 {
11647 cx.focus(&descendant);
11648 } else {
11649 if let Some(blame) = self.blame.as_ref() {
11650 blame.update(cx, GitBlame::focus)
11651 }
11652
11653 self.blink_manager.update(cx, BlinkManager::enable);
11654 self.show_cursor_names(cx);
11655 self.buffer.update(cx, |buffer, cx| {
11656 buffer.finalize_last_transaction(cx);
11657 if self.leader_peer_id.is_none() {
11658 buffer.set_active_selections(
11659 &self.selections.disjoint_anchors(),
11660 self.selections.line_mode,
11661 self.cursor_shape,
11662 cx,
11663 );
11664 }
11665 });
11666 }
11667 }
11668
11669 fn handle_focus_in(&mut self, cx: &mut ViewContext<Self>) {
11670 cx.emit(EditorEvent::FocusedIn)
11671 }
11672
11673 fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
11674 if event.blurred != self.focus_handle {
11675 self.last_focused_descendant = Some(event.blurred);
11676 }
11677 }
11678
11679 pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
11680 self.blink_manager.update(cx, BlinkManager::disable);
11681 self.buffer
11682 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
11683
11684 if let Some(blame) = self.blame.as_ref() {
11685 blame.update(cx, GitBlame::blur)
11686 }
11687 if !self.hover_state.focused(cx) {
11688 hide_hover(self, cx);
11689 }
11690
11691 self.hide_context_menu(cx);
11692 cx.emit(EditorEvent::Blurred);
11693 cx.notify();
11694 }
11695
11696 pub fn register_action<A: Action>(
11697 &mut self,
11698 listener: impl Fn(&A, &mut WindowContext) + 'static,
11699 ) -> Subscription {
11700 let id = self.next_editor_action_id.post_inc();
11701 let listener = Arc::new(listener);
11702 self.editor_actions.borrow_mut().insert(
11703 id,
11704 Box::new(move |cx| {
11705 let _view = cx.view().clone();
11706 let cx = cx.window_context();
11707 let listener = listener.clone();
11708 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
11709 let action = action.downcast_ref().unwrap();
11710 if phase == DispatchPhase::Bubble {
11711 listener(action, cx)
11712 }
11713 })
11714 }),
11715 );
11716
11717 let editor_actions = self.editor_actions.clone();
11718 Subscription::new(move || {
11719 editor_actions.borrow_mut().remove(&id);
11720 })
11721 }
11722
11723 pub fn file_header_size(&self) -> u8 {
11724 self.file_header_size
11725 }
11726}
11727
11728fn hunks_for_selections(
11729 multi_buffer_snapshot: &MultiBufferSnapshot,
11730 selections: &[Selection<Anchor>],
11731) -> Vec<DiffHunk<MultiBufferRow>> {
11732 let mut hunks = Vec::with_capacity(selections.len());
11733 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
11734 HashMap::default();
11735 let buffer_rows_for_selections = selections.iter().map(|selection| {
11736 let head = selection.head();
11737 let tail = selection.tail();
11738 let start = MultiBufferRow(tail.to_point(&multi_buffer_snapshot).row);
11739 let end = MultiBufferRow(head.to_point(&multi_buffer_snapshot).row);
11740 if start > end {
11741 end..start
11742 } else {
11743 start..end
11744 }
11745 });
11746
11747 for selected_multi_buffer_rows in buffer_rows_for_selections {
11748 let query_rows =
11749 selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
11750 for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
11751 // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
11752 // when the caret is just above or just below the deleted hunk.
11753 let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
11754 let related_to_selection = if allow_adjacent {
11755 hunk.associated_range.overlaps(&query_rows)
11756 || hunk.associated_range.start == query_rows.end
11757 || hunk.associated_range.end == query_rows.start
11758 } else {
11759 // `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
11760 // `hunk.associated_range` is exclusive (e.g. [2..3] means 2nd row is selected)
11761 hunk.associated_range.overlaps(&selected_multi_buffer_rows)
11762 || selected_multi_buffer_rows.end == hunk.associated_range.start
11763 };
11764 if related_to_selection {
11765 if !processed_buffer_rows
11766 .entry(hunk.buffer_id)
11767 .or_default()
11768 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
11769 {
11770 continue;
11771 }
11772 hunks.push(hunk);
11773 }
11774 }
11775 }
11776
11777 hunks
11778}
11779
11780pub trait CollaborationHub {
11781 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
11782 fn user_participant_indices<'a>(
11783 &self,
11784 cx: &'a AppContext,
11785 ) -> &'a HashMap<u64, ParticipantIndex>;
11786 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
11787}
11788
11789impl CollaborationHub for Model<Project> {
11790 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
11791 self.read(cx).collaborators()
11792 }
11793
11794 fn user_participant_indices<'a>(
11795 &self,
11796 cx: &'a AppContext,
11797 ) -> &'a HashMap<u64, ParticipantIndex> {
11798 self.read(cx).user_store().read(cx).participant_indices()
11799 }
11800
11801 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
11802 let this = self.read(cx);
11803 let user_ids = this.collaborators().values().map(|c| c.user_id);
11804 this.user_store().read_with(cx, |user_store, cx| {
11805 user_store.participant_names(user_ids, cx)
11806 })
11807 }
11808}
11809
11810pub trait CompletionProvider {
11811 fn completions(
11812 &self,
11813 buffer: &Model<Buffer>,
11814 buffer_position: text::Anchor,
11815 trigger: CompletionContext,
11816 cx: &mut ViewContext<Editor>,
11817 ) -> Task<Result<Vec<Completion>>>;
11818
11819 fn resolve_completions(
11820 &self,
11821 buffer: Model<Buffer>,
11822 completion_indices: Vec<usize>,
11823 completions: Arc<RwLock<Box<[Completion]>>>,
11824 cx: &mut ViewContext<Editor>,
11825 ) -> Task<Result<bool>>;
11826
11827 fn apply_additional_edits_for_completion(
11828 &self,
11829 buffer: Model<Buffer>,
11830 completion: Completion,
11831 push_to_history: bool,
11832 cx: &mut ViewContext<Editor>,
11833 ) -> Task<Result<Option<language::Transaction>>>;
11834
11835 fn is_completion_trigger(
11836 &self,
11837 buffer: &Model<Buffer>,
11838 position: language::Anchor,
11839 text: &str,
11840 trigger_in_words: bool,
11841 cx: &mut ViewContext<Editor>,
11842 ) -> bool;
11843}
11844
11845fn snippet_completions(
11846 project: &Project,
11847 buffer: &Model<Buffer>,
11848 buffer_position: text::Anchor,
11849 cx: &mut AppContext,
11850) -> Vec<Completion> {
11851 let language = buffer.read(cx).language_at(buffer_position);
11852 let language_name = language.as_ref().map(|language| language.lsp_id());
11853 let snippet_store = project.snippets().read(cx);
11854 let snippets = snippet_store.snippets_for(language_name, cx);
11855
11856 if snippets.is_empty() {
11857 return vec![];
11858 }
11859 let snapshot = buffer.read(cx).text_snapshot();
11860 let chunks = snapshot.reversed_chunks_in_range(text::Anchor::MIN..buffer_position);
11861
11862 let mut lines = chunks.lines();
11863 let Some(line_at) = lines.next().filter(|line| !line.is_empty()) else {
11864 return vec![];
11865 };
11866
11867 let scope = language.map(|language| language.default_scope());
11868 let mut last_word = line_at
11869 .chars()
11870 .rev()
11871 .take_while(|c| char_kind(&scope, *c) == CharKind::Word)
11872 .collect::<String>();
11873 last_word = last_word.chars().rev().collect();
11874 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
11875 let to_lsp = |point: &text::Anchor| {
11876 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
11877 point_to_lsp(end)
11878 };
11879 let lsp_end = to_lsp(&buffer_position);
11880 snippets
11881 .into_iter()
11882 .filter_map(|snippet| {
11883 let matching_prefix = snippet
11884 .prefix
11885 .iter()
11886 .find(|prefix| prefix.starts_with(&last_word))?;
11887 let start = as_offset - last_word.len();
11888 let start = snapshot.anchor_before(start);
11889 let range = start..buffer_position;
11890 let lsp_start = to_lsp(&start);
11891 let lsp_range = lsp::Range {
11892 start: lsp_start,
11893 end: lsp_end,
11894 };
11895 Some(Completion {
11896 old_range: range,
11897 new_text: snippet.body.clone(),
11898 label: CodeLabel {
11899 text: matching_prefix.clone(),
11900 runs: vec![],
11901 filter_range: 0..matching_prefix.len(),
11902 },
11903 server_id: LanguageServerId(usize::MAX),
11904 documentation: snippet
11905 .description
11906 .clone()
11907 .map(|description| Documentation::SingleLine(description)),
11908 lsp_completion: lsp::CompletionItem {
11909 label: snippet.prefix.first().unwrap().clone(),
11910 kind: Some(CompletionItemKind::SNIPPET),
11911 label_details: snippet.description.as_ref().map(|description| {
11912 lsp::CompletionItemLabelDetails {
11913 detail: Some(description.clone()),
11914 description: None,
11915 }
11916 }),
11917 insert_text_format: Some(InsertTextFormat::SNIPPET),
11918 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11919 lsp::InsertReplaceEdit {
11920 new_text: snippet.body.clone(),
11921 insert: lsp_range,
11922 replace: lsp_range,
11923 },
11924 )),
11925 filter_text: Some(snippet.body.clone()),
11926 sort_text: Some(char::MAX.to_string()),
11927 ..Default::default()
11928 },
11929 confirm: None,
11930 show_new_completions_on_confirm: false,
11931 })
11932 })
11933 .collect()
11934}
11935
11936impl CompletionProvider for Model<Project> {
11937 fn completions(
11938 &self,
11939 buffer: &Model<Buffer>,
11940 buffer_position: text::Anchor,
11941 options: CompletionContext,
11942 cx: &mut ViewContext<Editor>,
11943 ) -> Task<Result<Vec<Completion>>> {
11944 self.update(cx, |project, cx| {
11945 let snippets = snippet_completions(project, buffer, buffer_position, cx);
11946 let project_completions = project.completions(&buffer, buffer_position, options, cx);
11947 cx.background_executor().spawn(async move {
11948 let mut completions = project_completions.await?;
11949 //let snippets = snippets.into_iter().;
11950 completions.extend(snippets);
11951 Ok(completions)
11952 })
11953 })
11954 }
11955
11956 fn resolve_completions(
11957 &self,
11958 buffer: Model<Buffer>,
11959 completion_indices: Vec<usize>,
11960 completions: Arc<RwLock<Box<[Completion]>>>,
11961 cx: &mut ViewContext<Editor>,
11962 ) -> Task<Result<bool>> {
11963 self.update(cx, |project, cx| {
11964 project.resolve_completions(buffer, completion_indices, completions, cx)
11965 })
11966 }
11967
11968 fn apply_additional_edits_for_completion(
11969 &self,
11970 buffer: Model<Buffer>,
11971 completion: Completion,
11972 push_to_history: bool,
11973 cx: &mut ViewContext<Editor>,
11974 ) -> Task<Result<Option<language::Transaction>>> {
11975 self.update(cx, |project, cx| {
11976 project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
11977 })
11978 }
11979
11980 fn is_completion_trigger(
11981 &self,
11982 buffer: &Model<Buffer>,
11983 position: language::Anchor,
11984 text: &str,
11985 trigger_in_words: bool,
11986 cx: &mut ViewContext<Editor>,
11987 ) -> bool {
11988 if !EditorSettings::get_global(cx).show_completions_on_input {
11989 return false;
11990 }
11991
11992 let mut chars = text.chars();
11993 let char = if let Some(char) = chars.next() {
11994 char
11995 } else {
11996 return false;
11997 };
11998 if chars.next().is_some() {
11999 return false;
12000 }
12001
12002 let buffer = buffer.read(cx);
12003 let scope = buffer.snapshot().language_scope_at(position);
12004 if trigger_in_words && char_kind(&scope, char) == CharKind::Word {
12005 return true;
12006 }
12007
12008 buffer
12009 .completion_triggers()
12010 .iter()
12011 .any(|string| string == text)
12012 }
12013}
12014
12015fn inlay_hint_settings(
12016 location: Anchor,
12017 snapshot: &MultiBufferSnapshot,
12018 cx: &mut ViewContext<'_, Editor>,
12019) -> InlayHintSettings {
12020 let file = snapshot.file_at(location);
12021 let language = snapshot.language_at(location);
12022 let settings = all_language_settings(file, cx);
12023 settings
12024 .language(language.map(|l| l.name()).as_deref())
12025 .inlay_hints
12026}
12027
12028fn consume_contiguous_rows(
12029 contiguous_row_selections: &mut Vec<Selection<Point>>,
12030 selection: &Selection<Point>,
12031 display_map: &DisplaySnapshot,
12032 selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
12033) -> (MultiBufferRow, MultiBufferRow) {
12034 contiguous_row_selections.push(selection.clone());
12035 let start_row = MultiBufferRow(selection.start.row);
12036 let mut end_row = ending_row(selection, display_map);
12037
12038 while let Some(next_selection) = selections.peek() {
12039 if next_selection.start.row <= end_row.0 {
12040 end_row = ending_row(next_selection, display_map);
12041 contiguous_row_selections.push(selections.next().unwrap().clone());
12042 } else {
12043 break;
12044 }
12045 }
12046 (start_row, end_row)
12047}
12048
12049fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
12050 if next_selection.end.column > 0 || next_selection.is_empty() {
12051 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
12052 } else {
12053 MultiBufferRow(next_selection.end.row)
12054 }
12055}
12056
12057impl EditorSnapshot {
12058 pub fn remote_selections_in_range<'a>(
12059 &'a self,
12060 range: &'a Range<Anchor>,
12061 collaboration_hub: &dyn CollaborationHub,
12062 cx: &'a AppContext,
12063 ) -> impl 'a + Iterator<Item = RemoteSelection> {
12064 let participant_names = collaboration_hub.user_names(cx);
12065 let participant_indices = collaboration_hub.user_participant_indices(cx);
12066 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
12067 let collaborators_by_replica_id = collaborators_by_peer_id
12068 .iter()
12069 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
12070 .collect::<HashMap<_, _>>();
12071 self.buffer_snapshot
12072 .selections_in_range(range, false)
12073 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
12074 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
12075 let participant_index = participant_indices.get(&collaborator.user_id).copied();
12076 let user_name = participant_names.get(&collaborator.user_id).cloned();
12077 Some(RemoteSelection {
12078 replica_id,
12079 selection,
12080 cursor_shape,
12081 line_mode,
12082 participant_index,
12083 peer_id: collaborator.peer_id,
12084 user_name,
12085 })
12086 })
12087 }
12088
12089 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
12090 self.display_snapshot.buffer_snapshot.language_at(position)
12091 }
12092
12093 pub fn is_focused(&self) -> bool {
12094 self.is_focused
12095 }
12096
12097 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
12098 self.placeholder_text.as_ref()
12099 }
12100
12101 pub fn scroll_position(&self) -> gpui::Point<f32> {
12102 self.scroll_anchor.scroll_position(&self.display_snapshot)
12103 }
12104
12105 pub fn gutter_dimensions(
12106 &self,
12107 font_id: FontId,
12108 font_size: Pixels,
12109 em_width: Pixels,
12110 max_line_number_width: Pixels,
12111 cx: &AppContext,
12112 ) -> GutterDimensions {
12113 if !self.show_gutter {
12114 return GutterDimensions::default();
12115 }
12116 let descent = cx.text_system().descent(font_id, font_size);
12117
12118 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
12119 matches!(
12120 ProjectSettings::get_global(cx).git.git_gutter,
12121 Some(GitGutterSetting::TrackedFiles)
12122 )
12123 });
12124 let gutter_settings = EditorSettings::get_global(cx).gutter;
12125 let show_line_numbers = self
12126 .show_line_numbers
12127 .unwrap_or(gutter_settings.line_numbers);
12128 let line_gutter_width = if show_line_numbers {
12129 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
12130 let min_width_for_number_on_gutter = em_width * 4.0;
12131 max_line_number_width.max(min_width_for_number_on_gutter)
12132 } else {
12133 0.0.into()
12134 };
12135
12136 let show_code_actions = self
12137 .show_code_actions
12138 .unwrap_or(gutter_settings.code_actions);
12139
12140 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
12141
12142 let git_blame_entries_width = self
12143 .render_git_blame_gutter
12144 .then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
12145
12146 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
12147 left_padding += if show_code_actions || show_runnables {
12148 em_width * 3.0
12149 } else if show_git_gutter && show_line_numbers {
12150 em_width * 2.0
12151 } else if show_git_gutter || show_line_numbers {
12152 em_width
12153 } else {
12154 px(0.)
12155 };
12156
12157 let right_padding = if gutter_settings.folds && show_line_numbers {
12158 em_width * 4.0
12159 } else if gutter_settings.folds {
12160 em_width * 3.0
12161 } else if show_line_numbers {
12162 em_width
12163 } else {
12164 px(0.)
12165 };
12166
12167 GutterDimensions {
12168 left_padding,
12169 right_padding,
12170 width: line_gutter_width + left_padding + right_padding,
12171 margin: -descent,
12172 git_blame_entries_width,
12173 }
12174 }
12175
12176 pub fn render_fold_toggle(
12177 &self,
12178 buffer_row: MultiBufferRow,
12179 row_contains_cursor: bool,
12180 editor: View<Editor>,
12181 cx: &mut WindowContext,
12182 ) -> Option<AnyElement> {
12183 let folded = self.is_line_folded(buffer_row);
12184
12185 if let Some(crease) = self
12186 .crease_snapshot
12187 .query_row(buffer_row, &self.buffer_snapshot)
12188 {
12189 let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
12190 if folded {
12191 editor.update(cx, |editor, cx| {
12192 editor.fold_at(&crate::FoldAt { buffer_row }, cx)
12193 });
12194 } else {
12195 editor.update(cx, |editor, cx| {
12196 editor.unfold_at(&crate::UnfoldAt { buffer_row }, cx)
12197 });
12198 }
12199 });
12200
12201 Some((crease.render_toggle)(
12202 buffer_row,
12203 folded,
12204 toggle_callback,
12205 cx,
12206 ))
12207 } else if folded
12208 || (self.starts_indent(buffer_row) && (row_contains_cursor || self.gutter_hovered))
12209 {
12210 Some(
12211 Disclosure::new(("indent-fold-indicator", buffer_row.0), !folded)
12212 .selected(folded)
12213 .on_click(cx.listener_for(&editor, move |this, _e, cx| {
12214 if folded {
12215 this.unfold_at(&UnfoldAt { buffer_row }, cx);
12216 } else {
12217 this.fold_at(&FoldAt { buffer_row }, cx);
12218 }
12219 }))
12220 .into_any_element(),
12221 )
12222 } else {
12223 None
12224 }
12225 }
12226
12227 pub fn render_crease_trailer(
12228 &self,
12229 buffer_row: MultiBufferRow,
12230 cx: &mut WindowContext,
12231 ) -> Option<AnyElement> {
12232 let folded = self.is_line_folded(buffer_row);
12233 let crease = self
12234 .crease_snapshot
12235 .query_row(buffer_row, &self.buffer_snapshot)?;
12236 Some((crease.render_trailer)(buffer_row, folded, cx))
12237 }
12238}
12239
12240impl Deref for EditorSnapshot {
12241 type Target = DisplaySnapshot;
12242
12243 fn deref(&self) -> &Self::Target {
12244 &self.display_snapshot
12245 }
12246}
12247
12248#[derive(Clone, Debug, PartialEq, Eq)]
12249pub enum EditorEvent {
12250 InputIgnored {
12251 text: Arc<str>,
12252 },
12253 InputHandled {
12254 utf16_range_to_replace: Option<Range<isize>>,
12255 text: Arc<str>,
12256 },
12257 ExcerptsAdded {
12258 buffer: Model<Buffer>,
12259 predecessor: ExcerptId,
12260 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
12261 },
12262 ExcerptsRemoved {
12263 ids: Vec<ExcerptId>,
12264 },
12265 ExcerptsEdited {
12266 ids: Vec<ExcerptId>,
12267 },
12268 ExcerptsExpanded {
12269 ids: Vec<ExcerptId>,
12270 },
12271 BufferEdited,
12272 Edited {
12273 transaction_id: clock::Lamport,
12274 },
12275 Reparsed(BufferId),
12276 Focused,
12277 FocusedIn,
12278 Blurred,
12279 DirtyChanged,
12280 Saved,
12281 TitleChanged,
12282 DiffBaseChanged,
12283 SelectionsChanged {
12284 local: bool,
12285 },
12286 ScrollPositionChanged {
12287 local: bool,
12288 autoscroll: bool,
12289 },
12290 Closed,
12291 TransactionUndone {
12292 transaction_id: clock::Lamport,
12293 },
12294 TransactionBegun {
12295 transaction_id: clock::Lamport,
12296 },
12297}
12298
12299impl EventEmitter<EditorEvent> for Editor {}
12300
12301impl FocusableView for Editor {
12302 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
12303 self.focus_handle.clone()
12304 }
12305}
12306
12307impl Render for Editor {
12308 fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
12309 let settings = ThemeSettings::get_global(cx);
12310
12311 let text_style = match self.mode {
12312 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
12313 color: cx.theme().colors().editor_foreground,
12314 font_family: settings.ui_font.family.clone(),
12315 font_features: settings.ui_font.features.clone(),
12316 font_size: rems(0.875).into(),
12317 font_weight: settings.ui_font.weight,
12318 font_style: FontStyle::Normal,
12319 line_height: relative(settings.buffer_line_height.value()),
12320 background_color: None,
12321 underline: None,
12322 strikethrough: None,
12323 white_space: WhiteSpace::Normal,
12324 },
12325 EditorMode::Full => TextStyle {
12326 color: cx.theme().colors().editor_foreground,
12327 font_family: settings.buffer_font.family.clone(),
12328 font_features: settings.buffer_font.features.clone(),
12329 font_size: settings.buffer_font_size(cx).into(),
12330 font_weight: settings.buffer_font.weight,
12331 font_style: FontStyle::Normal,
12332 line_height: relative(settings.buffer_line_height.value()),
12333 background_color: None,
12334 underline: None,
12335 strikethrough: None,
12336 white_space: WhiteSpace::Normal,
12337 },
12338 };
12339
12340 let background = match self.mode {
12341 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
12342 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
12343 EditorMode::Full => cx.theme().colors().editor_background,
12344 };
12345
12346 EditorElement::new(
12347 cx.view(),
12348 EditorStyle {
12349 background,
12350 local_player: cx.theme().players().local(),
12351 text: text_style,
12352 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
12353 syntax: cx.theme().syntax().clone(),
12354 status: cx.theme().status().clone(),
12355 inlay_hints_style: HighlightStyle {
12356 color: Some(cx.theme().status().hint),
12357 ..HighlightStyle::default()
12358 },
12359 suggestions_style: HighlightStyle {
12360 color: Some(cx.theme().status().predictive),
12361 ..HighlightStyle::default()
12362 },
12363 },
12364 )
12365 }
12366}
12367
12368impl ViewInputHandler for Editor {
12369 fn text_for_range(
12370 &mut self,
12371 range_utf16: Range<usize>,
12372 cx: &mut ViewContext<Self>,
12373 ) -> Option<String> {
12374 Some(
12375 self.buffer
12376 .read(cx)
12377 .read(cx)
12378 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
12379 .collect(),
12380 )
12381 }
12382
12383 fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
12384 // Prevent the IME menu from appearing when holding down an alphabetic key
12385 // while input is disabled.
12386 if !self.input_enabled {
12387 return None;
12388 }
12389
12390 let range = self.selections.newest::<OffsetUtf16>(cx).range();
12391 Some(range.start.0..range.end.0)
12392 }
12393
12394 fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
12395 let snapshot = self.buffer.read(cx).read(cx);
12396 let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
12397 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
12398 }
12399
12400 fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
12401 self.clear_highlights::<InputComposition>(cx);
12402 self.ime_transaction.take();
12403 }
12404
12405 fn replace_text_in_range(
12406 &mut self,
12407 range_utf16: Option<Range<usize>>,
12408 text: &str,
12409 cx: &mut ViewContext<Self>,
12410 ) {
12411 if !self.input_enabled {
12412 cx.emit(EditorEvent::InputIgnored { text: text.into() });
12413 return;
12414 }
12415
12416 self.transact(cx, |this, cx| {
12417 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
12418 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
12419 Some(this.selection_replacement_ranges(range_utf16, cx))
12420 } else {
12421 this.marked_text_ranges(cx)
12422 };
12423
12424 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
12425 let newest_selection_id = this.selections.newest_anchor().id;
12426 this.selections
12427 .all::<OffsetUtf16>(cx)
12428 .iter()
12429 .zip(ranges_to_replace.iter())
12430 .find_map(|(selection, range)| {
12431 if selection.id == newest_selection_id {
12432 Some(
12433 (range.start.0 as isize - selection.head().0 as isize)
12434 ..(range.end.0 as isize - selection.head().0 as isize),
12435 )
12436 } else {
12437 None
12438 }
12439 })
12440 });
12441
12442 cx.emit(EditorEvent::InputHandled {
12443 utf16_range_to_replace: range_to_replace,
12444 text: text.into(),
12445 });
12446
12447 if let Some(new_selected_ranges) = new_selected_ranges {
12448 this.change_selections(None, cx, |selections| {
12449 selections.select_ranges(new_selected_ranges)
12450 });
12451 this.backspace(&Default::default(), cx);
12452 }
12453
12454 this.handle_input(text, cx);
12455 });
12456
12457 if let Some(transaction) = self.ime_transaction {
12458 self.buffer.update(cx, |buffer, cx| {
12459 buffer.group_until_transaction(transaction, cx);
12460 });
12461 }
12462
12463 self.unmark_text(cx);
12464 }
12465
12466 fn replace_and_mark_text_in_range(
12467 &mut self,
12468 range_utf16: Option<Range<usize>>,
12469 text: &str,
12470 new_selected_range_utf16: Option<Range<usize>>,
12471 cx: &mut ViewContext<Self>,
12472 ) {
12473 if !self.input_enabled {
12474 return;
12475 }
12476
12477 let transaction = self.transact(cx, |this, cx| {
12478 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
12479 let snapshot = this.buffer.read(cx).read(cx);
12480 if let Some(relative_range_utf16) = range_utf16.as_ref() {
12481 for marked_range in &mut marked_ranges {
12482 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
12483 marked_range.start.0 += relative_range_utf16.start;
12484 marked_range.start =
12485 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
12486 marked_range.end =
12487 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
12488 }
12489 }
12490 Some(marked_ranges)
12491 } else if let Some(range_utf16) = range_utf16 {
12492 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
12493 Some(this.selection_replacement_ranges(range_utf16, cx))
12494 } else {
12495 None
12496 };
12497
12498 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
12499 let newest_selection_id = this.selections.newest_anchor().id;
12500 this.selections
12501 .all::<OffsetUtf16>(cx)
12502 .iter()
12503 .zip(ranges_to_replace.iter())
12504 .find_map(|(selection, range)| {
12505 if selection.id == newest_selection_id {
12506 Some(
12507 (range.start.0 as isize - selection.head().0 as isize)
12508 ..(range.end.0 as isize - selection.head().0 as isize),
12509 )
12510 } else {
12511 None
12512 }
12513 })
12514 });
12515
12516 cx.emit(EditorEvent::InputHandled {
12517 utf16_range_to_replace: range_to_replace,
12518 text: text.into(),
12519 });
12520
12521 if let Some(ranges) = ranges_to_replace {
12522 this.change_selections(None, cx, |s| s.select_ranges(ranges));
12523 }
12524
12525 let marked_ranges = {
12526 let snapshot = this.buffer.read(cx).read(cx);
12527 this.selections
12528 .disjoint_anchors()
12529 .iter()
12530 .map(|selection| {
12531 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
12532 })
12533 .collect::<Vec<_>>()
12534 };
12535
12536 if text.is_empty() {
12537 this.unmark_text(cx);
12538 } else {
12539 this.highlight_text::<InputComposition>(
12540 marked_ranges.clone(),
12541 HighlightStyle {
12542 underline: Some(UnderlineStyle {
12543 thickness: px(1.),
12544 color: None,
12545 wavy: false,
12546 }),
12547 ..Default::default()
12548 },
12549 cx,
12550 );
12551 }
12552
12553 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
12554 let use_autoclose = this.use_autoclose;
12555 let use_auto_surround = this.use_auto_surround;
12556 this.set_use_autoclose(false);
12557 this.set_use_auto_surround(false);
12558 this.handle_input(text, cx);
12559 this.set_use_autoclose(use_autoclose);
12560 this.set_use_auto_surround(use_auto_surround);
12561
12562 if let Some(new_selected_range) = new_selected_range_utf16 {
12563 let snapshot = this.buffer.read(cx).read(cx);
12564 let new_selected_ranges = marked_ranges
12565 .into_iter()
12566 .map(|marked_range| {
12567 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
12568 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
12569 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
12570 snapshot.clip_offset_utf16(new_start, Bias::Left)
12571 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
12572 })
12573 .collect::<Vec<_>>();
12574
12575 drop(snapshot);
12576 this.change_selections(None, cx, |selections| {
12577 selections.select_ranges(new_selected_ranges)
12578 });
12579 }
12580 });
12581
12582 self.ime_transaction = self.ime_transaction.or(transaction);
12583 if let Some(transaction) = self.ime_transaction {
12584 self.buffer.update(cx, |buffer, cx| {
12585 buffer.group_until_transaction(transaction, cx);
12586 });
12587 }
12588
12589 if self.text_highlights::<InputComposition>(cx).is_none() {
12590 self.ime_transaction.take();
12591 }
12592 }
12593
12594 fn bounds_for_range(
12595 &mut self,
12596 range_utf16: Range<usize>,
12597 element_bounds: gpui::Bounds<Pixels>,
12598 cx: &mut ViewContext<Self>,
12599 ) -> Option<gpui::Bounds<Pixels>> {
12600 let text_layout_details = self.text_layout_details(cx);
12601 let style = &text_layout_details.editor_style;
12602 let font_id = cx.text_system().resolve_font(&style.text.font());
12603 let font_size = style.text.font_size.to_pixels(cx.rem_size());
12604 let line_height = style.text.line_height_in_pixels(cx.rem_size());
12605
12606 let em_width = cx
12607 .text_system()
12608 .typographic_bounds(font_id, font_size, 'm')
12609 .unwrap()
12610 .size
12611 .width;
12612
12613 let snapshot = self.snapshot(cx);
12614 let scroll_position = snapshot.scroll_position();
12615 let scroll_left = scroll_position.x * em_width;
12616
12617 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
12618 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
12619 + self.gutter_dimensions.width;
12620 let y = line_height * (start.row().as_f32() - scroll_position.y);
12621
12622 Some(Bounds {
12623 origin: element_bounds.origin + point(x, y),
12624 size: size(em_width, line_height),
12625 })
12626 }
12627}
12628
12629trait SelectionExt {
12630 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
12631 fn spanned_rows(
12632 &self,
12633 include_end_if_at_line_start: bool,
12634 map: &DisplaySnapshot,
12635 ) -> Range<MultiBufferRow>;
12636}
12637
12638impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
12639 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
12640 let start = self
12641 .start
12642 .to_point(&map.buffer_snapshot)
12643 .to_display_point(map);
12644 let end = self
12645 .end
12646 .to_point(&map.buffer_snapshot)
12647 .to_display_point(map);
12648 if self.reversed {
12649 end..start
12650 } else {
12651 start..end
12652 }
12653 }
12654
12655 fn spanned_rows(
12656 &self,
12657 include_end_if_at_line_start: bool,
12658 map: &DisplaySnapshot,
12659 ) -> Range<MultiBufferRow> {
12660 let start = self.start.to_point(&map.buffer_snapshot);
12661 let mut end = self.end.to_point(&map.buffer_snapshot);
12662 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
12663 end.row -= 1;
12664 }
12665
12666 let buffer_start = map.prev_line_boundary(start).0;
12667 let buffer_end = map.next_line_boundary(end).0;
12668 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
12669 }
12670}
12671
12672impl<T: InvalidationRegion> InvalidationStack<T> {
12673 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
12674 where
12675 S: Clone + ToOffset,
12676 {
12677 while let Some(region) = self.last() {
12678 let all_selections_inside_invalidation_ranges =
12679 if selections.len() == region.ranges().len() {
12680 selections
12681 .iter()
12682 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
12683 .all(|(selection, invalidation_range)| {
12684 let head = selection.head().to_offset(buffer);
12685 invalidation_range.start <= head && invalidation_range.end >= head
12686 })
12687 } else {
12688 false
12689 };
12690
12691 if all_selections_inside_invalidation_ranges {
12692 break;
12693 } else {
12694 self.pop();
12695 }
12696 }
12697 }
12698}
12699
12700impl<T> Default for InvalidationStack<T> {
12701 fn default() -> Self {
12702 Self(Default::default())
12703 }
12704}
12705
12706impl<T> Deref for InvalidationStack<T> {
12707 type Target = Vec<T>;
12708
12709 fn deref(&self) -> &Self::Target {
12710 &self.0
12711 }
12712}
12713
12714impl<T> DerefMut for InvalidationStack<T> {
12715 fn deref_mut(&mut self) -> &mut Self::Target {
12716 &mut self.0
12717 }
12718}
12719
12720impl InvalidationRegion for SnippetState {
12721 fn ranges(&self) -> &[Range<Anchor>] {
12722 &self.ranges[self.active_index]
12723 }
12724}
12725
12726pub fn diagnostic_block_renderer(
12727 diagnostic: Diagnostic,
12728 max_message_rows: Option<u8>,
12729 allow_closing: bool,
12730 _is_valid: bool,
12731) -> RenderBlock {
12732 let (text_without_backticks, code_ranges) =
12733 highlight_diagnostic_message(&diagnostic, max_message_rows);
12734
12735 Box::new(move |cx: &mut BlockContext| {
12736 let group_id: SharedString = cx.transform_block_id.to_string().into();
12737
12738 let mut text_style = cx.text_style().clone();
12739 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
12740 let theme_settings = ThemeSettings::get_global(cx);
12741 text_style.font_family = theme_settings.buffer_font.family.clone();
12742 text_style.font_style = theme_settings.buffer_font.style;
12743 text_style.font_features = theme_settings.buffer_font.features.clone();
12744 text_style.font_weight = theme_settings.buffer_font.weight;
12745
12746 let multi_line_diagnostic = diagnostic.message.contains('\n');
12747
12748 let buttons = |diagnostic: &Diagnostic, block_id: TransformBlockId| {
12749 if multi_line_diagnostic {
12750 v_flex()
12751 } else {
12752 h_flex()
12753 }
12754 .when(allow_closing, |div| {
12755 div.children(diagnostic.is_primary.then(|| {
12756 IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
12757 .icon_color(Color::Muted)
12758 .size(ButtonSize::Compact)
12759 .style(ButtonStyle::Transparent)
12760 .visible_on_hover(group_id.clone())
12761 .on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
12762 .tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
12763 }))
12764 })
12765 .child(
12766 IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
12767 .icon_color(Color::Muted)
12768 .size(ButtonSize::Compact)
12769 .style(ButtonStyle::Transparent)
12770 .visible_on_hover(group_id.clone())
12771 .on_click({
12772 let message = diagnostic.message.clone();
12773 move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
12774 })
12775 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
12776 )
12777 };
12778
12779 let icon_size = buttons(&diagnostic, cx.transform_block_id)
12780 .into_any_element()
12781 .layout_as_root(AvailableSpace::min_size(), cx);
12782
12783 h_flex()
12784 .id(cx.transform_block_id)
12785 .group(group_id.clone())
12786 .relative()
12787 .size_full()
12788 .pl(cx.gutter_dimensions.width)
12789 .w(cx.max_width + cx.gutter_dimensions.width)
12790 .child(
12791 div()
12792 .flex()
12793 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
12794 .flex_shrink(),
12795 )
12796 .child(buttons(&diagnostic, cx.transform_block_id))
12797 .child(div().flex().flex_shrink_0().child(
12798 StyledText::new(text_without_backticks.clone()).with_highlights(
12799 &text_style,
12800 code_ranges.iter().map(|range| {
12801 (
12802 range.clone(),
12803 HighlightStyle {
12804 font_weight: Some(FontWeight::BOLD),
12805 ..Default::default()
12806 },
12807 )
12808 }),
12809 ),
12810 ))
12811 .into_any_element()
12812 })
12813}
12814
12815pub fn highlight_diagnostic_message(
12816 diagnostic: &Diagnostic,
12817 mut max_message_rows: Option<u8>,
12818) -> (SharedString, Vec<Range<usize>>) {
12819 let mut text_without_backticks = String::new();
12820 let mut code_ranges = Vec::new();
12821
12822 if let Some(source) = &diagnostic.source {
12823 text_without_backticks.push_str(&source);
12824 code_ranges.push(0..source.len());
12825 text_without_backticks.push_str(": ");
12826 }
12827
12828 let mut prev_offset = 0;
12829 let mut in_code_block = false;
12830 let mut newline_indices = diagnostic
12831 .message
12832 .match_indices('\n')
12833 .map(|(ix, _)| ix)
12834 .fuse()
12835 .peekable();
12836 for (ix, _) in diagnostic
12837 .message
12838 .match_indices('`')
12839 .chain([(diagnostic.message.len(), "")])
12840 {
12841 let mut trimmed_ix = ix;
12842 while let Some(newline_index) = newline_indices.peek() {
12843 if *newline_index < ix {
12844 if let Some(rows_left) = &mut max_message_rows {
12845 if *rows_left == 0 {
12846 trimmed_ix = newline_index.saturating_sub(1);
12847 break;
12848 } else {
12849 *rows_left -= 1;
12850 }
12851 }
12852 let _ = newline_indices.next();
12853 } else {
12854 break;
12855 }
12856 }
12857 let prev_len = text_without_backticks.len();
12858 let new_text = &diagnostic.message[prev_offset..trimmed_ix];
12859 text_without_backticks.push_str(new_text);
12860 if in_code_block {
12861 code_ranges.push(prev_len..text_without_backticks.len());
12862 }
12863 prev_offset = trimmed_ix + 1;
12864 in_code_block = !in_code_block;
12865 if trimmed_ix != ix {
12866 text_without_backticks.push_str("...");
12867 break;
12868 }
12869 }
12870
12871 (text_without_backticks.into(), code_ranges)
12872}
12873
12874fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
12875 match severity {
12876 DiagnosticSeverity::ERROR => colors.error,
12877 DiagnosticSeverity::WARNING => colors.warning,
12878 DiagnosticSeverity::INFORMATION => colors.info,
12879 DiagnosticSeverity::HINT => colors.info,
12880 _ => colors.ignored,
12881 }
12882}
12883
12884pub fn styled_runs_for_code_label<'a>(
12885 label: &'a CodeLabel,
12886 syntax_theme: &'a theme::SyntaxTheme,
12887) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
12888 let fade_out = HighlightStyle {
12889 fade_out: Some(0.35),
12890 ..Default::default()
12891 };
12892
12893 let mut prev_end = label.filter_range.end;
12894 label
12895 .runs
12896 .iter()
12897 .enumerate()
12898 .flat_map(move |(ix, (range, highlight_id))| {
12899 let style = if let Some(style) = highlight_id.style(syntax_theme) {
12900 style
12901 } else {
12902 return Default::default();
12903 };
12904 let mut muted_style = style;
12905 muted_style.highlight(fade_out);
12906
12907 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
12908 if range.start >= label.filter_range.end {
12909 if range.start > prev_end {
12910 runs.push((prev_end..range.start, fade_out));
12911 }
12912 runs.push((range.clone(), muted_style));
12913 } else if range.end <= label.filter_range.end {
12914 runs.push((range.clone(), style));
12915 } else {
12916 runs.push((range.start..label.filter_range.end, style));
12917 runs.push((label.filter_range.end..range.end, muted_style));
12918 }
12919 prev_end = cmp::max(prev_end, range.end);
12920
12921 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
12922 runs.push((prev_end..label.text.len(), fade_out));
12923 }
12924
12925 runs
12926 })
12927}
12928
12929pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
12930 let mut prev_index = 0;
12931 let mut prev_codepoint: Option<char> = None;
12932 text.char_indices()
12933 .chain([(text.len(), '\0')])
12934 .filter_map(move |(index, codepoint)| {
12935 let prev_codepoint = prev_codepoint.replace(codepoint)?;
12936 let is_boundary = index == text.len()
12937 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
12938 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
12939 if is_boundary {
12940 let chunk = &text[prev_index..index];
12941 prev_index = index;
12942 Some(chunk)
12943 } else {
12944 None
12945 }
12946 })
12947}
12948
12949pub trait RangeToAnchorExt {
12950 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
12951}
12952
12953impl<T: ToOffset> RangeToAnchorExt for Range<T> {
12954 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
12955 let start_offset = self.start.to_offset(snapshot);
12956 let end_offset = self.end.to_offset(snapshot);
12957 if start_offset == end_offset {
12958 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
12959 } else {
12960 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
12961 }
12962 }
12963}
12964
12965pub trait RowExt {
12966 fn as_f32(&self) -> f32;
12967
12968 fn next_row(&self) -> Self;
12969
12970 fn previous_row(&self) -> Self;
12971
12972 fn minus(&self, other: Self) -> u32;
12973}
12974
12975impl RowExt for DisplayRow {
12976 fn as_f32(&self) -> f32 {
12977 self.0 as f32
12978 }
12979
12980 fn next_row(&self) -> Self {
12981 Self(self.0 + 1)
12982 }
12983
12984 fn previous_row(&self) -> Self {
12985 Self(self.0.saturating_sub(1))
12986 }
12987
12988 fn minus(&self, other: Self) -> u32 {
12989 self.0 - other.0
12990 }
12991}
12992
12993impl RowExt for MultiBufferRow {
12994 fn as_f32(&self) -> f32 {
12995 self.0 as f32
12996 }
12997
12998 fn next_row(&self) -> Self {
12999 Self(self.0 + 1)
13000 }
13001
13002 fn previous_row(&self) -> Self {
13003 Self(self.0.saturating_sub(1))
13004 }
13005
13006 fn minus(&self, other: Self) -> u32 {
13007 self.0 - other.0
13008 }
13009}
13010
13011trait RowRangeExt {
13012 type Row;
13013
13014 fn len(&self) -> usize;
13015
13016 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
13017}
13018
13019impl RowRangeExt for Range<MultiBufferRow> {
13020 type Row = MultiBufferRow;
13021
13022 fn len(&self) -> usize {
13023 (self.end.0 - self.start.0) as usize
13024 }
13025
13026 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
13027 (self.start.0..self.end.0).map(MultiBufferRow)
13028 }
13029}
13030
13031impl RowRangeExt for Range<DisplayRow> {
13032 type Row = DisplayRow;
13033
13034 fn len(&self) -> usize {
13035 (self.end.0 - self.start.0) as usize
13036 }
13037
13038 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
13039 (self.start.0..self.end.0).map(DisplayRow)
13040 }
13041}
13042
13043fn hunk_status(hunk: &DiffHunk<MultiBufferRow>) -> DiffHunkStatus {
13044 if hunk.diff_base_byte_range.is_empty() {
13045 DiffHunkStatus::Added
13046 } else if hunk.associated_range.is_empty() {
13047 DiffHunkStatus::Removed
13048 } else {
13049 DiffHunkStatus::Modified
13050 }
13051}