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: text.chars().last(),
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(_), _) => CompletionTriggerKind::TRIGGER_CHARACTER,
4068 _ => CompletionTriggerKind::INVOKED,
4069 };
4070 let completion_context = CompletionContext {
4071 trigger_character: options.trigger.and_then(|c| {
4072 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4073 Some(String::from(c))
4074 } else {
4075 None
4076 }
4077 }),
4078 trigger_kind,
4079 };
4080 let completions = provider.completions(&buffer, buffer_position, completion_context, cx);
4081
4082 let id = post_inc(&mut self.next_completion_id);
4083 let task = cx.spawn(|this, mut cx| {
4084 async move {
4085 this.update(&mut cx, |this, _| {
4086 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4087 })?;
4088 let completions = completions.await.log_err();
4089 let menu = if let Some(completions) = completions {
4090 let mut menu = CompletionsMenu {
4091 id,
4092 initial_position: position,
4093 match_candidates: completions
4094 .iter()
4095 .enumerate()
4096 .map(|(id, completion)| {
4097 StringMatchCandidate::new(
4098 id,
4099 completion.label.text[completion.label.filter_range.clone()]
4100 .into(),
4101 )
4102 })
4103 .collect(),
4104 buffer: buffer.clone(),
4105 completions: Arc::new(RwLock::new(completions.into())),
4106 matches: Vec::new().into(),
4107 selected_item: 0,
4108 scroll_handle: UniformListScrollHandle::new(),
4109 selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new(
4110 DebouncedDelay::new(),
4111 )),
4112 };
4113 menu.filter(query.as_deref(), cx.background_executor().clone())
4114 .await;
4115
4116 if menu.matches.is_empty() {
4117 None
4118 } else {
4119 this.update(&mut cx, |editor, cx| {
4120 let completions = menu.completions.clone();
4121 let matches = menu.matches.clone();
4122
4123 let delay_ms = EditorSettings::get_global(cx)
4124 .completion_documentation_secondary_query_debounce;
4125 let delay = Duration::from_millis(delay_ms);
4126 editor
4127 .completion_documentation_pre_resolve_debounce
4128 .fire_new(delay, cx, |editor, cx| {
4129 CompletionsMenu::pre_resolve_completion_documentation(
4130 buffer,
4131 completions,
4132 matches,
4133 editor,
4134 cx,
4135 )
4136 });
4137 })
4138 .ok();
4139 Some(menu)
4140 }
4141 } else {
4142 None
4143 };
4144
4145 this.update(&mut cx, |this, cx| {
4146 let mut context_menu = this.context_menu.write();
4147 match context_menu.as_ref() {
4148 None => {}
4149
4150 Some(ContextMenu::Completions(prev_menu)) => {
4151 if prev_menu.id > id {
4152 return;
4153 }
4154 }
4155
4156 _ => return,
4157 }
4158
4159 if this.focus_handle.is_focused(cx) && menu.is_some() {
4160 let menu = menu.unwrap();
4161 *context_menu = Some(ContextMenu::Completions(menu));
4162 drop(context_menu);
4163 this.discard_inline_completion(false, cx);
4164 cx.notify();
4165 } else if this.completion_tasks.len() <= 1 {
4166 // If there are no more completion tasks and the last menu was
4167 // empty, we should hide it. If it was already hidden, we should
4168 // also show the copilot completion when available.
4169 drop(context_menu);
4170 if this.hide_context_menu(cx).is_none() {
4171 this.update_visible_inline_completion(cx);
4172 }
4173 }
4174 })?;
4175
4176 Ok::<_, anyhow::Error>(())
4177 }
4178 .log_err()
4179 });
4180
4181 self.completion_tasks.push((id, task));
4182 }
4183
4184 pub fn confirm_completion(
4185 &mut self,
4186 action: &ConfirmCompletion,
4187 cx: &mut ViewContext<Self>,
4188 ) -> Option<Task<Result<()>>> {
4189 use language::ToOffset as _;
4190
4191 let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
4192 menu
4193 } else {
4194 return None;
4195 };
4196
4197 let mat = completions_menu
4198 .matches
4199 .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
4200 let buffer_handle = completions_menu.buffer;
4201 let completions = completions_menu.completions.read();
4202 let completion = completions.get(mat.candidate_id)?;
4203 cx.stop_propagation();
4204
4205 let snippet;
4206 let text;
4207
4208 if completion.is_snippet() {
4209 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4210 text = snippet.as_ref().unwrap().text.clone();
4211 } else {
4212 snippet = None;
4213 text = completion.new_text.clone();
4214 };
4215 let selections = self.selections.all::<usize>(cx);
4216 let buffer = buffer_handle.read(cx);
4217 let old_range = completion.old_range.to_offset(buffer);
4218 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4219
4220 let newest_selection = self.selections.newest_anchor();
4221 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4222 return None;
4223 }
4224
4225 let lookbehind = newest_selection
4226 .start
4227 .text_anchor
4228 .to_offset(buffer)
4229 .saturating_sub(old_range.start);
4230 let lookahead = old_range
4231 .end
4232 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4233 let mut common_prefix_len = old_text
4234 .bytes()
4235 .zip(text.bytes())
4236 .take_while(|(a, b)| a == b)
4237 .count();
4238
4239 let snapshot = self.buffer.read(cx).snapshot(cx);
4240 let mut range_to_replace: Option<Range<isize>> = None;
4241 let mut ranges = Vec::new();
4242 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4243 for selection in &selections {
4244 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4245 let start = selection.start.saturating_sub(lookbehind);
4246 let end = selection.end + lookahead;
4247 if selection.id == newest_selection.id {
4248 range_to_replace = Some(
4249 ((start + common_prefix_len) as isize - selection.start as isize)
4250 ..(end as isize - selection.start as isize),
4251 );
4252 }
4253 ranges.push(start + common_prefix_len..end);
4254 } else {
4255 common_prefix_len = 0;
4256 ranges.clear();
4257 ranges.extend(selections.iter().map(|s| {
4258 if s.id == newest_selection.id {
4259 range_to_replace = Some(
4260 old_range.start.to_offset_utf16(&snapshot).0 as isize
4261 - selection.start as isize
4262 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4263 - selection.start as isize,
4264 );
4265 old_range.clone()
4266 } else {
4267 s.start..s.end
4268 }
4269 }));
4270 break;
4271 }
4272 if !self.linked_edit_ranges.is_empty() {
4273 let start_anchor = snapshot.anchor_before(selection.head());
4274 let end_anchor = snapshot.anchor_after(selection.tail());
4275 if let Some(ranges) = self
4276 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4277 {
4278 for (buffer, edits) in ranges {
4279 linked_edits.entry(buffer.clone()).or_default().extend(
4280 edits
4281 .into_iter()
4282 .map(|range| (range, text[common_prefix_len..].to_owned())),
4283 );
4284 }
4285 }
4286 }
4287 }
4288 let text = &text[common_prefix_len..];
4289
4290 cx.emit(EditorEvent::InputHandled {
4291 utf16_range_to_replace: range_to_replace,
4292 text: text.into(),
4293 });
4294
4295 self.transact(cx, |this, cx| {
4296 if let Some(mut snippet) = snippet {
4297 snippet.text = text.to_string();
4298 for tabstop in snippet.tabstops.iter_mut().flatten() {
4299 tabstop.start -= common_prefix_len as isize;
4300 tabstop.end -= common_prefix_len as isize;
4301 }
4302
4303 this.insert_snippet(&ranges, snippet, cx).log_err();
4304 } else {
4305 this.buffer.update(cx, |buffer, cx| {
4306 buffer.edit(
4307 ranges.iter().map(|range| (range.clone(), text)),
4308 this.autoindent_mode.clone(),
4309 cx,
4310 );
4311 });
4312 }
4313 for (buffer, edits) in linked_edits {
4314 buffer.update(cx, |buffer, cx| {
4315 let snapshot = buffer.snapshot();
4316 let edits = edits
4317 .into_iter()
4318 .map(|(range, text)| {
4319 use text::ToPoint as TP;
4320 let end_point = TP::to_point(&range.end, &snapshot);
4321 let start_point = TP::to_point(&range.start, &snapshot);
4322 (start_point..end_point, text)
4323 })
4324 .sorted_by_key(|(range, _)| range.start)
4325 .collect::<Vec<_>>();
4326 buffer.edit(edits, None, cx);
4327 })
4328 }
4329
4330 this.refresh_inline_completion(true, cx);
4331 });
4332
4333 if let Some(confirm) = completion.confirm.as_ref() {
4334 (confirm)(cx);
4335 }
4336
4337 if completion.show_new_completions_on_confirm {
4338 self.show_completions(&ShowCompletions { trigger: None }, cx);
4339 }
4340
4341 let provider = self.completion_provider.as_ref()?;
4342 let apply_edits = provider.apply_additional_edits_for_completion(
4343 buffer_handle,
4344 completion.clone(),
4345 true,
4346 cx,
4347 );
4348
4349 let editor_settings = EditorSettings::get_global(cx);
4350 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4351 // After the code completion is finished, users often want to know what signatures are needed.
4352 // so we should automatically call signature_help
4353 self.show_signature_help(&ShowSignatureHelp, cx);
4354 }
4355
4356 Some(cx.foreground_executor().spawn(async move {
4357 apply_edits.await?;
4358 Ok(())
4359 }))
4360 }
4361
4362 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
4363 let mut context_menu = self.context_menu.write();
4364 if let Some(ContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4365 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4366 // Toggle if we're selecting the same one
4367 *context_menu = None;
4368 cx.notify();
4369 return;
4370 } else {
4371 // Otherwise, clear it and start a new one
4372 *context_menu = None;
4373 cx.notify();
4374 }
4375 }
4376 drop(context_menu);
4377 let snapshot = self.snapshot(cx);
4378 let deployed_from_indicator = action.deployed_from_indicator;
4379 let mut task = self.code_actions_task.take();
4380 let action = action.clone();
4381 cx.spawn(|editor, mut cx| async move {
4382 while let Some(prev_task) = task {
4383 prev_task.await;
4384 task = editor.update(&mut cx, |this, _| this.code_actions_task.take())?;
4385 }
4386
4387 let spawned_test_task = editor.update(&mut cx, |editor, cx| {
4388 if editor.focus_handle.is_focused(cx) {
4389 let multibuffer_point = action
4390 .deployed_from_indicator
4391 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4392 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4393 let (buffer, buffer_row) = snapshot
4394 .buffer_snapshot
4395 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4396 .and_then(|(buffer_snapshot, range)| {
4397 editor
4398 .buffer
4399 .read(cx)
4400 .buffer(buffer_snapshot.remote_id())
4401 .map(|buffer| (buffer, range.start.row))
4402 })?;
4403 let (_, code_actions) = editor
4404 .available_code_actions
4405 .clone()
4406 .and_then(|(location, code_actions)| {
4407 let snapshot = location.buffer.read(cx).snapshot();
4408 let point_range = location.range.to_point(&snapshot);
4409 let point_range = point_range.start.row..=point_range.end.row;
4410 if point_range.contains(&buffer_row) {
4411 Some((location, code_actions))
4412 } else {
4413 None
4414 }
4415 })
4416 .unzip();
4417 let buffer_id = buffer.read(cx).remote_id();
4418 let tasks = editor
4419 .tasks
4420 .get(&(buffer_id, buffer_row))
4421 .map(|t| Arc::new(t.to_owned()));
4422 if tasks.is_none() && code_actions.is_none() {
4423 return None;
4424 }
4425
4426 editor.completion_tasks.clear();
4427 editor.discard_inline_completion(false, cx);
4428 let task_context =
4429 tasks
4430 .as_ref()
4431 .zip(editor.project.clone())
4432 .map(|(tasks, project)| {
4433 let position = Point::new(buffer_row, tasks.column);
4434 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
4435 let location = Location {
4436 buffer: buffer.clone(),
4437 range: range_start..range_start,
4438 };
4439 // Fill in the environmental variables from the tree-sitter captures
4440 let mut captured_task_variables = TaskVariables::default();
4441 for (capture_name, value) in tasks.extra_variables.clone() {
4442 captured_task_variables.insert(
4443 task::VariableName::Custom(capture_name.into()),
4444 value.clone(),
4445 );
4446 }
4447 project.update(cx, |project, cx| {
4448 project.task_context_for_location(
4449 captured_task_variables,
4450 location,
4451 cx,
4452 )
4453 })
4454 });
4455
4456 Some(cx.spawn(|editor, mut cx| async move {
4457 let task_context = match task_context {
4458 Some(task_context) => task_context.await,
4459 None => None,
4460 };
4461 let resolved_tasks =
4462 tasks.zip(task_context).map(|(tasks, task_context)| {
4463 Arc::new(ResolvedTasks {
4464 templates: tasks
4465 .templates
4466 .iter()
4467 .filter_map(|(kind, template)| {
4468 template
4469 .resolve_task(&kind.to_id_base(), &task_context)
4470 .map(|task| (kind.clone(), task))
4471 })
4472 .collect(),
4473 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4474 multibuffer_point.row,
4475 tasks.column,
4476 )),
4477 })
4478 });
4479 let spawn_straight_away = resolved_tasks
4480 .as_ref()
4481 .map_or(false, |tasks| tasks.templates.len() == 1)
4482 && code_actions
4483 .as_ref()
4484 .map_or(true, |actions| actions.is_empty());
4485 if let Some(task) = editor
4486 .update(&mut cx, |editor, cx| {
4487 *editor.context_menu.write() =
4488 Some(ContextMenu::CodeActions(CodeActionsMenu {
4489 buffer,
4490 actions: CodeActionContents {
4491 tasks: resolved_tasks,
4492 actions: code_actions,
4493 },
4494 selected_item: Default::default(),
4495 scroll_handle: UniformListScrollHandle::default(),
4496 deployed_from_indicator,
4497 }));
4498 if spawn_straight_away {
4499 if let Some(task) = editor.confirm_code_action(
4500 &ConfirmCodeAction { item_ix: Some(0) },
4501 cx,
4502 ) {
4503 cx.notify();
4504 return task;
4505 }
4506 }
4507 cx.notify();
4508 Task::ready(Ok(()))
4509 })
4510 .ok()
4511 {
4512 task.await
4513 } else {
4514 Ok(())
4515 }
4516 }))
4517 } else {
4518 Some(Task::ready(Ok(())))
4519 }
4520 })?;
4521 if let Some(task) = spawned_test_task {
4522 task.await?;
4523 }
4524
4525 Ok::<_, anyhow::Error>(())
4526 })
4527 .detach_and_log_err(cx);
4528 }
4529
4530 pub fn confirm_code_action(
4531 &mut self,
4532 action: &ConfirmCodeAction,
4533 cx: &mut ViewContext<Self>,
4534 ) -> Option<Task<Result<()>>> {
4535 let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
4536 menu
4537 } else {
4538 return None;
4539 };
4540 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4541 let action = actions_menu.actions.get(action_ix)?;
4542 let title = action.label();
4543 let buffer = actions_menu.buffer;
4544 let workspace = self.workspace()?;
4545
4546 match action {
4547 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4548 workspace.update(cx, |workspace, cx| {
4549 workspace::tasks::schedule_resolved_task(
4550 workspace,
4551 task_source_kind,
4552 resolved_task,
4553 false,
4554 cx,
4555 );
4556
4557 Some(Task::ready(Ok(())))
4558 })
4559 }
4560 CodeActionsItem::CodeAction(action) => {
4561 let apply_code_actions = workspace
4562 .read(cx)
4563 .project()
4564 .clone()
4565 .update(cx, |project, cx| {
4566 project.apply_code_action(buffer, action, true, cx)
4567 });
4568 let workspace = workspace.downgrade();
4569 Some(cx.spawn(|editor, cx| async move {
4570 let project_transaction = apply_code_actions.await?;
4571 Self::open_project_transaction(
4572 &editor,
4573 workspace,
4574 project_transaction,
4575 title,
4576 cx,
4577 )
4578 .await
4579 }))
4580 }
4581 }
4582 }
4583
4584 pub async fn open_project_transaction(
4585 this: &WeakView<Editor>,
4586 workspace: WeakView<Workspace>,
4587 transaction: ProjectTransaction,
4588 title: String,
4589 mut cx: AsyncWindowContext,
4590 ) -> Result<()> {
4591 let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
4592
4593 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4594 cx.update(|cx| {
4595 entries.sort_unstable_by_key(|(buffer, _)| {
4596 buffer.read(cx).file().map(|f| f.path().clone())
4597 });
4598 })?;
4599
4600 // If the project transaction's edits are all contained within this editor, then
4601 // avoid opening a new editor to display them.
4602
4603 if let Some((buffer, transaction)) = entries.first() {
4604 if entries.len() == 1 {
4605 let excerpt = this.update(&mut cx, |editor, cx| {
4606 editor
4607 .buffer()
4608 .read(cx)
4609 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4610 })?;
4611 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4612 if excerpted_buffer == *buffer {
4613 let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
4614 let excerpt_range = excerpt_range.to_offset(buffer);
4615 buffer
4616 .edited_ranges_for_transaction::<usize>(transaction)
4617 .all(|range| {
4618 excerpt_range.start <= range.start
4619 && excerpt_range.end >= range.end
4620 })
4621 })?;
4622
4623 if all_edits_within_excerpt {
4624 return Ok(());
4625 }
4626 }
4627 }
4628 }
4629 } else {
4630 return Ok(());
4631 }
4632
4633 let mut ranges_to_highlight = Vec::new();
4634 let excerpt_buffer = cx.new_model(|cx| {
4635 let mut multibuffer =
4636 MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
4637 for (buffer_handle, transaction) in &entries {
4638 let buffer = buffer_handle.read(cx);
4639 ranges_to_highlight.extend(
4640 multibuffer.push_excerpts_with_context_lines(
4641 buffer_handle.clone(),
4642 buffer
4643 .edited_ranges_for_transaction::<usize>(transaction)
4644 .collect(),
4645 DEFAULT_MULTIBUFFER_CONTEXT,
4646 cx,
4647 ),
4648 );
4649 }
4650 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
4651 multibuffer
4652 })?;
4653
4654 workspace.update(&mut cx, |workspace, cx| {
4655 let project = workspace.project().clone();
4656 let editor =
4657 cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, cx));
4658 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, cx);
4659 editor.update(cx, |editor, cx| {
4660 editor.highlight_background::<Self>(
4661 &ranges_to_highlight,
4662 |theme| theme.editor_highlighted_line_background,
4663 cx,
4664 );
4665 });
4666 })?;
4667
4668 Ok(())
4669 }
4670
4671 fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4672 let project = self.project.clone()?;
4673 let buffer = self.buffer.read(cx);
4674 let newest_selection = self.selections.newest_anchor().clone();
4675 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
4676 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
4677 if start_buffer != end_buffer {
4678 return None;
4679 }
4680
4681 self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
4682 cx.background_executor()
4683 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
4684 .await;
4685
4686 let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
4687 project.code_actions(&start_buffer, start..end, cx)
4688 }) {
4689 code_actions.await
4690 } else {
4691 Vec::new()
4692 };
4693
4694 this.update(&mut cx, |this, cx| {
4695 this.available_code_actions = if actions.is_empty() {
4696 None
4697 } else {
4698 Some((
4699 Location {
4700 buffer: start_buffer,
4701 range: start..end,
4702 },
4703 actions.into(),
4704 ))
4705 };
4706 cx.notify();
4707 })
4708 .log_err();
4709 }));
4710 None
4711 }
4712
4713 fn start_inline_blame_timer(&mut self, cx: &mut ViewContext<Self>) {
4714 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
4715 self.show_git_blame_inline = false;
4716
4717 self.show_git_blame_inline_delay_task = Some(cx.spawn(|this, mut cx| async move {
4718 cx.background_executor().timer(delay).await;
4719
4720 this.update(&mut cx, |this, cx| {
4721 this.show_git_blame_inline = true;
4722 cx.notify();
4723 })
4724 .log_err();
4725 }));
4726 }
4727 }
4728
4729 fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
4730 if self.pending_rename.is_some() {
4731 return None;
4732 }
4733
4734 let project = self.project.clone()?;
4735 let buffer = self.buffer.read(cx);
4736 let newest_selection = self.selections.newest_anchor().clone();
4737 let cursor_position = newest_selection.head();
4738 let (cursor_buffer, cursor_buffer_position) =
4739 buffer.text_anchor_for_position(cursor_position, cx)?;
4740 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
4741 if cursor_buffer != tail_buffer {
4742 return None;
4743 }
4744
4745 self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
4746 cx.background_executor()
4747 .timer(DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT)
4748 .await;
4749
4750 let highlights = if let Some(highlights) = project
4751 .update(&mut cx, |project, cx| {
4752 project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
4753 })
4754 .log_err()
4755 {
4756 highlights.await.log_err()
4757 } else {
4758 None
4759 };
4760
4761 if let Some(highlights) = highlights {
4762 this.update(&mut cx, |this, cx| {
4763 if this.pending_rename.is_some() {
4764 return;
4765 }
4766
4767 let buffer_id = cursor_position.buffer_id;
4768 let buffer = this.buffer.read(cx);
4769 if !buffer
4770 .text_anchor_for_position(cursor_position, cx)
4771 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
4772 {
4773 return;
4774 }
4775
4776 let cursor_buffer_snapshot = cursor_buffer.read(cx);
4777 let mut write_ranges = Vec::new();
4778 let mut read_ranges = Vec::new();
4779 for highlight in highlights {
4780 for (excerpt_id, excerpt_range) in
4781 buffer.excerpts_for_buffer(&cursor_buffer, cx)
4782 {
4783 let start = highlight
4784 .range
4785 .start
4786 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
4787 let end = highlight
4788 .range
4789 .end
4790 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
4791 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
4792 continue;
4793 }
4794
4795 let range = Anchor {
4796 buffer_id,
4797 excerpt_id: excerpt_id,
4798 text_anchor: start,
4799 }..Anchor {
4800 buffer_id,
4801 excerpt_id,
4802 text_anchor: end,
4803 };
4804 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
4805 write_ranges.push(range);
4806 } else {
4807 read_ranges.push(range);
4808 }
4809 }
4810 }
4811
4812 this.highlight_background::<DocumentHighlightRead>(
4813 &read_ranges,
4814 |theme| theme.editor_document_highlight_read_background,
4815 cx,
4816 );
4817 this.highlight_background::<DocumentHighlightWrite>(
4818 &write_ranges,
4819 |theme| theme.editor_document_highlight_write_background,
4820 cx,
4821 );
4822 cx.notify();
4823 })
4824 .log_err();
4825 }
4826 }));
4827 None
4828 }
4829
4830 fn refresh_inline_completion(
4831 &mut self,
4832 debounce: bool,
4833 cx: &mut ViewContext<Self>,
4834 ) -> Option<()> {
4835 let provider = self.inline_completion_provider()?;
4836 let cursor = self.selections.newest_anchor().head();
4837 let (buffer, cursor_buffer_position) =
4838 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4839 if !self.show_inline_completions
4840 || !provider.is_enabled(&buffer, cursor_buffer_position, cx)
4841 {
4842 self.discard_inline_completion(false, cx);
4843 return None;
4844 }
4845
4846 self.update_visible_inline_completion(cx);
4847 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
4848 Some(())
4849 }
4850
4851 fn cycle_inline_completion(
4852 &mut self,
4853 direction: Direction,
4854 cx: &mut ViewContext<Self>,
4855 ) -> Option<()> {
4856 let provider = self.inline_completion_provider()?;
4857 let cursor = self.selections.newest_anchor().head();
4858 let (buffer, cursor_buffer_position) =
4859 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
4860 if !self.show_inline_completions
4861 || !provider.is_enabled(&buffer, cursor_buffer_position, cx)
4862 {
4863 return None;
4864 }
4865
4866 provider.cycle(buffer, cursor_buffer_position, direction, cx);
4867 self.update_visible_inline_completion(cx);
4868
4869 Some(())
4870 }
4871
4872 pub fn show_inline_completion(&mut self, _: &ShowInlineCompletion, cx: &mut ViewContext<Self>) {
4873 if !self.has_active_inline_completion(cx) {
4874 self.refresh_inline_completion(false, cx);
4875 return;
4876 }
4877
4878 self.update_visible_inline_completion(cx);
4879 }
4880
4881 pub fn display_cursor_names(&mut self, _: &DisplayCursorNames, cx: &mut ViewContext<Self>) {
4882 self.show_cursor_names(cx);
4883 }
4884
4885 fn show_cursor_names(&mut self, cx: &mut ViewContext<Self>) {
4886 self.show_cursor_names = true;
4887 cx.notify();
4888 cx.spawn(|this, mut cx| async move {
4889 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
4890 this.update(&mut cx, |this, cx| {
4891 this.show_cursor_names = false;
4892 cx.notify()
4893 })
4894 .ok()
4895 })
4896 .detach();
4897 }
4898
4899 pub fn next_inline_completion(&mut self, _: &NextInlineCompletion, cx: &mut ViewContext<Self>) {
4900 if self.has_active_inline_completion(cx) {
4901 self.cycle_inline_completion(Direction::Next, cx);
4902 } else {
4903 let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
4904 if is_copilot_disabled {
4905 cx.propagate();
4906 }
4907 }
4908 }
4909
4910 pub fn previous_inline_completion(
4911 &mut self,
4912 _: &PreviousInlineCompletion,
4913 cx: &mut ViewContext<Self>,
4914 ) {
4915 if self.has_active_inline_completion(cx) {
4916 self.cycle_inline_completion(Direction::Prev, cx);
4917 } else {
4918 let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
4919 if is_copilot_disabled {
4920 cx.propagate();
4921 }
4922 }
4923 }
4924
4925 pub fn accept_inline_completion(
4926 &mut self,
4927 _: &AcceptInlineCompletion,
4928 cx: &mut ViewContext<Self>,
4929 ) {
4930 let Some(completion) = self.take_active_inline_completion(cx) else {
4931 return;
4932 };
4933 if let Some(provider) = self.inline_completion_provider() {
4934 provider.accept(cx);
4935 }
4936
4937 cx.emit(EditorEvent::InputHandled {
4938 utf16_range_to_replace: None,
4939 text: completion.text.to_string().into(),
4940 });
4941 self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
4942 self.refresh_inline_completion(true, cx);
4943 cx.notify();
4944 }
4945
4946 pub fn accept_partial_inline_completion(
4947 &mut self,
4948 _: &AcceptPartialInlineCompletion,
4949 cx: &mut ViewContext<Self>,
4950 ) {
4951 if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
4952 if let Some(completion) = self.take_active_inline_completion(cx) {
4953 let mut partial_completion = completion
4954 .text
4955 .chars()
4956 .by_ref()
4957 .take_while(|c| c.is_alphabetic())
4958 .collect::<String>();
4959 if partial_completion.is_empty() {
4960 partial_completion = completion
4961 .text
4962 .chars()
4963 .by_ref()
4964 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
4965 .collect::<String>();
4966 }
4967
4968 cx.emit(EditorEvent::InputHandled {
4969 utf16_range_to_replace: None,
4970 text: partial_completion.clone().into(),
4971 });
4972 self.insert_with_autoindent_mode(&partial_completion, None, cx);
4973 self.refresh_inline_completion(true, cx);
4974 cx.notify();
4975 }
4976 }
4977 }
4978
4979 fn discard_inline_completion(
4980 &mut self,
4981 should_report_inline_completion_event: bool,
4982 cx: &mut ViewContext<Self>,
4983 ) -> bool {
4984 if let Some(provider) = self.inline_completion_provider() {
4985 provider.discard(should_report_inline_completion_event, cx);
4986 }
4987
4988 self.take_active_inline_completion(cx).is_some()
4989 }
4990
4991 pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
4992 if let Some(completion) = self.active_inline_completion.as_ref() {
4993 let buffer = self.buffer.read(cx).read(cx);
4994 completion.position.is_valid(&buffer)
4995 } else {
4996 false
4997 }
4998 }
4999
5000 fn take_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
5001 let completion = self.active_inline_completion.take()?;
5002 self.display_map.update(cx, |map, cx| {
5003 map.splice_inlays(vec![completion.id], Default::default(), cx);
5004 });
5005 let buffer = self.buffer.read(cx).read(cx);
5006
5007 if completion.position.is_valid(&buffer) {
5008 Some(completion)
5009 } else {
5010 None
5011 }
5012 }
5013
5014 fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
5015 let selection = self.selections.newest_anchor();
5016 let cursor = selection.head();
5017
5018 if self.context_menu.read().is_none()
5019 && self.completion_tasks.is_empty()
5020 && selection.start == selection.end
5021 {
5022 if let Some(provider) = self.inline_completion_provider() {
5023 if let Some((buffer, cursor_buffer_position)) =
5024 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5025 {
5026 if let Some(text) =
5027 provider.active_completion_text(&buffer, cursor_buffer_position, cx)
5028 {
5029 let text = Rope::from(text);
5030 let mut to_remove = Vec::new();
5031 if let Some(completion) = self.active_inline_completion.take() {
5032 to_remove.push(completion.id);
5033 }
5034
5035 let completion_inlay =
5036 Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
5037 self.active_inline_completion = Some(completion_inlay.clone());
5038 self.display_map.update(cx, move |map, cx| {
5039 map.splice_inlays(to_remove, vec![completion_inlay], cx)
5040 });
5041 cx.notify();
5042 return;
5043 }
5044 }
5045 }
5046 }
5047
5048 self.discard_inline_completion(false, cx);
5049 }
5050
5051 fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
5052 Some(self.inline_completion_provider.as_ref()?.provider.clone())
5053 }
5054
5055 fn render_code_actions_indicator(
5056 &self,
5057 _style: &EditorStyle,
5058 row: DisplayRow,
5059 is_active: bool,
5060 cx: &mut ViewContext<Self>,
5061 ) -> Option<IconButton> {
5062 if self.available_code_actions.is_some() {
5063 Some(
5064 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
5065 .shape(ui::IconButtonShape::Square)
5066 .icon_size(IconSize::XSmall)
5067 .icon_color(Color::Muted)
5068 .selected(is_active)
5069 .on_click(cx.listener(move |editor, _e, cx| {
5070 editor.focus(cx);
5071 editor.toggle_code_actions(
5072 &ToggleCodeActions {
5073 deployed_from_indicator: Some(row),
5074 },
5075 cx,
5076 );
5077 })),
5078 )
5079 } else {
5080 None
5081 }
5082 }
5083
5084 fn clear_tasks(&mut self) {
5085 self.tasks.clear()
5086 }
5087
5088 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
5089 if let Some(_) = self.tasks.insert(key, value) {
5090 // This case should hopefully be rare, but just in case...
5091 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
5092 }
5093 }
5094
5095 fn render_run_indicator(
5096 &self,
5097 _style: &EditorStyle,
5098 is_active: bool,
5099 row: DisplayRow,
5100 cx: &mut ViewContext<Self>,
5101 ) -> IconButton {
5102 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
5103 .shape(ui::IconButtonShape::Square)
5104 .icon_size(IconSize::XSmall)
5105 .icon_color(Color::Muted)
5106 .selected(is_active)
5107 .on_click(cx.listener(move |editor, _e, cx| {
5108 editor.focus(cx);
5109 editor.toggle_code_actions(
5110 &ToggleCodeActions {
5111 deployed_from_indicator: Some(row),
5112 },
5113 cx,
5114 );
5115 }))
5116 }
5117
5118 pub fn context_menu_visible(&self) -> bool {
5119 self.context_menu
5120 .read()
5121 .as_ref()
5122 .map_or(false, |menu| menu.visible())
5123 }
5124
5125 fn render_context_menu(
5126 &self,
5127 cursor_position: DisplayPoint,
5128 style: &EditorStyle,
5129 max_height: Pixels,
5130 cx: &mut ViewContext<Editor>,
5131 ) -> Option<(ContextMenuOrigin, AnyElement)> {
5132 self.context_menu.read().as_ref().map(|menu| {
5133 menu.render(
5134 cursor_position,
5135 style,
5136 max_height,
5137 self.workspace.as_ref().map(|(w, _)| w.clone()),
5138 cx,
5139 )
5140 })
5141 }
5142
5143 fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
5144 cx.notify();
5145 self.completion_tasks.clear();
5146 let context_menu = self.context_menu.write().take();
5147 if context_menu.is_some() {
5148 self.update_visible_inline_completion(cx);
5149 }
5150 context_menu
5151 }
5152
5153 pub fn insert_snippet(
5154 &mut self,
5155 insertion_ranges: &[Range<usize>],
5156 snippet: Snippet,
5157 cx: &mut ViewContext<Self>,
5158 ) -> Result<()> {
5159 struct Tabstop<T> {
5160 is_end_tabstop: bool,
5161 ranges: Vec<Range<T>>,
5162 }
5163
5164 let tabstops = self.buffer.update(cx, |buffer, cx| {
5165 let snippet_text: Arc<str> = snippet.text.clone().into();
5166 buffer.edit(
5167 insertion_ranges
5168 .iter()
5169 .cloned()
5170 .map(|range| (range, snippet_text.clone())),
5171 Some(AutoindentMode::EachLine),
5172 cx,
5173 );
5174
5175 let snapshot = &*buffer.read(cx);
5176 let snippet = &snippet;
5177 snippet
5178 .tabstops
5179 .iter()
5180 .map(|tabstop| {
5181 let is_end_tabstop = tabstop.first().map_or(false, |tabstop| {
5182 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
5183 });
5184 let mut tabstop_ranges = tabstop
5185 .iter()
5186 .flat_map(|tabstop_range| {
5187 let mut delta = 0_isize;
5188 insertion_ranges.iter().map(move |insertion_range| {
5189 let insertion_start = insertion_range.start as isize + delta;
5190 delta +=
5191 snippet.text.len() as isize - insertion_range.len() as isize;
5192
5193 let start = ((insertion_start + tabstop_range.start) as usize)
5194 .min(snapshot.len());
5195 let end = ((insertion_start + tabstop_range.end) as usize)
5196 .min(snapshot.len());
5197 snapshot.anchor_before(start)..snapshot.anchor_after(end)
5198 })
5199 })
5200 .collect::<Vec<_>>();
5201 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
5202
5203 Tabstop {
5204 is_end_tabstop,
5205 ranges: tabstop_ranges,
5206 }
5207 })
5208 .collect::<Vec<_>>()
5209 });
5210 if let Some(tabstop) = tabstops.first() {
5211 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5212 s.select_ranges(tabstop.ranges.iter().cloned());
5213 });
5214
5215 // If we're already at the last tabstop and it's at the end of the snippet,
5216 // we're done, we don't need to keep the state around.
5217 if !tabstop.is_end_tabstop {
5218 let ranges = tabstops
5219 .into_iter()
5220 .map(|tabstop| tabstop.ranges)
5221 .collect::<Vec<_>>();
5222 self.snippet_stack.push(SnippetState {
5223 active_index: 0,
5224 ranges,
5225 });
5226 }
5227
5228 // Check whether the just-entered snippet ends with an auto-closable bracket.
5229 if self.autoclose_regions.is_empty() {
5230 let snapshot = self.buffer.read(cx).snapshot(cx);
5231 for selection in &mut self.selections.all::<Point>(cx) {
5232 let selection_head = selection.head();
5233 let Some(scope) = snapshot.language_scope_at(selection_head) else {
5234 continue;
5235 };
5236
5237 let mut bracket_pair = None;
5238 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
5239 let prev_chars = snapshot
5240 .reversed_chars_at(selection_head)
5241 .collect::<String>();
5242 for (pair, enabled) in scope.brackets() {
5243 if enabled
5244 && pair.close
5245 && prev_chars.starts_with(pair.start.as_str())
5246 && next_chars.starts_with(pair.end.as_str())
5247 {
5248 bracket_pair = Some(pair.clone());
5249 break;
5250 }
5251 }
5252 if let Some(pair) = bracket_pair {
5253 let start = snapshot.anchor_after(selection_head);
5254 let end = snapshot.anchor_after(selection_head);
5255 self.autoclose_regions.push(AutocloseRegion {
5256 selection_id: selection.id,
5257 range: start..end,
5258 pair,
5259 });
5260 }
5261 }
5262 }
5263 }
5264 Ok(())
5265 }
5266
5267 pub fn move_to_next_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
5268 self.move_to_snippet_tabstop(Bias::Right, cx)
5269 }
5270
5271 pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
5272 self.move_to_snippet_tabstop(Bias::Left, cx)
5273 }
5274
5275 pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
5276 if let Some(mut snippet) = self.snippet_stack.pop() {
5277 match bias {
5278 Bias::Left => {
5279 if snippet.active_index > 0 {
5280 snippet.active_index -= 1;
5281 } else {
5282 self.snippet_stack.push(snippet);
5283 return false;
5284 }
5285 }
5286 Bias::Right => {
5287 if snippet.active_index + 1 < snippet.ranges.len() {
5288 snippet.active_index += 1;
5289 } else {
5290 self.snippet_stack.push(snippet);
5291 return false;
5292 }
5293 }
5294 }
5295 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
5296 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
5297 s.select_anchor_ranges(current_ranges.iter().cloned())
5298 });
5299 // If snippet state is not at the last tabstop, push it back on the stack
5300 if snippet.active_index + 1 < snippet.ranges.len() {
5301 self.snippet_stack.push(snippet);
5302 }
5303 return true;
5304 }
5305 }
5306
5307 false
5308 }
5309
5310 pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
5311 self.transact(cx, |this, cx| {
5312 this.select_all(&SelectAll, cx);
5313 this.insert("", cx);
5314 });
5315 }
5316
5317 pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
5318 self.transact(cx, |this, cx| {
5319 this.select_autoclose_pair(cx);
5320 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
5321 if !this.linked_edit_ranges.is_empty() {
5322 let selections = this.selections.all::<MultiBufferPoint>(cx);
5323 let snapshot = this.buffer.read(cx).snapshot(cx);
5324
5325 for selection in selections.iter() {
5326 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
5327 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
5328 if selection_start.buffer_id != selection_end.buffer_id {
5329 continue;
5330 }
5331 if let Some(ranges) =
5332 this.linked_editing_ranges_for(selection_start..selection_end, cx)
5333 {
5334 for (buffer, entries) in ranges {
5335 linked_ranges.entry(buffer).or_default().extend(entries);
5336 }
5337 }
5338 }
5339 }
5340
5341 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
5342 if !this.selections.line_mode {
5343 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5344 for selection in &mut selections {
5345 if selection.is_empty() {
5346 let old_head = selection.head();
5347 let mut new_head =
5348 movement::left(&display_map, old_head.to_display_point(&display_map))
5349 .to_point(&display_map);
5350 if let Some((buffer, line_buffer_range)) = display_map
5351 .buffer_snapshot
5352 .buffer_line_for_row(MultiBufferRow(old_head.row))
5353 {
5354 let indent_size =
5355 buffer.indent_size_for_line(line_buffer_range.start.row);
5356 let indent_len = match indent_size.kind {
5357 IndentKind::Space => {
5358 buffer.settings_at(line_buffer_range.start, cx).tab_size
5359 }
5360 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
5361 };
5362 if old_head.column <= indent_size.len && old_head.column > 0 {
5363 let indent_len = indent_len.get();
5364 new_head = cmp::min(
5365 new_head,
5366 MultiBufferPoint::new(
5367 old_head.row,
5368 ((old_head.column - 1) / indent_len) * indent_len,
5369 ),
5370 );
5371 }
5372 }
5373
5374 selection.set_head(new_head, SelectionGoal::None);
5375 }
5376 }
5377 }
5378
5379 this.signature_help_state.set_backspace_pressed(true);
5380 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5381 this.insert("", cx);
5382 let empty_str: Arc<str> = Arc::from("");
5383 for (buffer, edits) in linked_ranges {
5384 let snapshot = buffer.read(cx).snapshot();
5385 use text::ToPoint as TP;
5386
5387 let edits = edits
5388 .into_iter()
5389 .map(|range| {
5390 let end_point = TP::to_point(&range.end, &snapshot);
5391 let mut start_point = TP::to_point(&range.start, &snapshot);
5392
5393 if end_point == start_point {
5394 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
5395 .saturating_sub(1);
5396 start_point = TP::to_point(&offset, &snapshot);
5397 };
5398
5399 (start_point..end_point, empty_str.clone())
5400 })
5401 .sorted_by_key(|(range, _)| range.start)
5402 .collect::<Vec<_>>();
5403 buffer.update(cx, |this, cx| {
5404 this.edit(edits, None, cx);
5405 })
5406 }
5407 this.refresh_inline_completion(true, cx);
5408 linked_editing_ranges::refresh_linked_ranges(this, cx);
5409 });
5410 }
5411
5412 pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
5413 self.transact(cx, |this, cx| {
5414 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5415 let line_mode = s.line_mode;
5416 s.move_with(|map, selection| {
5417 if selection.is_empty() && !line_mode {
5418 let cursor = movement::right(map, selection.head());
5419 selection.end = cursor;
5420 selection.reversed = true;
5421 selection.goal = SelectionGoal::None;
5422 }
5423 })
5424 });
5425 this.insert("", cx);
5426 this.refresh_inline_completion(true, cx);
5427 });
5428 }
5429
5430 pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
5431 if self.move_to_prev_snippet_tabstop(cx) {
5432 return;
5433 }
5434
5435 self.outdent(&Outdent, cx);
5436 }
5437
5438 pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
5439 if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) {
5440 return;
5441 }
5442
5443 let mut selections = self.selections.all_adjusted(cx);
5444 let buffer = self.buffer.read(cx);
5445 let snapshot = buffer.snapshot(cx);
5446 let rows_iter = selections.iter().map(|s| s.head().row);
5447 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
5448
5449 let mut edits = Vec::new();
5450 let mut prev_edited_row = 0;
5451 let mut row_delta = 0;
5452 for selection in &mut selections {
5453 if selection.start.row != prev_edited_row {
5454 row_delta = 0;
5455 }
5456 prev_edited_row = selection.end.row;
5457
5458 // If the selection is non-empty, then increase the indentation of the selected lines.
5459 if !selection.is_empty() {
5460 row_delta =
5461 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5462 continue;
5463 }
5464
5465 // If the selection is empty and the cursor is in the leading whitespace before the
5466 // suggested indentation, then auto-indent the line.
5467 let cursor = selection.head();
5468 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
5469 if let Some(suggested_indent) =
5470 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
5471 {
5472 if cursor.column < suggested_indent.len
5473 && cursor.column <= current_indent.len
5474 && current_indent.len <= suggested_indent.len
5475 {
5476 selection.start = Point::new(cursor.row, suggested_indent.len);
5477 selection.end = selection.start;
5478 if row_delta == 0 {
5479 edits.extend(Buffer::edit_for_indent_size_adjustment(
5480 cursor.row,
5481 current_indent,
5482 suggested_indent,
5483 ));
5484 row_delta = suggested_indent.len - current_indent.len;
5485 }
5486 continue;
5487 }
5488 }
5489
5490 // Otherwise, insert a hard or soft tab.
5491 let settings = buffer.settings_at(cursor, cx);
5492 let tab_size = if settings.hard_tabs {
5493 IndentSize::tab()
5494 } else {
5495 let tab_size = settings.tab_size.get();
5496 let char_column = snapshot
5497 .text_for_range(Point::new(cursor.row, 0)..cursor)
5498 .flat_map(str::chars)
5499 .count()
5500 + row_delta as usize;
5501 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
5502 IndentSize::spaces(chars_to_next_tab_stop)
5503 };
5504 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
5505 selection.end = selection.start;
5506 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
5507 row_delta += tab_size.len;
5508 }
5509
5510 self.transact(cx, |this, cx| {
5511 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5512 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5513 this.refresh_inline_completion(true, cx);
5514 });
5515 }
5516
5517 pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
5518 if self.read_only(cx) {
5519 return;
5520 }
5521 let mut selections = self.selections.all::<Point>(cx);
5522 let mut prev_edited_row = 0;
5523 let mut row_delta = 0;
5524 let mut edits = Vec::new();
5525 let buffer = self.buffer.read(cx);
5526 let snapshot = buffer.snapshot(cx);
5527 for selection in &mut selections {
5528 if selection.start.row != prev_edited_row {
5529 row_delta = 0;
5530 }
5531 prev_edited_row = selection.end.row;
5532
5533 row_delta =
5534 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
5535 }
5536
5537 self.transact(cx, |this, cx| {
5538 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
5539 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5540 });
5541 }
5542
5543 fn indent_selection(
5544 buffer: &MultiBuffer,
5545 snapshot: &MultiBufferSnapshot,
5546 selection: &mut Selection<Point>,
5547 edits: &mut Vec<(Range<Point>, String)>,
5548 delta_for_start_row: u32,
5549 cx: &AppContext,
5550 ) -> u32 {
5551 let settings = buffer.settings_at(selection.start, cx);
5552 let tab_size = settings.tab_size.get();
5553 let indent_kind = if settings.hard_tabs {
5554 IndentKind::Tab
5555 } else {
5556 IndentKind::Space
5557 };
5558 let mut start_row = selection.start.row;
5559 let mut end_row = selection.end.row + 1;
5560
5561 // If a selection ends at the beginning of a line, don't indent
5562 // that last line.
5563 if selection.end.column == 0 && selection.end.row > selection.start.row {
5564 end_row -= 1;
5565 }
5566
5567 // Avoid re-indenting a row that has already been indented by a
5568 // previous selection, but still update this selection's column
5569 // to reflect that indentation.
5570 if delta_for_start_row > 0 {
5571 start_row += 1;
5572 selection.start.column += delta_for_start_row;
5573 if selection.end.row == selection.start.row {
5574 selection.end.column += delta_for_start_row;
5575 }
5576 }
5577
5578 let mut delta_for_end_row = 0;
5579 let has_multiple_rows = start_row + 1 != end_row;
5580 for row in start_row..end_row {
5581 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
5582 let indent_delta = match (current_indent.kind, indent_kind) {
5583 (IndentKind::Space, IndentKind::Space) => {
5584 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
5585 IndentSize::spaces(columns_to_next_tab_stop)
5586 }
5587 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
5588 (_, IndentKind::Tab) => IndentSize::tab(),
5589 };
5590
5591 let start = if has_multiple_rows || current_indent.len < selection.start.column {
5592 0
5593 } else {
5594 selection.start.column
5595 };
5596 let row_start = Point::new(row, start);
5597 edits.push((
5598 row_start..row_start,
5599 indent_delta.chars().collect::<String>(),
5600 ));
5601
5602 // Update this selection's endpoints to reflect the indentation.
5603 if row == selection.start.row {
5604 selection.start.column += indent_delta.len;
5605 }
5606 if row == selection.end.row {
5607 selection.end.column += indent_delta.len;
5608 delta_for_end_row = indent_delta.len;
5609 }
5610 }
5611
5612 if selection.start.row == selection.end.row {
5613 delta_for_start_row + delta_for_end_row
5614 } else {
5615 delta_for_end_row
5616 }
5617 }
5618
5619 pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
5620 if self.read_only(cx) {
5621 return;
5622 }
5623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5624 let selections = self.selections.all::<Point>(cx);
5625 let mut deletion_ranges = Vec::new();
5626 let mut last_outdent = None;
5627 {
5628 let buffer = self.buffer.read(cx);
5629 let snapshot = buffer.snapshot(cx);
5630 for selection in &selections {
5631 let settings = buffer.settings_at(selection.start, cx);
5632 let tab_size = settings.tab_size.get();
5633 let mut rows = selection.spanned_rows(false, &display_map);
5634
5635 // Avoid re-outdenting a row that has already been outdented by a
5636 // previous selection.
5637 if let Some(last_row) = last_outdent {
5638 if last_row == rows.start {
5639 rows.start = rows.start.next_row();
5640 }
5641 }
5642 let has_multiple_rows = rows.len() > 1;
5643 for row in rows.iter_rows() {
5644 let indent_size = snapshot.indent_size_for_line(row);
5645 if indent_size.len > 0 {
5646 let deletion_len = match indent_size.kind {
5647 IndentKind::Space => {
5648 let columns_to_prev_tab_stop = indent_size.len % tab_size;
5649 if columns_to_prev_tab_stop == 0 {
5650 tab_size
5651 } else {
5652 columns_to_prev_tab_stop
5653 }
5654 }
5655 IndentKind::Tab => 1,
5656 };
5657 let start = if has_multiple_rows
5658 || deletion_len > selection.start.column
5659 || indent_size.len < selection.start.column
5660 {
5661 0
5662 } else {
5663 selection.start.column - deletion_len
5664 };
5665 deletion_ranges.push(
5666 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
5667 );
5668 last_outdent = Some(row);
5669 }
5670 }
5671 }
5672 }
5673
5674 self.transact(cx, |this, cx| {
5675 this.buffer.update(cx, |buffer, cx| {
5676 let empty_str: Arc<str> = "".into();
5677 buffer.edit(
5678 deletion_ranges
5679 .into_iter()
5680 .map(|range| (range, empty_str.clone())),
5681 None,
5682 cx,
5683 );
5684 });
5685 let selections = this.selections.all::<usize>(cx);
5686 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
5687 });
5688 }
5689
5690 pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
5691 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5692 let selections = self.selections.all::<Point>(cx);
5693
5694 let mut new_cursors = Vec::new();
5695 let mut edit_ranges = Vec::new();
5696 let mut selections = selections.iter().peekable();
5697 while let Some(selection) = selections.next() {
5698 let mut rows = selection.spanned_rows(false, &display_map);
5699 let goal_display_column = selection.head().to_display_point(&display_map).column();
5700
5701 // Accumulate contiguous regions of rows that we want to delete.
5702 while let Some(next_selection) = selections.peek() {
5703 let next_rows = next_selection.spanned_rows(false, &display_map);
5704 if next_rows.start <= rows.end {
5705 rows.end = next_rows.end;
5706 selections.next().unwrap();
5707 } else {
5708 break;
5709 }
5710 }
5711
5712 let buffer = &display_map.buffer_snapshot;
5713 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
5714 let edit_end;
5715 let cursor_buffer_row;
5716 if buffer.max_point().row >= rows.end.0 {
5717 // If there's a line after the range, delete the \n from the end of the row range
5718 // and position the cursor on the next line.
5719 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
5720 cursor_buffer_row = rows.end;
5721 } else {
5722 // If there isn't a line after the range, delete the \n from the line before the
5723 // start of the row range and position the cursor there.
5724 edit_start = edit_start.saturating_sub(1);
5725 edit_end = buffer.len();
5726 cursor_buffer_row = rows.start.previous_row();
5727 }
5728
5729 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
5730 *cursor.column_mut() =
5731 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
5732
5733 new_cursors.push((
5734 selection.id,
5735 buffer.anchor_after(cursor.to_point(&display_map)),
5736 ));
5737 edit_ranges.push(edit_start..edit_end);
5738 }
5739
5740 self.transact(cx, |this, cx| {
5741 let buffer = this.buffer.update(cx, |buffer, cx| {
5742 let empty_str: Arc<str> = "".into();
5743 buffer.edit(
5744 edit_ranges
5745 .into_iter()
5746 .map(|range| (range, empty_str.clone())),
5747 None,
5748 cx,
5749 );
5750 buffer.snapshot(cx)
5751 });
5752 let new_selections = new_cursors
5753 .into_iter()
5754 .map(|(id, cursor)| {
5755 let cursor = cursor.to_point(&buffer);
5756 Selection {
5757 id,
5758 start: cursor,
5759 end: cursor,
5760 reversed: false,
5761 goal: SelectionGoal::None,
5762 }
5763 })
5764 .collect();
5765
5766 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5767 s.select(new_selections);
5768 });
5769 });
5770 }
5771
5772 pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
5773 if self.read_only(cx) {
5774 return;
5775 }
5776 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
5777 for selection in self.selections.all::<Point>(cx) {
5778 let start = MultiBufferRow(selection.start.row);
5779 let end = if selection.start.row == selection.end.row {
5780 MultiBufferRow(selection.start.row + 1)
5781 } else {
5782 MultiBufferRow(selection.end.row)
5783 };
5784
5785 if let Some(last_row_range) = row_ranges.last_mut() {
5786 if start <= last_row_range.end {
5787 last_row_range.end = end;
5788 continue;
5789 }
5790 }
5791 row_ranges.push(start..end);
5792 }
5793
5794 let snapshot = self.buffer.read(cx).snapshot(cx);
5795 let mut cursor_positions = Vec::new();
5796 for row_range in &row_ranges {
5797 let anchor = snapshot.anchor_before(Point::new(
5798 row_range.end.previous_row().0,
5799 snapshot.line_len(row_range.end.previous_row()),
5800 ));
5801 cursor_positions.push(anchor..anchor);
5802 }
5803
5804 self.transact(cx, |this, cx| {
5805 for row_range in row_ranges.into_iter().rev() {
5806 for row in row_range.iter_rows().rev() {
5807 let end_of_line = Point::new(row.0, snapshot.line_len(row));
5808 let next_line_row = row.next_row();
5809 let indent = snapshot.indent_size_for_line(next_line_row);
5810 let start_of_next_line = Point::new(next_line_row.0, indent.len);
5811
5812 let replace = if snapshot.line_len(next_line_row) > indent.len {
5813 " "
5814 } else {
5815 ""
5816 };
5817
5818 this.buffer.update(cx, |buffer, cx| {
5819 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
5820 });
5821 }
5822 }
5823
5824 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
5825 s.select_anchor_ranges(cursor_positions)
5826 });
5827 });
5828 }
5829
5830 pub fn sort_lines_case_sensitive(
5831 &mut self,
5832 _: &SortLinesCaseSensitive,
5833 cx: &mut ViewContext<Self>,
5834 ) {
5835 self.manipulate_lines(cx, |lines| lines.sort())
5836 }
5837
5838 pub fn sort_lines_case_insensitive(
5839 &mut self,
5840 _: &SortLinesCaseInsensitive,
5841 cx: &mut ViewContext<Self>,
5842 ) {
5843 self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
5844 }
5845
5846 pub fn unique_lines_case_insensitive(
5847 &mut self,
5848 _: &UniqueLinesCaseInsensitive,
5849 cx: &mut ViewContext<Self>,
5850 ) {
5851 self.manipulate_lines(cx, |lines| {
5852 let mut seen = HashSet::default();
5853 lines.retain(|line| seen.insert(line.to_lowercase()));
5854 })
5855 }
5856
5857 pub fn unique_lines_case_sensitive(
5858 &mut self,
5859 _: &UniqueLinesCaseSensitive,
5860 cx: &mut ViewContext<Self>,
5861 ) {
5862 self.manipulate_lines(cx, |lines| {
5863 let mut seen = HashSet::default();
5864 lines.retain(|line| seen.insert(*line));
5865 })
5866 }
5867
5868 pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
5869 let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
5870 if !revert_changes.is_empty() {
5871 self.transact(cx, |editor, cx| {
5872 editor.buffer().update(cx, |multi_buffer, cx| {
5873 for (buffer_id, changes) in revert_changes {
5874 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
5875 buffer.update(cx, |buffer, cx| {
5876 buffer.edit(
5877 changes.into_iter().map(|(range, text)| {
5878 (range, text.to_string().map(Arc::<str>::from))
5879 }),
5880 None,
5881 cx,
5882 );
5883 });
5884 }
5885 }
5886 });
5887 editor.change_selections(None, cx, |selections| selections.refresh());
5888 });
5889 }
5890 }
5891
5892 pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
5893 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
5894 let project_path = buffer.read(cx).project_path(cx)?;
5895 let project = self.project.as_ref()?.read(cx);
5896 let entry = project.entry_for_path(&project_path, cx)?;
5897 let abs_path = project.absolute_path(&project_path, cx)?;
5898 let parent = if entry.is_symlink {
5899 abs_path.canonicalize().ok()?
5900 } else {
5901 abs_path
5902 }
5903 .parent()?
5904 .to_path_buf();
5905 Some(parent)
5906 }) {
5907 cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone());
5908 }
5909 }
5910
5911 fn gather_revert_changes(
5912 &mut self,
5913 selections: &[Selection<Anchor>],
5914 cx: &mut ViewContext<'_, Editor>,
5915 ) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
5916 let mut revert_changes = HashMap::default();
5917 self.buffer.update(cx, |multi_buffer, cx| {
5918 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5919 for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
5920 Self::prepare_revert_change(&mut revert_changes, &multi_buffer, &hunk, cx);
5921 }
5922 });
5923 revert_changes
5924 }
5925
5926 fn prepare_revert_change(
5927 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
5928 multi_buffer: &MultiBuffer,
5929 hunk: &DiffHunk<MultiBufferRow>,
5930 cx: &mut AppContext,
5931 ) -> Option<()> {
5932 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
5933 let buffer = buffer.read(cx);
5934 let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
5935 let buffer_snapshot = buffer.snapshot();
5936 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
5937 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
5938 probe
5939 .0
5940 .start
5941 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
5942 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
5943 }) {
5944 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
5945 Some(())
5946 } else {
5947 None
5948 }
5949 }
5950
5951 pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
5952 self.manipulate_lines(cx, |lines| lines.reverse())
5953 }
5954
5955 pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
5956 self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
5957 }
5958
5959 fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
5960 where
5961 Fn: FnMut(&mut Vec<&str>),
5962 {
5963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5964 let buffer = self.buffer.read(cx).snapshot(cx);
5965
5966 let mut edits = Vec::new();
5967
5968 let selections = self.selections.all::<Point>(cx);
5969 let mut selections = selections.iter().peekable();
5970 let mut contiguous_row_selections = Vec::new();
5971 let mut new_selections = Vec::new();
5972 let mut added_lines = 0;
5973 let mut removed_lines = 0;
5974
5975 while let Some(selection) = selections.next() {
5976 let (start_row, end_row) = consume_contiguous_rows(
5977 &mut contiguous_row_selections,
5978 selection,
5979 &display_map,
5980 &mut selections,
5981 );
5982
5983 let start_point = Point::new(start_row.0, 0);
5984 let end_point = Point::new(
5985 end_row.previous_row().0,
5986 buffer.line_len(end_row.previous_row()),
5987 );
5988 let text = buffer
5989 .text_for_range(start_point..end_point)
5990 .collect::<String>();
5991
5992 let mut lines = text.split('\n').collect_vec();
5993
5994 let lines_before = lines.len();
5995 callback(&mut lines);
5996 let lines_after = lines.len();
5997
5998 edits.push((start_point..end_point, lines.join("\n")));
5999
6000 // Selections must change based on added and removed line count
6001 let start_row =
6002 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
6003 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
6004 new_selections.push(Selection {
6005 id: selection.id,
6006 start: start_row,
6007 end: end_row,
6008 goal: SelectionGoal::None,
6009 reversed: selection.reversed,
6010 });
6011
6012 if lines_after > lines_before {
6013 added_lines += lines_after - lines_before;
6014 } else if lines_before > lines_after {
6015 removed_lines += lines_before - lines_after;
6016 }
6017 }
6018
6019 self.transact(cx, |this, cx| {
6020 let buffer = this.buffer.update(cx, |buffer, cx| {
6021 buffer.edit(edits, None, cx);
6022 buffer.snapshot(cx)
6023 });
6024
6025 // Recalculate offsets on newly edited buffer
6026 let new_selections = new_selections
6027 .iter()
6028 .map(|s| {
6029 let start_point = Point::new(s.start.0, 0);
6030 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
6031 Selection {
6032 id: s.id,
6033 start: buffer.point_to_offset(start_point),
6034 end: buffer.point_to_offset(end_point),
6035 goal: s.goal,
6036 reversed: s.reversed,
6037 }
6038 })
6039 .collect();
6040
6041 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6042 s.select(new_selections);
6043 });
6044
6045 this.request_autoscroll(Autoscroll::fit(), cx);
6046 });
6047 }
6048
6049 pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
6050 self.manipulate_text(cx, |text| text.to_uppercase())
6051 }
6052
6053 pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
6054 self.manipulate_text(cx, |text| text.to_lowercase())
6055 }
6056
6057 pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
6058 self.manipulate_text(cx, |text| {
6059 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
6060 // https://github.com/rutrum/convert-case/issues/16
6061 text.split('\n')
6062 .map(|line| line.to_case(Case::Title))
6063 .join("\n")
6064 })
6065 }
6066
6067 pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
6068 self.manipulate_text(cx, |text| text.to_case(Case::Snake))
6069 }
6070
6071 pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
6072 self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
6073 }
6074
6075 pub fn convert_to_upper_camel_case(
6076 &mut self,
6077 _: &ConvertToUpperCamelCase,
6078 cx: &mut ViewContext<Self>,
6079 ) {
6080 self.manipulate_text(cx, |text| {
6081 // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary
6082 // https://github.com/rutrum/convert-case/issues/16
6083 text.split('\n')
6084 .map(|line| line.to_case(Case::UpperCamel))
6085 .join("\n")
6086 })
6087 }
6088
6089 pub fn convert_to_lower_camel_case(
6090 &mut self,
6091 _: &ConvertToLowerCamelCase,
6092 cx: &mut ViewContext<Self>,
6093 ) {
6094 self.manipulate_text(cx, |text| text.to_case(Case::Camel))
6095 }
6096
6097 pub fn convert_to_opposite_case(
6098 &mut self,
6099 _: &ConvertToOppositeCase,
6100 cx: &mut ViewContext<Self>,
6101 ) {
6102 self.manipulate_text(cx, |text| {
6103 text.chars()
6104 .fold(String::with_capacity(text.len()), |mut t, c| {
6105 if c.is_uppercase() {
6106 t.extend(c.to_lowercase());
6107 } else {
6108 t.extend(c.to_uppercase());
6109 }
6110 t
6111 })
6112 })
6113 }
6114
6115 fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
6116 where
6117 Fn: FnMut(&str) -> String,
6118 {
6119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6120 let buffer = self.buffer.read(cx).snapshot(cx);
6121
6122 let mut new_selections = Vec::new();
6123 let mut edits = Vec::new();
6124 let mut selection_adjustment = 0i32;
6125
6126 for selection in self.selections.all::<usize>(cx) {
6127 let selection_is_empty = selection.is_empty();
6128
6129 let (start, end) = if selection_is_empty {
6130 let word_range = movement::surrounding_word(
6131 &display_map,
6132 selection.start.to_display_point(&display_map),
6133 );
6134 let start = word_range.start.to_offset(&display_map, Bias::Left);
6135 let end = word_range.end.to_offset(&display_map, Bias::Left);
6136 (start, end)
6137 } else {
6138 (selection.start, selection.end)
6139 };
6140
6141 let text = buffer.text_for_range(start..end).collect::<String>();
6142 let old_length = text.len() as i32;
6143 let text = callback(&text);
6144
6145 new_selections.push(Selection {
6146 start: (start as i32 - selection_adjustment) as usize,
6147 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
6148 goal: SelectionGoal::None,
6149 ..selection
6150 });
6151
6152 selection_adjustment += old_length - text.len() as i32;
6153
6154 edits.push((start..end, text));
6155 }
6156
6157 self.transact(cx, |this, cx| {
6158 this.buffer.update(cx, |buffer, cx| {
6159 buffer.edit(edits, None, cx);
6160 });
6161
6162 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6163 s.select(new_selections);
6164 });
6165
6166 this.request_autoscroll(Autoscroll::fit(), cx);
6167 });
6168 }
6169
6170 pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
6171 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6172 let buffer = &display_map.buffer_snapshot;
6173 let selections = self.selections.all::<Point>(cx);
6174
6175 let mut edits = Vec::new();
6176 let mut selections_iter = selections.iter().peekable();
6177 while let Some(selection) = selections_iter.next() {
6178 // Avoid duplicating the same lines twice.
6179 let mut rows = selection.spanned_rows(false, &display_map);
6180
6181 while let Some(next_selection) = selections_iter.peek() {
6182 let next_rows = next_selection.spanned_rows(false, &display_map);
6183 if next_rows.start < rows.end {
6184 rows.end = next_rows.end;
6185 selections_iter.next().unwrap();
6186 } else {
6187 break;
6188 }
6189 }
6190
6191 // Copy the text from the selected row region and splice it either at the start
6192 // or end of the region.
6193 let start = Point::new(rows.start.0, 0);
6194 let end = Point::new(
6195 rows.end.previous_row().0,
6196 buffer.line_len(rows.end.previous_row()),
6197 );
6198 let text = buffer
6199 .text_for_range(start..end)
6200 .chain(Some("\n"))
6201 .collect::<String>();
6202 let insert_location = if upwards {
6203 Point::new(rows.end.0, 0)
6204 } else {
6205 start
6206 };
6207 edits.push((insert_location..insert_location, text));
6208 }
6209
6210 self.transact(cx, |this, cx| {
6211 this.buffer.update(cx, |buffer, cx| {
6212 buffer.edit(edits, None, cx);
6213 });
6214
6215 this.request_autoscroll(Autoscroll::fit(), cx);
6216 });
6217 }
6218
6219 pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
6220 self.duplicate_line(true, cx);
6221 }
6222
6223 pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
6224 self.duplicate_line(false, cx);
6225 }
6226
6227 pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
6228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6229 let buffer = self.buffer.read(cx).snapshot(cx);
6230
6231 let mut edits = Vec::new();
6232 let mut unfold_ranges = Vec::new();
6233 let mut refold_ranges = Vec::new();
6234
6235 let selections = self.selections.all::<Point>(cx);
6236 let mut selections = selections.iter().peekable();
6237 let mut contiguous_row_selections = Vec::new();
6238 let mut new_selections = Vec::new();
6239
6240 while let Some(selection) = selections.next() {
6241 // Find all the selections that span a contiguous row range
6242 let (start_row, end_row) = consume_contiguous_rows(
6243 &mut contiguous_row_selections,
6244 selection,
6245 &display_map,
6246 &mut selections,
6247 );
6248
6249 // Move the text spanned by the row range to be before the line preceding the row range
6250 if start_row.0 > 0 {
6251 let range_to_move = Point::new(
6252 start_row.previous_row().0,
6253 buffer.line_len(start_row.previous_row()),
6254 )
6255 ..Point::new(
6256 end_row.previous_row().0,
6257 buffer.line_len(end_row.previous_row()),
6258 );
6259 let insertion_point = display_map
6260 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
6261 .0;
6262
6263 // Don't move lines across excerpts
6264 if buffer
6265 .excerpt_boundaries_in_range((
6266 Bound::Excluded(insertion_point),
6267 Bound::Included(range_to_move.end),
6268 ))
6269 .next()
6270 .is_none()
6271 {
6272 let text = buffer
6273 .text_for_range(range_to_move.clone())
6274 .flat_map(|s| s.chars())
6275 .skip(1)
6276 .chain(['\n'])
6277 .collect::<String>();
6278
6279 edits.push((
6280 buffer.anchor_after(range_to_move.start)
6281 ..buffer.anchor_before(range_to_move.end),
6282 String::new(),
6283 ));
6284 let insertion_anchor = buffer.anchor_after(insertion_point);
6285 edits.push((insertion_anchor..insertion_anchor, text));
6286
6287 let row_delta = range_to_move.start.row - insertion_point.row + 1;
6288
6289 // Move selections up
6290 new_selections.extend(contiguous_row_selections.drain(..).map(
6291 |mut selection| {
6292 selection.start.row -= row_delta;
6293 selection.end.row -= row_delta;
6294 selection
6295 },
6296 ));
6297
6298 // Move folds up
6299 unfold_ranges.push(range_to_move.clone());
6300 for fold in display_map.folds_in_range(
6301 buffer.anchor_before(range_to_move.start)
6302 ..buffer.anchor_after(range_to_move.end),
6303 ) {
6304 let mut start = fold.range.start.to_point(&buffer);
6305 let mut end = fold.range.end.to_point(&buffer);
6306 start.row -= row_delta;
6307 end.row -= row_delta;
6308 refold_ranges.push((start..end, fold.placeholder.clone()));
6309 }
6310 }
6311 }
6312
6313 // If we didn't move line(s), preserve the existing selections
6314 new_selections.append(&mut contiguous_row_selections);
6315 }
6316
6317 self.transact(cx, |this, cx| {
6318 this.unfold_ranges(unfold_ranges, true, true, cx);
6319 this.buffer.update(cx, |buffer, cx| {
6320 for (range, text) in edits {
6321 buffer.edit([(range, text)], None, cx);
6322 }
6323 });
6324 this.fold_ranges(refold_ranges, true, cx);
6325 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6326 s.select(new_selections);
6327 })
6328 });
6329 }
6330
6331 pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
6332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
6333 let buffer = self.buffer.read(cx).snapshot(cx);
6334
6335 let mut edits = Vec::new();
6336 let mut unfold_ranges = Vec::new();
6337 let mut refold_ranges = Vec::new();
6338
6339 let selections = self.selections.all::<Point>(cx);
6340 let mut selections = selections.iter().peekable();
6341 let mut contiguous_row_selections = Vec::new();
6342 let mut new_selections = Vec::new();
6343
6344 while let Some(selection) = selections.next() {
6345 // Find all the selections that span a contiguous row range
6346 let (start_row, end_row) = consume_contiguous_rows(
6347 &mut contiguous_row_selections,
6348 selection,
6349 &display_map,
6350 &mut selections,
6351 );
6352
6353 // Move the text spanned by the row range to be after the last line of the row range
6354 if end_row.0 <= buffer.max_point().row {
6355 let range_to_move =
6356 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
6357 let insertion_point = display_map
6358 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
6359 .0;
6360
6361 // Don't move lines across excerpt boundaries
6362 if buffer
6363 .excerpt_boundaries_in_range((
6364 Bound::Excluded(range_to_move.start),
6365 Bound::Included(insertion_point),
6366 ))
6367 .next()
6368 .is_none()
6369 {
6370 let mut text = String::from("\n");
6371 text.extend(buffer.text_for_range(range_to_move.clone()));
6372 text.pop(); // Drop trailing newline
6373 edits.push((
6374 buffer.anchor_after(range_to_move.start)
6375 ..buffer.anchor_before(range_to_move.end),
6376 String::new(),
6377 ));
6378 let insertion_anchor = buffer.anchor_after(insertion_point);
6379 edits.push((insertion_anchor..insertion_anchor, text));
6380
6381 let row_delta = insertion_point.row - range_to_move.end.row + 1;
6382
6383 // Move selections down
6384 new_selections.extend(contiguous_row_selections.drain(..).map(
6385 |mut selection| {
6386 selection.start.row += row_delta;
6387 selection.end.row += row_delta;
6388 selection
6389 },
6390 ));
6391
6392 // Move folds down
6393 unfold_ranges.push(range_to_move.clone());
6394 for fold in display_map.folds_in_range(
6395 buffer.anchor_before(range_to_move.start)
6396 ..buffer.anchor_after(range_to_move.end),
6397 ) {
6398 let mut start = fold.range.start.to_point(&buffer);
6399 let mut end = fold.range.end.to_point(&buffer);
6400 start.row += row_delta;
6401 end.row += row_delta;
6402 refold_ranges.push((start..end, fold.placeholder.clone()));
6403 }
6404 }
6405 }
6406
6407 // If we didn't move line(s), preserve the existing selections
6408 new_selections.append(&mut contiguous_row_selections);
6409 }
6410
6411 self.transact(cx, |this, cx| {
6412 this.unfold_ranges(unfold_ranges, true, true, cx);
6413 this.buffer.update(cx, |buffer, cx| {
6414 for (range, text) in edits {
6415 buffer.edit([(range, text)], None, cx);
6416 }
6417 });
6418 this.fold_ranges(refold_ranges, true, cx);
6419 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
6420 });
6421 }
6422
6423 pub fn transpose(&mut self, _: &Transpose, cx: &mut ViewContext<Self>) {
6424 let text_layout_details = &self.text_layout_details(cx);
6425 self.transact(cx, |this, cx| {
6426 let edits = this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6427 let mut edits: Vec<(Range<usize>, String)> = Default::default();
6428 let line_mode = s.line_mode;
6429 s.move_with(|display_map, selection| {
6430 if !selection.is_empty() || line_mode {
6431 return;
6432 }
6433
6434 let mut head = selection.head();
6435 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
6436 if head.column() == display_map.line_len(head.row()) {
6437 transpose_offset = display_map
6438 .buffer_snapshot
6439 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
6440 }
6441
6442 if transpose_offset == 0 {
6443 return;
6444 }
6445
6446 *head.column_mut() += 1;
6447 head = display_map.clip_point(head, Bias::Right);
6448 let goal = SelectionGoal::HorizontalPosition(
6449 display_map
6450 .x_for_display_point(head, &text_layout_details)
6451 .into(),
6452 );
6453 selection.collapse_to(head, goal);
6454
6455 let transpose_start = display_map
6456 .buffer_snapshot
6457 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
6458 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
6459 let transpose_end = display_map
6460 .buffer_snapshot
6461 .clip_offset(transpose_offset + 1, Bias::Right);
6462 if let Some(ch) =
6463 display_map.buffer_snapshot.chars_at(transpose_start).next()
6464 {
6465 edits.push((transpose_start..transpose_offset, String::new()));
6466 edits.push((transpose_end..transpose_end, ch.to_string()));
6467 }
6468 }
6469 });
6470 edits
6471 });
6472 this.buffer
6473 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
6474 let selections = this.selections.all::<usize>(cx);
6475 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6476 s.select(selections);
6477 });
6478 });
6479 }
6480
6481 pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
6482 let mut text = String::new();
6483 let buffer = self.buffer.read(cx).snapshot(cx);
6484 let mut selections = self.selections.all::<Point>(cx);
6485 let mut clipboard_selections = Vec::with_capacity(selections.len());
6486 {
6487 let max_point = buffer.max_point();
6488 let mut is_first = true;
6489 for selection in &mut selections {
6490 let is_entire_line = selection.is_empty() || self.selections.line_mode;
6491 if is_entire_line {
6492 selection.start = Point::new(selection.start.row, 0);
6493 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
6494 selection.goal = SelectionGoal::None;
6495 }
6496 if is_first {
6497 is_first = false;
6498 } else {
6499 text += "\n";
6500 }
6501 let mut len = 0;
6502 for chunk in buffer.text_for_range(selection.start..selection.end) {
6503 text.push_str(chunk);
6504 len += chunk.len();
6505 }
6506 clipboard_selections.push(ClipboardSelection {
6507 len,
6508 is_entire_line,
6509 first_line_indent: buffer
6510 .indent_size_for_line(MultiBufferRow(selection.start.row))
6511 .len,
6512 });
6513 }
6514 }
6515
6516 self.transact(cx, |this, cx| {
6517 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
6518 s.select(selections);
6519 });
6520 this.insert("", cx);
6521 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
6522 });
6523 }
6524
6525 pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
6526 let selections = self.selections.all::<Point>(cx);
6527 let buffer = self.buffer.read(cx).read(cx);
6528 let mut text = String::new();
6529
6530 let mut clipboard_selections = Vec::with_capacity(selections.len());
6531 {
6532 let max_point = buffer.max_point();
6533 let mut is_first = true;
6534 for selection in selections.iter() {
6535 let mut start = selection.start;
6536 let mut end = selection.end;
6537 let is_entire_line = selection.is_empty() || self.selections.line_mode;
6538 if is_entire_line {
6539 start = Point::new(start.row, 0);
6540 end = cmp::min(max_point, Point::new(end.row + 1, 0));
6541 }
6542 if is_first {
6543 is_first = false;
6544 } else {
6545 text += "\n";
6546 }
6547 let mut len = 0;
6548 for chunk in buffer.text_for_range(start..end) {
6549 text.push_str(chunk);
6550 len += chunk.len();
6551 }
6552 clipboard_selections.push(ClipboardSelection {
6553 len,
6554 is_entire_line,
6555 first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
6556 });
6557 }
6558 }
6559
6560 cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
6561 }
6562
6563 pub fn do_paste(
6564 &mut self,
6565 text: &String,
6566 clipboard_selections: Option<Vec<ClipboardSelection>>,
6567 handle_entire_lines: bool,
6568 cx: &mut ViewContext<Self>,
6569 ) {
6570 if self.read_only(cx) {
6571 return;
6572 }
6573
6574 let clipboard_text = Cow::Borrowed(text);
6575
6576 self.transact(cx, |this, cx| {
6577 if let Some(mut clipboard_selections) = clipboard_selections {
6578 let old_selections = this.selections.all::<usize>(cx);
6579 let all_selections_were_entire_line =
6580 clipboard_selections.iter().all(|s| s.is_entire_line);
6581 let first_selection_indent_column =
6582 clipboard_selections.first().map(|s| s.first_line_indent);
6583 if clipboard_selections.len() != old_selections.len() {
6584 clipboard_selections.drain(..);
6585 }
6586
6587 this.buffer.update(cx, |buffer, cx| {
6588 let snapshot = buffer.read(cx);
6589 let mut start_offset = 0;
6590 let mut edits = Vec::new();
6591 let mut original_indent_columns = Vec::new();
6592 for (ix, selection) in old_selections.iter().enumerate() {
6593 let to_insert;
6594 let entire_line;
6595 let original_indent_column;
6596 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
6597 let end_offset = start_offset + clipboard_selection.len;
6598 to_insert = &clipboard_text[start_offset..end_offset];
6599 entire_line = clipboard_selection.is_entire_line;
6600 start_offset = end_offset + 1;
6601 original_indent_column = Some(clipboard_selection.first_line_indent);
6602 } else {
6603 to_insert = clipboard_text.as_str();
6604 entire_line = all_selections_were_entire_line;
6605 original_indent_column = first_selection_indent_column
6606 }
6607
6608 // If the corresponding selection was empty when this slice of the
6609 // clipboard text was written, then the entire line containing the
6610 // selection was copied. If this selection is also currently empty,
6611 // then paste the line before the current line of the buffer.
6612 let range = if selection.is_empty() && handle_entire_lines && entire_line {
6613 let column = selection.start.to_point(&snapshot).column as usize;
6614 let line_start = selection.start - column;
6615 line_start..line_start
6616 } else {
6617 selection.range()
6618 };
6619
6620 edits.push((range, to_insert));
6621 original_indent_columns.extend(original_indent_column);
6622 }
6623 drop(snapshot);
6624
6625 buffer.edit(
6626 edits,
6627 Some(AutoindentMode::Block {
6628 original_indent_columns,
6629 }),
6630 cx,
6631 );
6632 });
6633
6634 let selections = this.selections.all::<usize>(cx);
6635 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
6636 } else {
6637 this.insert(&clipboard_text, cx);
6638 }
6639 });
6640 }
6641
6642 pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
6643 if let Some(item) = cx.read_from_clipboard() {
6644 self.do_paste(
6645 item.text(),
6646 item.metadata::<Vec<ClipboardSelection>>(),
6647 true,
6648 cx,
6649 )
6650 };
6651 }
6652
6653 pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
6654 if self.read_only(cx) {
6655 return;
6656 }
6657
6658 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
6659 if let Some((selections, _)) =
6660 self.selection_history.transaction(transaction_id).cloned()
6661 {
6662 self.change_selections(None, cx, |s| {
6663 s.select_anchors(selections.to_vec());
6664 });
6665 }
6666 self.request_autoscroll(Autoscroll::fit(), cx);
6667 self.unmark_text(cx);
6668 self.refresh_inline_completion(true, cx);
6669 cx.emit(EditorEvent::Edited { transaction_id });
6670 cx.emit(EditorEvent::TransactionUndone { transaction_id });
6671 }
6672 }
6673
6674 pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
6675 if self.read_only(cx) {
6676 return;
6677 }
6678
6679 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
6680 if let Some((_, Some(selections))) =
6681 self.selection_history.transaction(transaction_id).cloned()
6682 {
6683 self.change_selections(None, cx, |s| {
6684 s.select_anchors(selections.to_vec());
6685 });
6686 }
6687 self.request_autoscroll(Autoscroll::fit(), cx);
6688 self.unmark_text(cx);
6689 self.refresh_inline_completion(true, cx);
6690 cx.emit(EditorEvent::Edited { transaction_id });
6691 }
6692 }
6693
6694 pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext<Self>) {
6695 self.buffer
6696 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
6697 }
6698
6699 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut ViewContext<Self>) {
6700 self.buffer
6701 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
6702 }
6703
6704 pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
6705 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6706 let line_mode = s.line_mode;
6707 s.move_with(|map, selection| {
6708 let cursor = if selection.is_empty() && !line_mode {
6709 movement::left(map, selection.start)
6710 } else {
6711 selection.start
6712 };
6713 selection.collapse_to(cursor, SelectionGoal::None);
6714 });
6715 })
6716 }
6717
6718 pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
6719 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6720 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
6721 })
6722 }
6723
6724 pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
6725 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6726 let line_mode = s.line_mode;
6727 s.move_with(|map, selection| {
6728 let cursor = if selection.is_empty() && !line_mode {
6729 movement::right(map, selection.end)
6730 } else {
6731 selection.end
6732 };
6733 selection.collapse_to(cursor, SelectionGoal::None)
6734 });
6735 })
6736 }
6737
6738 pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
6739 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6740 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
6741 })
6742 }
6743
6744 pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
6745 if self.take_rename(true, cx).is_some() {
6746 return;
6747 }
6748
6749 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6750 cx.propagate();
6751 return;
6752 }
6753
6754 let text_layout_details = &self.text_layout_details(cx);
6755 let selection_count = self.selections.count();
6756 let first_selection = self.selections.first_anchor();
6757
6758 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6759 let line_mode = s.line_mode;
6760 s.move_with(|map, selection| {
6761 if !selection.is_empty() && !line_mode {
6762 selection.goal = SelectionGoal::None;
6763 }
6764 let (cursor, goal) = movement::up(
6765 map,
6766 selection.start,
6767 selection.goal,
6768 false,
6769 &text_layout_details,
6770 );
6771 selection.collapse_to(cursor, goal);
6772 });
6773 });
6774
6775 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
6776 {
6777 cx.propagate();
6778 }
6779 }
6780
6781 pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) {
6782 if self.take_rename(true, cx).is_some() {
6783 return;
6784 }
6785
6786 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6787 cx.propagate();
6788 return;
6789 }
6790
6791 let text_layout_details = &self.text_layout_details(cx);
6792
6793 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6794 let line_mode = s.line_mode;
6795 s.move_with(|map, selection| {
6796 if !selection.is_empty() && !line_mode {
6797 selection.goal = SelectionGoal::None;
6798 }
6799 let (cursor, goal) = movement::up_by_rows(
6800 map,
6801 selection.start,
6802 action.lines,
6803 selection.goal,
6804 false,
6805 &text_layout_details,
6806 );
6807 selection.collapse_to(cursor, goal);
6808 });
6809 })
6810 }
6811
6812 pub fn move_down_by_lines(&mut self, action: &MoveDownByLines, cx: &mut ViewContext<Self>) {
6813 if self.take_rename(true, cx).is_some() {
6814 return;
6815 }
6816
6817 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6818 cx.propagate();
6819 return;
6820 }
6821
6822 let text_layout_details = &self.text_layout_details(cx);
6823
6824 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6825 let line_mode = s.line_mode;
6826 s.move_with(|map, selection| {
6827 if !selection.is_empty() && !line_mode {
6828 selection.goal = SelectionGoal::None;
6829 }
6830 let (cursor, goal) = movement::down_by_rows(
6831 map,
6832 selection.start,
6833 action.lines,
6834 selection.goal,
6835 false,
6836 &text_layout_details,
6837 );
6838 selection.collapse_to(cursor, goal);
6839 });
6840 })
6841 }
6842
6843 pub fn select_down_by_lines(&mut self, action: &SelectDownByLines, cx: &mut ViewContext<Self>) {
6844 let text_layout_details = &self.text_layout_details(cx);
6845 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6846 s.move_heads_with(|map, head, goal| {
6847 movement::down_by_rows(map, head, action.lines, goal, false, &text_layout_details)
6848 })
6849 })
6850 }
6851
6852 pub fn select_up_by_lines(&mut self, action: &SelectUpByLines, cx: &mut ViewContext<Self>) {
6853 let text_layout_details = &self.text_layout_details(cx);
6854 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6855 s.move_heads_with(|map, head, goal| {
6856 movement::up_by_rows(map, head, action.lines, goal, false, &text_layout_details)
6857 })
6858 })
6859 }
6860
6861 pub fn select_page_up(&mut self, _: &SelectPageUp, cx: &mut ViewContext<Self>) {
6862 let Some(row_count) = self.visible_row_count() else {
6863 return;
6864 };
6865
6866 let text_layout_details = &self.text_layout_details(cx);
6867
6868 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6869 s.move_heads_with(|map, head, goal| {
6870 movement::up_by_rows(map, head, row_count, goal, false, &text_layout_details)
6871 })
6872 })
6873 }
6874
6875 pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
6876 if self.take_rename(true, cx).is_some() {
6877 return;
6878 }
6879
6880 if self
6881 .context_menu
6882 .write()
6883 .as_mut()
6884 .map(|menu| menu.select_first(self.project.as_ref(), cx))
6885 .unwrap_or(false)
6886 {
6887 return;
6888 }
6889
6890 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6891 cx.propagate();
6892 return;
6893 }
6894
6895 let Some(row_count) = self.visible_row_count() else {
6896 return;
6897 };
6898
6899 let autoscroll = if action.center_cursor {
6900 Autoscroll::center()
6901 } else {
6902 Autoscroll::fit()
6903 };
6904
6905 let text_layout_details = &self.text_layout_details(cx);
6906
6907 self.change_selections(Some(autoscroll), cx, |s| {
6908 let line_mode = s.line_mode;
6909 s.move_with(|map, selection| {
6910 if !selection.is_empty() && !line_mode {
6911 selection.goal = SelectionGoal::None;
6912 }
6913 let (cursor, goal) = movement::up_by_rows(
6914 map,
6915 selection.end,
6916 row_count,
6917 selection.goal,
6918 false,
6919 &text_layout_details,
6920 );
6921 selection.collapse_to(cursor, goal);
6922 });
6923 });
6924 }
6925
6926 pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
6927 let text_layout_details = &self.text_layout_details(cx);
6928 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6929 s.move_heads_with(|map, head, goal| {
6930 movement::up(map, head, goal, false, &text_layout_details)
6931 })
6932 })
6933 }
6934
6935 pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
6936 self.take_rename(true, cx);
6937
6938 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6939 cx.propagate();
6940 return;
6941 }
6942
6943 let text_layout_details = &self.text_layout_details(cx);
6944 let selection_count = self.selections.count();
6945 let first_selection = self.selections.first_anchor();
6946
6947 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6948 let line_mode = s.line_mode;
6949 s.move_with(|map, selection| {
6950 if !selection.is_empty() && !line_mode {
6951 selection.goal = SelectionGoal::None;
6952 }
6953 let (cursor, goal) = movement::down(
6954 map,
6955 selection.end,
6956 selection.goal,
6957 false,
6958 &text_layout_details,
6959 );
6960 selection.collapse_to(cursor, goal);
6961 });
6962 });
6963
6964 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
6965 {
6966 cx.propagate();
6967 }
6968 }
6969
6970 pub fn select_page_down(&mut self, _: &SelectPageDown, cx: &mut ViewContext<Self>) {
6971 let Some(row_count) = self.visible_row_count() else {
6972 return;
6973 };
6974
6975 let text_layout_details = &self.text_layout_details(cx);
6976
6977 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
6978 s.move_heads_with(|map, head, goal| {
6979 movement::down_by_rows(map, head, row_count, goal, false, &text_layout_details)
6980 })
6981 })
6982 }
6983
6984 pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
6985 if self.take_rename(true, cx).is_some() {
6986 return;
6987 }
6988
6989 if self
6990 .context_menu
6991 .write()
6992 .as_mut()
6993 .map(|menu| menu.select_last(self.project.as_ref(), cx))
6994 .unwrap_or(false)
6995 {
6996 return;
6997 }
6998
6999 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7000 cx.propagate();
7001 return;
7002 }
7003
7004 let Some(row_count) = self.visible_row_count() else {
7005 return;
7006 };
7007
7008 let autoscroll = if action.center_cursor {
7009 Autoscroll::center()
7010 } else {
7011 Autoscroll::fit()
7012 };
7013
7014 let text_layout_details = &self.text_layout_details(cx);
7015 self.change_selections(Some(autoscroll), cx, |s| {
7016 let line_mode = s.line_mode;
7017 s.move_with(|map, selection| {
7018 if !selection.is_empty() && !line_mode {
7019 selection.goal = SelectionGoal::None;
7020 }
7021 let (cursor, goal) = movement::down_by_rows(
7022 map,
7023 selection.end,
7024 row_count,
7025 selection.goal,
7026 false,
7027 &text_layout_details,
7028 );
7029 selection.collapse_to(cursor, goal);
7030 });
7031 });
7032 }
7033
7034 pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
7035 let text_layout_details = &self.text_layout_details(cx);
7036 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7037 s.move_heads_with(|map, head, goal| {
7038 movement::down(map, head, goal, false, &text_layout_details)
7039 })
7040 });
7041 }
7042
7043 pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
7044 if let Some(context_menu) = self.context_menu.write().as_mut() {
7045 context_menu.select_first(self.project.as_ref(), cx);
7046 }
7047 }
7048
7049 pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext<Self>) {
7050 if let Some(context_menu) = self.context_menu.write().as_mut() {
7051 context_menu.select_prev(self.project.as_ref(), cx);
7052 }
7053 }
7054
7055 pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext<Self>) {
7056 if let Some(context_menu) = self.context_menu.write().as_mut() {
7057 context_menu.select_next(self.project.as_ref(), cx);
7058 }
7059 }
7060
7061 pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext<Self>) {
7062 if let Some(context_menu) = self.context_menu.write().as_mut() {
7063 context_menu.select_last(self.project.as_ref(), cx);
7064 }
7065 }
7066
7067 pub fn move_to_previous_word_start(
7068 &mut self,
7069 _: &MoveToPreviousWordStart,
7070 cx: &mut ViewContext<Self>,
7071 ) {
7072 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7073 s.move_cursors_with(|map, head, _| {
7074 (
7075 movement::previous_word_start(map, head),
7076 SelectionGoal::None,
7077 )
7078 });
7079 })
7080 }
7081
7082 pub fn move_to_previous_subword_start(
7083 &mut self,
7084 _: &MoveToPreviousSubwordStart,
7085 cx: &mut ViewContext<Self>,
7086 ) {
7087 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7088 s.move_cursors_with(|map, head, _| {
7089 (
7090 movement::previous_subword_start(map, head),
7091 SelectionGoal::None,
7092 )
7093 });
7094 })
7095 }
7096
7097 pub fn select_to_previous_word_start(
7098 &mut self,
7099 _: &SelectToPreviousWordStart,
7100 cx: &mut ViewContext<Self>,
7101 ) {
7102 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7103 s.move_heads_with(|map, head, _| {
7104 (
7105 movement::previous_word_start(map, head),
7106 SelectionGoal::None,
7107 )
7108 });
7109 })
7110 }
7111
7112 pub fn select_to_previous_subword_start(
7113 &mut self,
7114 _: &SelectToPreviousSubwordStart,
7115 cx: &mut ViewContext<Self>,
7116 ) {
7117 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7118 s.move_heads_with(|map, head, _| {
7119 (
7120 movement::previous_subword_start(map, head),
7121 SelectionGoal::None,
7122 )
7123 });
7124 })
7125 }
7126
7127 pub fn delete_to_previous_word_start(
7128 &mut self,
7129 _: &DeleteToPreviousWordStart,
7130 cx: &mut ViewContext<Self>,
7131 ) {
7132 self.transact(cx, |this, cx| {
7133 this.select_autoclose_pair(cx);
7134 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7135 let line_mode = s.line_mode;
7136 s.move_with(|map, selection| {
7137 if selection.is_empty() && !line_mode {
7138 let cursor = movement::previous_word_start(map, selection.head());
7139 selection.set_head(cursor, SelectionGoal::None);
7140 }
7141 });
7142 });
7143 this.insert("", cx);
7144 });
7145 }
7146
7147 pub fn delete_to_previous_subword_start(
7148 &mut self,
7149 _: &DeleteToPreviousSubwordStart,
7150 cx: &mut ViewContext<Self>,
7151 ) {
7152 self.transact(cx, |this, cx| {
7153 this.select_autoclose_pair(cx);
7154 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7155 let line_mode = s.line_mode;
7156 s.move_with(|map, selection| {
7157 if selection.is_empty() && !line_mode {
7158 let cursor = movement::previous_subword_start(map, selection.head());
7159 selection.set_head(cursor, SelectionGoal::None);
7160 }
7161 });
7162 });
7163 this.insert("", cx);
7164 });
7165 }
7166
7167 pub fn move_to_next_word_end(&mut self, _: &MoveToNextWordEnd, cx: &mut ViewContext<Self>) {
7168 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7169 s.move_cursors_with(|map, head, _| {
7170 (movement::next_word_end(map, head), SelectionGoal::None)
7171 });
7172 })
7173 }
7174
7175 pub fn move_to_next_subword_end(
7176 &mut self,
7177 _: &MoveToNextSubwordEnd,
7178 cx: &mut ViewContext<Self>,
7179 ) {
7180 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7181 s.move_cursors_with(|map, head, _| {
7182 (movement::next_subword_end(map, head), SelectionGoal::None)
7183 });
7184 })
7185 }
7186
7187 pub fn select_to_next_word_end(&mut self, _: &SelectToNextWordEnd, cx: &mut ViewContext<Self>) {
7188 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7189 s.move_heads_with(|map, head, _| {
7190 (movement::next_word_end(map, head), SelectionGoal::None)
7191 });
7192 })
7193 }
7194
7195 pub fn select_to_next_subword_end(
7196 &mut self,
7197 _: &SelectToNextSubwordEnd,
7198 cx: &mut ViewContext<Self>,
7199 ) {
7200 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7201 s.move_heads_with(|map, head, _| {
7202 (movement::next_subword_end(map, head), SelectionGoal::None)
7203 });
7204 })
7205 }
7206
7207 pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
7208 self.transact(cx, |this, cx| {
7209 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7210 let line_mode = s.line_mode;
7211 s.move_with(|map, selection| {
7212 if selection.is_empty() && !line_mode {
7213 let cursor = movement::next_word_end(map, selection.head());
7214 selection.set_head(cursor, SelectionGoal::None);
7215 }
7216 });
7217 });
7218 this.insert("", cx);
7219 });
7220 }
7221
7222 pub fn delete_to_next_subword_end(
7223 &mut self,
7224 _: &DeleteToNextSubwordEnd,
7225 cx: &mut ViewContext<Self>,
7226 ) {
7227 self.transact(cx, |this, cx| {
7228 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7229 s.move_with(|map, selection| {
7230 if selection.is_empty() {
7231 let cursor = movement::next_subword_end(map, selection.head());
7232 selection.set_head(cursor, SelectionGoal::None);
7233 }
7234 });
7235 });
7236 this.insert("", cx);
7237 });
7238 }
7239
7240 pub fn move_to_beginning_of_line(
7241 &mut self,
7242 action: &MoveToBeginningOfLine,
7243 cx: &mut ViewContext<Self>,
7244 ) {
7245 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7246 s.move_cursors_with(|map, head, _| {
7247 (
7248 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
7249 SelectionGoal::None,
7250 )
7251 });
7252 })
7253 }
7254
7255 pub fn select_to_beginning_of_line(
7256 &mut self,
7257 action: &SelectToBeginningOfLine,
7258 cx: &mut ViewContext<Self>,
7259 ) {
7260 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7261 s.move_heads_with(|map, head, _| {
7262 (
7263 movement::indented_line_beginning(map, head, action.stop_at_soft_wraps),
7264 SelectionGoal::None,
7265 )
7266 });
7267 });
7268 }
7269
7270 pub fn delete_to_beginning_of_line(
7271 &mut self,
7272 _: &DeleteToBeginningOfLine,
7273 cx: &mut ViewContext<Self>,
7274 ) {
7275 self.transact(cx, |this, cx| {
7276 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
7277 s.move_with(|_, selection| {
7278 selection.reversed = true;
7279 });
7280 });
7281
7282 this.select_to_beginning_of_line(
7283 &SelectToBeginningOfLine {
7284 stop_at_soft_wraps: false,
7285 },
7286 cx,
7287 );
7288 this.backspace(&Backspace, cx);
7289 });
7290 }
7291
7292 pub fn move_to_end_of_line(&mut self, action: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
7293 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7294 s.move_cursors_with(|map, head, _| {
7295 (
7296 movement::line_end(map, head, action.stop_at_soft_wraps),
7297 SelectionGoal::None,
7298 )
7299 });
7300 })
7301 }
7302
7303 pub fn select_to_end_of_line(
7304 &mut self,
7305 action: &SelectToEndOfLine,
7306 cx: &mut ViewContext<Self>,
7307 ) {
7308 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7309 s.move_heads_with(|map, head, _| {
7310 (
7311 movement::line_end(map, head, action.stop_at_soft_wraps),
7312 SelectionGoal::None,
7313 )
7314 });
7315 })
7316 }
7317
7318 pub fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
7319 self.transact(cx, |this, cx| {
7320 this.select_to_end_of_line(
7321 &SelectToEndOfLine {
7322 stop_at_soft_wraps: false,
7323 },
7324 cx,
7325 );
7326 this.delete(&Delete, cx);
7327 });
7328 }
7329
7330 pub fn cut_to_end_of_line(&mut self, _: &CutToEndOfLine, cx: &mut ViewContext<Self>) {
7331 self.transact(cx, |this, cx| {
7332 this.select_to_end_of_line(
7333 &SelectToEndOfLine {
7334 stop_at_soft_wraps: false,
7335 },
7336 cx,
7337 );
7338 this.cut(&Cut, cx);
7339 });
7340 }
7341
7342 pub fn move_to_start_of_paragraph(
7343 &mut self,
7344 _: &MoveToStartOfParagraph,
7345 cx: &mut ViewContext<Self>,
7346 ) {
7347 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7348 cx.propagate();
7349 return;
7350 }
7351
7352 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7353 s.move_with(|map, selection| {
7354 selection.collapse_to(
7355 movement::start_of_paragraph(map, selection.head(), 1),
7356 SelectionGoal::None,
7357 )
7358 });
7359 })
7360 }
7361
7362 pub fn move_to_end_of_paragraph(
7363 &mut self,
7364 _: &MoveToEndOfParagraph,
7365 cx: &mut ViewContext<Self>,
7366 ) {
7367 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7368 cx.propagate();
7369 return;
7370 }
7371
7372 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7373 s.move_with(|map, selection| {
7374 selection.collapse_to(
7375 movement::end_of_paragraph(map, selection.head(), 1),
7376 SelectionGoal::None,
7377 )
7378 });
7379 })
7380 }
7381
7382 pub fn select_to_start_of_paragraph(
7383 &mut self,
7384 _: &SelectToStartOfParagraph,
7385 cx: &mut ViewContext<Self>,
7386 ) {
7387 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7388 cx.propagate();
7389 return;
7390 }
7391
7392 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7393 s.move_heads_with(|map, head, _| {
7394 (
7395 movement::start_of_paragraph(map, head, 1),
7396 SelectionGoal::None,
7397 )
7398 });
7399 })
7400 }
7401
7402 pub fn select_to_end_of_paragraph(
7403 &mut self,
7404 _: &SelectToEndOfParagraph,
7405 cx: &mut ViewContext<Self>,
7406 ) {
7407 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7408 cx.propagate();
7409 return;
7410 }
7411
7412 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7413 s.move_heads_with(|map, head, _| {
7414 (
7415 movement::end_of_paragraph(map, head, 1),
7416 SelectionGoal::None,
7417 )
7418 });
7419 })
7420 }
7421
7422 pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
7423 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7424 cx.propagate();
7425 return;
7426 }
7427
7428 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7429 s.select_ranges(vec![0..0]);
7430 });
7431 }
7432
7433 pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
7434 let mut selection = self.selections.last::<Point>(cx);
7435 selection.set_head(Point::zero(), SelectionGoal::None);
7436
7437 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7438 s.select(vec![selection]);
7439 });
7440 }
7441
7442 pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
7443 if matches!(self.mode, EditorMode::SingleLine { .. }) {
7444 cx.propagate();
7445 return;
7446 }
7447
7448 let cursor = self.buffer.read(cx).read(cx).len();
7449 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7450 s.select_ranges(vec![cursor..cursor])
7451 });
7452 }
7453
7454 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
7455 self.nav_history = nav_history;
7456 }
7457
7458 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
7459 self.nav_history.as_ref()
7460 }
7461
7462 fn push_to_nav_history(
7463 &mut self,
7464 cursor_anchor: Anchor,
7465 new_position: Option<Point>,
7466 cx: &mut ViewContext<Self>,
7467 ) {
7468 if let Some(nav_history) = self.nav_history.as_mut() {
7469 let buffer = self.buffer.read(cx).read(cx);
7470 let cursor_position = cursor_anchor.to_point(&buffer);
7471 let scroll_state = self.scroll_manager.anchor();
7472 let scroll_top_row = scroll_state.top_row(&buffer);
7473 drop(buffer);
7474
7475 if let Some(new_position) = new_position {
7476 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
7477 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
7478 return;
7479 }
7480 }
7481
7482 nav_history.push(
7483 Some(NavigationData {
7484 cursor_anchor,
7485 cursor_position,
7486 scroll_anchor: scroll_state,
7487 scroll_top_row,
7488 }),
7489 cx,
7490 );
7491 }
7492 }
7493
7494 pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
7495 let buffer = self.buffer.read(cx).snapshot(cx);
7496 let mut selection = self.selections.first::<usize>(cx);
7497 selection.set_head(buffer.len(), SelectionGoal::None);
7498 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7499 s.select(vec![selection]);
7500 });
7501 }
7502
7503 pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
7504 let end = self.buffer.read(cx).read(cx).len();
7505 self.change_selections(None, cx, |s| {
7506 s.select_ranges(vec![0..end]);
7507 });
7508 }
7509
7510 pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
7511 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7512 let mut selections = self.selections.all::<Point>(cx);
7513 let max_point = display_map.buffer_snapshot.max_point();
7514 for selection in &mut selections {
7515 let rows = selection.spanned_rows(true, &display_map);
7516 selection.start = Point::new(rows.start.0, 0);
7517 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
7518 selection.reversed = false;
7519 }
7520 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7521 s.select(selections);
7522 });
7523 }
7524
7525 pub fn split_selection_into_lines(
7526 &mut self,
7527 _: &SplitSelectionIntoLines,
7528 cx: &mut ViewContext<Self>,
7529 ) {
7530 let mut to_unfold = Vec::new();
7531 let mut new_selection_ranges = Vec::new();
7532 {
7533 let selections = self.selections.all::<Point>(cx);
7534 let buffer = self.buffer.read(cx).read(cx);
7535 for selection in selections {
7536 for row in selection.start.row..selection.end.row {
7537 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
7538 new_selection_ranges.push(cursor..cursor);
7539 }
7540 new_selection_ranges.push(selection.end..selection.end);
7541 to_unfold.push(selection.start..selection.end);
7542 }
7543 }
7544 self.unfold_ranges(to_unfold, true, true, cx);
7545 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7546 s.select_ranges(new_selection_ranges);
7547 });
7548 }
7549
7550 pub fn add_selection_above(&mut self, _: &AddSelectionAbove, cx: &mut ViewContext<Self>) {
7551 self.add_selection(true, cx);
7552 }
7553
7554 pub fn add_selection_below(&mut self, _: &AddSelectionBelow, cx: &mut ViewContext<Self>) {
7555 self.add_selection(false, cx);
7556 }
7557
7558 fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
7559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7560 let mut selections = self.selections.all::<Point>(cx);
7561 let text_layout_details = self.text_layout_details(cx);
7562 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
7563 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
7564 let range = oldest_selection.display_range(&display_map).sorted();
7565
7566 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
7567 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
7568 let positions = start_x.min(end_x)..start_x.max(end_x);
7569
7570 selections.clear();
7571 let mut stack = Vec::new();
7572 for row in range.start.row().0..=range.end.row().0 {
7573 if let Some(selection) = self.selections.build_columnar_selection(
7574 &display_map,
7575 DisplayRow(row),
7576 &positions,
7577 oldest_selection.reversed,
7578 &text_layout_details,
7579 ) {
7580 stack.push(selection.id);
7581 selections.push(selection);
7582 }
7583 }
7584
7585 if above {
7586 stack.reverse();
7587 }
7588
7589 AddSelectionsState { above, stack }
7590 });
7591
7592 let last_added_selection = *state.stack.last().unwrap();
7593 let mut new_selections = Vec::new();
7594 if above == state.above {
7595 let end_row = if above {
7596 DisplayRow(0)
7597 } else {
7598 display_map.max_point().row()
7599 };
7600
7601 'outer: for selection in selections {
7602 if selection.id == last_added_selection {
7603 let range = selection.display_range(&display_map).sorted();
7604 debug_assert_eq!(range.start.row(), range.end.row());
7605 let mut row = range.start.row();
7606 let positions =
7607 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
7608 px(start)..px(end)
7609 } else {
7610 let start_x =
7611 display_map.x_for_display_point(range.start, &text_layout_details);
7612 let end_x =
7613 display_map.x_for_display_point(range.end, &text_layout_details);
7614 start_x.min(end_x)..start_x.max(end_x)
7615 };
7616
7617 while row != end_row {
7618 if above {
7619 row.0 -= 1;
7620 } else {
7621 row.0 += 1;
7622 }
7623
7624 if let Some(new_selection) = self.selections.build_columnar_selection(
7625 &display_map,
7626 row,
7627 &positions,
7628 selection.reversed,
7629 &text_layout_details,
7630 ) {
7631 state.stack.push(new_selection.id);
7632 if above {
7633 new_selections.push(new_selection);
7634 new_selections.push(selection);
7635 } else {
7636 new_selections.push(selection);
7637 new_selections.push(new_selection);
7638 }
7639
7640 continue 'outer;
7641 }
7642 }
7643 }
7644
7645 new_selections.push(selection);
7646 }
7647 } else {
7648 new_selections = selections;
7649 new_selections.retain(|s| s.id != last_added_selection);
7650 state.stack.pop();
7651 }
7652
7653 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
7654 s.select(new_selections);
7655 });
7656 if state.stack.len() > 1 {
7657 self.add_selections_state = Some(state);
7658 }
7659 }
7660
7661 pub fn select_next_match_internal(
7662 &mut self,
7663 display_map: &DisplaySnapshot,
7664 replace_newest: bool,
7665 autoscroll: Option<Autoscroll>,
7666 cx: &mut ViewContext<Self>,
7667 ) -> Result<()> {
7668 fn select_next_match_ranges(
7669 this: &mut Editor,
7670 range: Range<usize>,
7671 replace_newest: bool,
7672 auto_scroll: Option<Autoscroll>,
7673 cx: &mut ViewContext<Editor>,
7674 ) {
7675 this.unfold_ranges([range.clone()], false, true, cx);
7676 this.change_selections(auto_scroll, cx, |s| {
7677 if replace_newest {
7678 s.delete(s.newest_anchor().id);
7679 }
7680 s.insert_range(range.clone());
7681 });
7682 }
7683
7684 let buffer = &display_map.buffer_snapshot;
7685 let mut selections = self.selections.all::<usize>(cx);
7686 if let Some(mut select_next_state) = self.select_next_state.take() {
7687 let query = &select_next_state.query;
7688 if !select_next_state.done {
7689 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
7690 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
7691 let mut next_selected_range = None;
7692
7693 let bytes_after_last_selection =
7694 buffer.bytes_in_range(last_selection.end..buffer.len());
7695 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
7696 let query_matches = query
7697 .stream_find_iter(bytes_after_last_selection)
7698 .map(|result| (last_selection.end, result))
7699 .chain(
7700 query
7701 .stream_find_iter(bytes_before_first_selection)
7702 .map(|result| (0, result)),
7703 );
7704
7705 for (start_offset, query_match) in query_matches {
7706 let query_match = query_match.unwrap(); // can only fail due to I/O
7707 let offset_range =
7708 start_offset + query_match.start()..start_offset + query_match.end();
7709 let display_range = offset_range.start.to_display_point(&display_map)
7710 ..offset_range.end.to_display_point(&display_map);
7711
7712 if !select_next_state.wordwise
7713 || (!movement::is_inside_word(&display_map, display_range.start)
7714 && !movement::is_inside_word(&display_map, display_range.end))
7715 {
7716 // TODO: This is n^2, because we might check all the selections
7717 if !selections
7718 .iter()
7719 .any(|selection| selection.range().overlaps(&offset_range))
7720 {
7721 next_selected_range = Some(offset_range);
7722 break;
7723 }
7724 }
7725 }
7726
7727 if let Some(next_selected_range) = next_selected_range {
7728 select_next_match_ranges(
7729 self,
7730 next_selected_range,
7731 replace_newest,
7732 autoscroll,
7733 cx,
7734 );
7735 } else {
7736 select_next_state.done = true;
7737 }
7738 }
7739
7740 self.select_next_state = Some(select_next_state);
7741 } else {
7742 let mut only_carets = true;
7743 let mut same_text_selected = true;
7744 let mut selected_text = None;
7745
7746 let mut selections_iter = selections.iter().peekable();
7747 while let Some(selection) = selections_iter.next() {
7748 if selection.start != selection.end {
7749 only_carets = false;
7750 }
7751
7752 if same_text_selected {
7753 if selected_text.is_none() {
7754 selected_text =
7755 Some(buffer.text_for_range(selection.range()).collect::<String>());
7756 }
7757
7758 if let Some(next_selection) = selections_iter.peek() {
7759 if next_selection.range().len() == selection.range().len() {
7760 let next_selected_text = buffer
7761 .text_for_range(next_selection.range())
7762 .collect::<String>();
7763 if Some(next_selected_text) != selected_text {
7764 same_text_selected = false;
7765 selected_text = None;
7766 }
7767 } else {
7768 same_text_selected = false;
7769 selected_text = None;
7770 }
7771 }
7772 }
7773 }
7774
7775 if only_carets {
7776 for selection in &mut selections {
7777 let word_range = movement::surrounding_word(
7778 &display_map,
7779 selection.start.to_display_point(&display_map),
7780 );
7781 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
7782 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
7783 selection.goal = SelectionGoal::None;
7784 selection.reversed = false;
7785 select_next_match_ranges(
7786 self,
7787 selection.start..selection.end,
7788 replace_newest,
7789 autoscroll,
7790 cx,
7791 );
7792 }
7793
7794 if selections.len() == 1 {
7795 let selection = selections
7796 .last()
7797 .expect("ensured that there's only one selection");
7798 let query = buffer
7799 .text_for_range(selection.start..selection.end)
7800 .collect::<String>();
7801 let is_empty = query.is_empty();
7802 let select_state = SelectNextState {
7803 query: AhoCorasick::new(&[query])?,
7804 wordwise: true,
7805 done: is_empty,
7806 };
7807 self.select_next_state = Some(select_state);
7808 } else {
7809 self.select_next_state = None;
7810 }
7811 } else if let Some(selected_text) = selected_text {
7812 self.select_next_state = Some(SelectNextState {
7813 query: AhoCorasick::new(&[selected_text])?,
7814 wordwise: false,
7815 done: false,
7816 });
7817 self.select_next_match_internal(display_map, replace_newest, autoscroll, cx)?;
7818 }
7819 }
7820 Ok(())
7821 }
7822
7823 pub fn select_all_matches(
7824 &mut self,
7825 _action: &SelectAllMatches,
7826 cx: &mut ViewContext<Self>,
7827 ) -> Result<()> {
7828 self.push_to_selection_history();
7829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7830
7831 self.select_next_match_internal(&display_map, false, None, cx)?;
7832 let Some(select_next_state) = self.select_next_state.as_mut() else {
7833 return Ok(());
7834 };
7835 if select_next_state.done {
7836 return Ok(());
7837 }
7838
7839 let mut new_selections = self.selections.all::<usize>(cx);
7840
7841 let buffer = &display_map.buffer_snapshot;
7842 let query_matches = select_next_state
7843 .query
7844 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
7845
7846 for query_match in query_matches {
7847 let query_match = query_match.unwrap(); // can only fail due to I/O
7848 let offset_range = query_match.start()..query_match.end();
7849 let display_range = offset_range.start.to_display_point(&display_map)
7850 ..offset_range.end.to_display_point(&display_map);
7851
7852 if !select_next_state.wordwise
7853 || (!movement::is_inside_word(&display_map, display_range.start)
7854 && !movement::is_inside_word(&display_map, display_range.end))
7855 {
7856 self.selections.change_with(cx, |selections| {
7857 new_selections.push(Selection {
7858 id: selections.new_selection_id(),
7859 start: offset_range.start,
7860 end: offset_range.end,
7861 reversed: false,
7862 goal: SelectionGoal::None,
7863 });
7864 });
7865 }
7866 }
7867
7868 new_selections.sort_by_key(|selection| selection.start);
7869 let mut ix = 0;
7870 while ix + 1 < new_selections.len() {
7871 let current_selection = &new_selections[ix];
7872 let next_selection = &new_selections[ix + 1];
7873 if current_selection.range().overlaps(&next_selection.range()) {
7874 if current_selection.id < next_selection.id {
7875 new_selections.remove(ix + 1);
7876 } else {
7877 new_selections.remove(ix);
7878 }
7879 } else {
7880 ix += 1;
7881 }
7882 }
7883
7884 select_next_state.done = true;
7885 self.unfold_ranges(
7886 new_selections.iter().map(|selection| selection.range()),
7887 false,
7888 false,
7889 cx,
7890 );
7891 self.change_selections(Some(Autoscroll::fit()), cx, |selections| {
7892 selections.select(new_selections)
7893 });
7894
7895 Ok(())
7896 }
7897
7898 pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) -> Result<()> {
7899 self.push_to_selection_history();
7900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7901 self.select_next_match_internal(
7902 &display_map,
7903 action.replace_newest,
7904 Some(Autoscroll::newest()),
7905 cx,
7906 )?;
7907 Ok(())
7908 }
7909
7910 pub fn select_previous(
7911 &mut self,
7912 action: &SelectPrevious,
7913 cx: &mut ViewContext<Self>,
7914 ) -> Result<()> {
7915 self.push_to_selection_history();
7916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7917 let buffer = &display_map.buffer_snapshot;
7918 let mut selections = self.selections.all::<usize>(cx);
7919 if let Some(mut select_prev_state) = self.select_prev_state.take() {
7920 let query = &select_prev_state.query;
7921 if !select_prev_state.done {
7922 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
7923 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
7924 let mut next_selected_range = None;
7925 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
7926 let bytes_before_last_selection =
7927 buffer.reversed_bytes_in_range(0..last_selection.start);
7928 let bytes_after_first_selection =
7929 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
7930 let query_matches = query
7931 .stream_find_iter(bytes_before_last_selection)
7932 .map(|result| (last_selection.start, result))
7933 .chain(
7934 query
7935 .stream_find_iter(bytes_after_first_selection)
7936 .map(|result| (buffer.len(), result)),
7937 );
7938 for (end_offset, query_match) in query_matches {
7939 let query_match = query_match.unwrap(); // can only fail due to I/O
7940 let offset_range =
7941 end_offset - query_match.end()..end_offset - query_match.start();
7942 let display_range = offset_range.start.to_display_point(&display_map)
7943 ..offset_range.end.to_display_point(&display_map);
7944
7945 if !select_prev_state.wordwise
7946 || (!movement::is_inside_word(&display_map, display_range.start)
7947 && !movement::is_inside_word(&display_map, display_range.end))
7948 {
7949 next_selected_range = Some(offset_range);
7950 break;
7951 }
7952 }
7953
7954 if let Some(next_selected_range) = next_selected_range {
7955 self.unfold_ranges([next_selected_range.clone()], false, true, cx);
7956 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
7957 if action.replace_newest {
7958 s.delete(s.newest_anchor().id);
7959 }
7960 s.insert_range(next_selected_range);
7961 });
7962 } else {
7963 select_prev_state.done = true;
7964 }
7965 }
7966
7967 self.select_prev_state = Some(select_prev_state);
7968 } else {
7969 let mut only_carets = true;
7970 let mut same_text_selected = true;
7971 let mut selected_text = None;
7972
7973 let mut selections_iter = selections.iter().peekable();
7974 while let Some(selection) = selections_iter.next() {
7975 if selection.start != selection.end {
7976 only_carets = false;
7977 }
7978
7979 if same_text_selected {
7980 if selected_text.is_none() {
7981 selected_text =
7982 Some(buffer.text_for_range(selection.range()).collect::<String>());
7983 }
7984
7985 if let Some(next_selection) = selections_iter.peek() {
7986 if next_selection.range().len() == selection.range().len() {
7987 let next_selected_text = buffer
7988 .text_for_range(next_selection.range())
7989 .collect::<String>();
7990 if Some(next_selected_text) != selected_text {
7991 same_text_selected = false;
7992 selected_text = None;
7993 }
7994 } else {
7995 same_text_selected = false;
7996 selected_text = None;
7997 }
7998 }
7999 }
8000 }
8001
8002 if only_carets {
8003 for selection in &mut selections {
8004 let word_range = movement::surrounding_word(
8005 &display_map,
8006 selection.start.to_display_point(&display_map),
8007 );
8008 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
8009 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
8010 selection.goal = SelectionGoal::None;
8011 selection.reversed = false;
8012 }
8013 if selections.len() == 1 {
8014 let selection = selections
8015 .last()
8016 .expect("ensured that there's only one selection");
8017 let query = buffer
8018 .text_for_range(selection.start..selection.end)
8019 .collect::<String>();
8020 let is_empty = query.is_empty();
8021 let select_state = SelectNextState {
8022 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
8023 wordwise: true,
8024 done: is_empty,
8025 };
8026 self.select_prev_state = Some(select_state);
8027 } else {
8028 self.select_prev_state = None;
8029 }
8030
8031 self.unfold_ranges(
8032 selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
8033 false,
8034 true,
8035 cx,
8036 );
8037 self.change_selections(Some(Autoscroll::newest()), cx, |s| {
8038 s.select(selections);
8039 });
8040 } else if let Some(selected_text) = selected_text {
8041 self.select_prev_state = Some(SelectNextState {
8042 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
8043 wordwise: false,
8044 done: false,
8045 });
8046 self.select_previous(action, cx)?;
8047 }
8048 }
8049 Ok(())
8050 }
8051
8052 pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
8053 let text_layout_details = &self.text_layout_details(cx);
8054 self.transact(cx, |this, cx| {
8055 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8056 let mut edits = Vec::new();
8057 let mut selection_edit_ranges = Vec::new();
8058 let mut last_toggled_row = None;
8059 let snapshot = this.buffer.read(cx).read(cx);
8060 let empty_str: Arc<str> = "".into();
8061 let mut suffixes_inserted = Vec::new();
8062
8063 fn comment_prefix_range(
8064 snapshot: &MultiBufferSnapshot,
8065 row: MultiBufferRow,
8066 comment_prefix: &str,
8067 comment_prefix_whitespace: &str,
8068 ) -> Range<Point> {
8069 let start = Point::new(row.0, snapshot.indent_size_for_line(row).len);
8070
8071 let mut line_bytes = snapshot
8072 .bytes_in_range(start..snapshot.max_point())
8073 .flatten()
8074 .copied();
8075
8076 // If this line currently begins with the line comment prefix, then record
8077 // the range containing the prefix.
8078 if line_bytes
8079 .by_ref()
8080 .take(comment_prefix.len())
8081 .eq(comment_prefix.bytes())
8082 {
8083 // Include any whitespace that matches the comment prefix.
8084 let matching_whitespace_len = line_bytes
8085 .zip(comment_prefix_whitespace.bytes())
8086 .take_while(|(a, b)| a == b)
8087 .count() as u32;
8088 let end = Point::new(
8089 start.row,
8090 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
8091 );
8092 start..end
8093 } else {
8094 start..start
8095 }
8096 }
8097
8098 fn comment_suffix_range(
8099 snapshot: &MultiBufferSnapshot,
8100 row: MultiBufferRow,
8101 comment_suffix: &str,
8102 comment_suffix_has_leading_space: bool,
8103 ) -> Range<Point> {
8104 let end = Point::new(row.0, snapshot.line_len(row));
8105 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
8106
8107 let mut line_end_bytes = snapshot
8108 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
8109 .flatten()
8110 .copied();
8111
8112 let leading_space_len = if suffix_start_column > 0
8113 && line_end_bytes.next() == Some(b' ')
8114 && comment_suffix_has_leading_space
8115 {
8116 1
8117 } else {
8118 0
8119 };
8120
8121 // If this line currently begins with the line comment prefix, then record
8122 // the range containing the prefix.
8123 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
8124 let start = Point::new(end.row, suffix_start_column - leading_space_len);
8125 start..end
8126 } else {
8127 end..end
8128 }
8129 }
8130
8131 // TODO: Handle selections that cross excerpts
8132 for selection in &mut selections {
8133 let start_column = snapshot
8134 .indent_size_for_line(MultiBufferRow(selection.start.row))
8135 .len;
8136 let language = if let Some(language) =
8137 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
8138 {
8139 language
8140 } else {
8141 continue;
8142 };
8143
8144 selection_edit_ranges.clear();
8145
8146 // If multiple selections contain a given row, avoid processing that
8147 // row more than once.
8148 let mut start_row = MultiBufferRow(selection.start.row);
8149 if last_toggled_row == Some(start_row) {
8150 start_row = start_row.next_row();
8151 }
8152 let end_row =
8153 if selection.end.row > selection.start.row && selection.end.column == 0 {
8154 MultiBufferRow(selection.end.row - 1)
8155 } else {
8156 MultiBufferRow(selection.end.row)
8157 };
8158 last_toggled_row = Some(end_row);
8159
8160 if start_row > end_row {
8161 continue;
8162 }
8163
8164 // If the language has line comments, toggle those.
8165 let full_comment_prefixes = language.line_comment_prefixes();
8166 if !full_comment_prefixes.is_empty() {
8167 let first_prefix = full_comment_prefixes
8168 .first()
8169 .expect("prefixes is non-empty");
8170 let prefix_trimmed_lengths = full_comment_prefixes
8171 .iter()
8172 .map(|p| p.trim_end_matches(' ').len())
8173 .collect::<SmallVec<[usize; 4]>>();
8174
8175 let mut all_selection_lines_are_comments = true;
8176
8177 for row in start_row.0..=end_row.0 {
8178 let row = MultiBufferRow(row);
8179 if start_row < end_row && snapshot.is_line_blank(row) {
8180 continue;
8181 }
8182
8183 let prefix_range = full_comment_prefixes
8184 .iter()
8185 .zip(prefix_trimmed_lengths.iter().copied())
8186 .map(|(prefix, trimmed_prefix_len)| {
8187 comment_prefix_range(
8188 snapshot.deref(),
8189 row,
8190 &prefix[..trimmed_prefix_len],
8191 &prefix[trimmed_prefix_len..],
8192 )
8193 })
8194 .max_by_key(|range| range.end.column - range.start.column)
8195 .expect("prefixes is non-empty");
8196
8197 if prefix_range.is_empty() {
8198 all_selection_lines_are_comments = false;
8199 }
8200
8201 selection_edit_ranges.push(prefix_range);
8202 }
8203
8204 if all_selection_lines_are_comments {
8205 edits.extend(
8206 selection_edit_ranges
8207 .iter()
8208 .cloned()
8209 .map(|range| (range, empty_str.clone())),
8210 );
8211 } else {
8212 let min_column = selection_edit_ranges
8213 .iter()
8214 .map(|range| range.start.column)
8215 .min()
8216 .unwrap_or(0);
8217 edits.extend(selection_edit_ranges.iter().map(|range| {
8218 let position = Point::new(range.start.row, min_column);
8219 (position..position, first_prefix.clone())
8220 }));
8221 }
8222 } else if let Some((full_comment_prefix, comment_suffix)) =
8223 language.block_comment_delimiters()
8224 {
8225 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
8226 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
8227 let prefix_range = comment_prefix_range(
8228 snapshot.deref(),
8229 start_row,
8230 comment_prefix,
8231 comment_prefix_whitespace,
8232 );
8233 let suffix_range = comment_suffix_range(
8234 snapshot.deref(),
8235 end_row,
8236 comment_suffix.trim_start_matches(' '),
8237 comment_suffix.starts_with(' '),
8238 );
8239
8240 if prefix_range.is_empty() || suffix_range.is_empty() {
8241 edits.push((
8242 prefix_range.start..prefix_range.start,
8243 full_comment_prefix.clone(),
8244 ));
8245 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
8246 suffixes_inserted.push((end_row, comment_suffix.len()));
8247 } else {
8248 edits.push((prefix_range, empty_str.clone()));
8249 edits.push((suffix_range, empty_str.clone()));
8250 }
8251 } else {
8252 continue;
8253 }
8254 }
8255
8256 drop(snapshot);
8257 this.buffer.update(cx, |buffer, cx| {
8258 buffer.edit(edits, None, cx);
8259 });
8260
8261 // Adjust selections so that they end before any comment suffixes that
8262 // were inserted.
8263 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
8264 let mut selections = this.selections.all::<Point>(cx);
8265 let snapshot = this.buffer.read(cx).read(cx);
8266 for selection in &mut selections {
8267 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
8268 match row.cmp(&MultiBufferRow(selection.end.row)) {
8269 Ordering::Less => {
8270 suffixes_inserted.next();
8271 continue;
8272 }
8273 Ordering::Greater => break,
8274 Ordering::Equal => {
8275 if selection.end.column == snapshot.line_len(row) {
8276 if selection.is_empty() {
8277 selection.start.column -= suffix_len as u32;
8278 }
8279 selection.end.column -= suffix_len as u32;
8280 }
8281 break;
8282 }
8283 }
8284 }
8285 }
8286
8287 drop(snapshot);
8288 this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
8289
8290 let selections = this.selections.all::<Point>(cx);
8291 let selections_on_single_row = selections.windows(2).all(|selections| {
8292 selections[0].start.row == selections[1].start.row
8293 && selections[0].end.row == selections[1].end.row
8294 && selections[0].start.row == selections[0].end.row
8295 });
8296 let selections_selecting = selections
8297 .iter()
8298 .any(|selection| selection.start != selection.end);
8299 let advance_downwards = action.advance_downwards
8300 && selections_on_single_row
8301 && !selections_selecting
8302 && !matches!(this.mode, EditorMode::SingleLine { .. });
8303
8304 if advance_downwards {
8305 let snapshot = this.buffer.read(cx).snapshot(cx);
8306
8307 this.change_selections(Some(Autoscroll::fit()), cx, |s| {
8308 s.move_cursors_with(|display_snapshot, display_point, _| {
8309 let mut point = display_point.to_point(display_snapshot);
8310 point.row += 1;
8311 point = snapshot.clip_point(point, Bias::Left);
8312 let display_point = point.to_display_point(display_snapshot);
8313 let goal = SelectionGoal::HorizontalPosition(
8314 display_snapshot
8315 .x_for_display_point(display_point, &text_layout_details)
8316 .into(),
8317 );
8318 (display_point, goal)
8319 })
8320 });
8321 }
8322 });
8323 }
8324
8325 pub fn select_enclosing_symbol(
8326 &mut self,
8327 _: &SelectEnclosingSymbol,
8328 cx: &mut ViewContext<Self>,
8329 ) {
8330 let buffer = self.buffer.read(cx).snapshot(cx);
8331 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
8332
8333 fn update_selection(
8334 selection: &Selection<usize>,
8335 buffer_snap: &MultiBufferSnapshot,
8336 ) -> Option<Selection<usize>> {
8337 let cursor = selection.head();
8338 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
8339 for symbol in symbols.iter().rev() {
8340 let start = symbol.range.start.to_offset(&buffer_snap);
8341 let end = symbol.range.end.to_offset(&buffer_snap);
8342 let new_range = start..end;
8343 if start < selection.start || end > selection.end {
8344 return Some(Selection {
8345 id: selection.id,
8346 start: new_range.start,
8347 end: new_range.end,
8348 goal: SelectionGoal::None,
8349 reversed: selection.reversed,
8350 });
8351 }
8352 }
8353 None
8354 }
8355
8356 let mut selected_larger_symbol = false;
8357 let new_selections = old_selections
8358 .iter()
8359 .map(|selection| match update_selection(selection, &buffer) {
8360 Some(new_selection) => {
8361 if new_selection.range() != selection.range() {
8362 selected_larger_symbol = true;
8363 }
8364 new_selection
8365 }
8366 None => selection.clone(),
8367 })
8368 .collect::<Vec<_>>();
8369
8370 if selected_larger_symbol {
8371 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8372 s.select(new_selections);
8373 });
8374 }
8375 }
8376
8377 pub fn select_larger_syntax_node(
8378 &mut self,
8379 _: &SelectLargerSyntaxNode,
8380 cx: &mut ViewContext<Self>,
8381 ) {
8382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8383 let buffer = self.buffer.read(cx).snapshot(cx);
8384 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
8385
8386 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
8387 let mut selected_larger_node = false;
8388 let new_selections = old_selections
8389 .iter()
8390 .map(|selection| {
8391 let old_range = selection.start..selection.end;
8392 let mut new_range = old_range.clone();
8393 while let Some(containing_range) =
8394 buffer.range_for_syntax_ancestor(new_range.clone())
8395 {
8396 new_range = containing_range;
8397 if !display_map.intersects_fold(new_range.start)
8398 && !display_map.intersects_fold(new_range.end)
8399 {
8400 break;
8401 }
8402 }
8403
8404 selected_larger_node |= new_range != old_range;
8405 Selection {
8406 id: selection.id,
8407 start: new_range.start,
8408 end: new_range.end,
8409 goal: SelectionGoal::None,
8410 reversed: selection.reversed,
8411 }
8412 })
8413 .collect::<Vec<_>>();
8414
8415 if selected_larger_node {
8416 stack.push(old_selections);
8417 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8418 s.select(new_selections);
8419 });
8420 }
8421 self.select_larger_syntax_node_stack = stack;
8422 }
8423
8424 pub fn select_smaller_syntax_node(
8425 &mut self,
8426 _: &SelectSmallerSyntaxNode,
8427 cx: &mut ViewContext<Self>,
8428 ) {
8429 let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
8430 if let Some(selections) = stack.pop() {
8431 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8432 s.select(selections.to_vec());
8433 });
8434 }
8435 self.select_larger_syntax_node_stack = stack;
8436 }
8437
8438 fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
8439 if !EditorSettings::get_global(cx).gutter.runnables {
8440 self.clear_tasks();
8441 return Task::ready(());
8442 }
8443 let project = self.project.clone();
8444 cx.spawn(|this, mut cx| async move {
8445 let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
8446 this.display_map.update(cx, |map, cx| map.snapshot(cx))
8447 }) else {
8448 return;
8449 };
8450
8451 let Some(project) = project else {
8452 return;
8453 };
8454
8455 let hide_runnables = project
8456 .update(&mut cx, |project, cx| {
8457 // Do not display any test indicators in non-dev server remote projects.
8458 project.is_remote() && project.ssh_connection_string(cx).is_none()
8459 })
8460 .unwrap_or(true);
8461 if hide_runnables {
8462 return;
8463 }
8464 let new_rows =
8465 cx.background_executor()
8466 .spawn({
8467 let snapshot = display_snapshot.clone();
8468 async move {
8469 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
8470 }
8471 })
8472 .await;
8473 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
8474
8475 this.update(&mut cx, |this, _| {
8476 this.clear_tasks();
8477 for (key, value) in rows {
8478 this.insert_tasks(key, value);
8479 }
8480 })
8481 .ok();
8482 })
8483 }
8484 fn fetch_runnable_ranges(
8485 snapshot: &DisplaySnapshot,
8486 range: Range<Anchor>,
8487 ) -> Vec<language::RunnableRange> {
8488 snapshot.buffer_snapshot.runnable_ranges(range).collect()
8489 }
8490
8491 fn runnable_rows(
8492 project: Model<Project>,
8493 snapshot: DisplaySnapshot,
8494 runnable_ranges: Vec<RunnableRange>,
8495 mut cx: AsyncWindowContext,
8496 ) -> Vec<((BufferId, u32), RunnableTasks)> {
8497 runnable_ranges
8498 .into_iter()
8499 .filter_map(|mut runnable| {
8500 let tasks = cx
8501 .update(|cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
8502 .ok()?;
8503 if tasks.is_empty() {
8504 return None;
8505 }
8506
8507 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
8508
8509 let row = snapshot
8510 .buffer_snapshot
8511 .buffer_line_for_row(MultiBufferRow(point.row))?
8512 .1
8513 .start
8514 .row;
8515
8516 let context_range =
8517 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
8518 Some((
8519 (runnable.buffer_id, row),
8520 RunnableTasks {
8521 templates: tasks,
8522 offset: MultiBufferOffset(runnable.run_range.start),
8523 context_range,
8524 column: point.column,
8525 extra_variables: runnable.extra_captures,
8526 },
8527 ))
8528 })
8529 .collect()
8530 }
8531
8532 fn templates_with_tags(
8533 project: &Model<Project>,
8534 runnable: &mut Runnable,
8535 cx: &WindowContext<'_>,
8536 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
8537 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
8538 let (worktree_id, file) = project
8539 .buffer_for_id(runnable.buffer, cx)
8540 .and_then(|buffer| buffer.read(cx).file())
8541 .map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
8542 .unzip();
8543
8544 (project.task_inventory().clone(), worktree_id, file)
8545 });
8546
8547 let inventory = inventory.read(cx);
8548 let tags = mem::take(&mut runnable.tags);
8549 let mut tags: Vec<_> = tags
8550 .into_iter()
8551 .flat_map(|tag| {
8552 let tag = tag.0.clone();
8553 inventory
8554 .list_tasks(
8555 file.clone(),
8556 Some(runnable.language.clone()),
8557 worktree_id,
8558 cx,
8559 )
8560 .into_iter()
8561 .filter(move |(_, template)| {
8562 template.tags.iter().any(|source_tag| source_tag == &tag)
8563 })
8564 })
8565 .sorted_by_key(|(kind, _)| kind.to_owned())
8566 .collect();
8567 if let Some((leading_tag_source, _)) = tags.first() {
8568 // Strongest source wins; if we have worktree tag binding, prefer that to
8569 // global and language bindings;
8570 // if we have a global binding, prefer that to language binding.
8571 let first_mismatch = tags
8572 .iter()
8573 .position(|(tag_source, _)| tag_source != leading_tag_source);
8574 if let Some(index) = first_mismatch {
8575 tags.truncate(index);
8576 }
8577 }
8578
8579 tags
8580 }
8581
8582 pub fn move_to_enclosing_bracket(
8583 &mut self,
8584 _: &MoveToEnclosingBracket,
8585 cx: &mut ViewContext<Self>,
8586 ) {
8587 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8588 s.move_offsets_with(|snapshot, selection| {
8589 let Some(enclosing_bracket_ranges) =
8590 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
8591 else {
8592 return;
8593 };
8594
8595 let mut best_length = usize::MAX;
8596 let mut best_inside = false;
8597 let mut best_in_bracket_range = false;
8598 let mut best_destination = None;
8599 for (open, close) in enclosing_bracket_ranges {
8600 let close = close.to_inclusive();
8601 let length = close.end() - open.start;
8602 let inside = selection.start >= open.end && selection.end <= *close.start();
8603 let in_bracket_range = open.to_inclusive().contains(&selection.head())
8604 || close.contains(&selection.head());
8605
8606 // If best is next to a bracket and current isn't, skip
8607 if !in_bracket_range && best_in_bracket_range {
8608 continue;
8609 }
8610
8611 // Prefer smaller lengths unless best is inside and current isn't
8612 if length > best_length && (best_inside || !inside) {
8613 continue;
8614 }
8615
8616 best_length = length;
8617 best_inside = inside;
8618 best_in_bracket_range = in_bracket_range;
8619 best_destination = Some(
8620 if close.contains(&selection.start) && close.contains(&selection.end) {
8621 if inside {
8622 open.end
8623 } else {
8624 open.start
8625 }
8626 } else {
8627 if inside {
8628 *close.start()
8629 } else {
8630 *close.end()
8631 }
8632 },
8633 );
8634 }
8635
8636 if let Some(destination) = best_destination {
8637 selection.collapse_to(destination, SelectionGoal::None);
8638 }
8639 })
8640 });
8641 }
8642
8643 pub fn undo_selection(&mut self, _: &UndoSelection, cx: &mut ViewContext<Self>) {
8644 self.end_selection(cx);
8645 self.selection_history.mode = SelectionHistoryMode::Undoing;
8646 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
8647 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
8648 self.select_next_state = entry.select_next_state;
8649 self.select_prev_state = entry.select_prev_state;
8650 self.add_selections_state = entry.add_selections_state;
8651 self.request_autoscroll(Autoscroll::newest(), cx);
8652 }
8653 self.selection_history.mode = SelectionHistoryMode::Normal;
8654 }
8655
8656 pub fn redo_selection(&mut self, _: &RedoSelection, cx: &mut ViewContext<Self>) {
8657 self.end_selection(cx);
8658 self.selection_history.mode = SelectionHistoryMode::Redoing;
8659 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
8660 self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
8661 self.select_next_state = entry.select_next_state;
8662 self.select_prev_state = entry.select_prev_state;
8663 self.add_selections_state = entry.add_selections_state;
8664 self.request_autoscroll(Autoscroll::newest(), cx);
8665 }
8666 self.selection_history.mode = SelectionHistoryMode::Normal;
8667 }
8668
8669 pub fn expand_excerpts(&mut self, action: &ExpandExcerpts, cx: &mut ViewContext<Self>) {
8670 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
8671 }
8672
8673 pub fn expand_excerpts_down(
8674 &mut self,
8675 action: &ExpandExcerptsDown,
8676 cx: &mut ViewContext<Self>,
8677 ) {
8678 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
8679 }
8680
8681 pub fn expand_excerpts_up(&mut self, action: &ExpandExcerptsUp, cx: &mut ViewContext<Self>) {
8682 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
8683 }
8684
8685 pub fn expand_excerpts_for_direction(
8686 &mut self,
8687 lines: u32,
8688 direction: ExpandExcerptDirection,
8689 cx: &mut ViewContext<Self>,
8690 ) {
8691 let selections = self.selections.disjoint_anchors();
8692
8693 let lines = if lines == 0 {
8694 EditorSettings::get_global(cx).expand_excerpt_lines
8695 } else {
8696 lines
8697 };
8698
8699 self.buffer.update(cx, |buffer, cx| {
8700 buffer.expand_excerpts(
8701 selections
8702 .into_iter()
8703 .map(|selection| selection.head().excerpt_id)
8704 .dedup(),
8705 lines,
8706 direction,
8707 cx,
8708 )
8709 })
8710 }
8711
8712 pub fn expand_excerpt(
8713 &mut self,
8714 excerpt: ExcerptId,
8715 direction: ExpandExcerptDirection,
8716 cx: &mut ViewContext<Self>,
8717 ) {
8718 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
8719 self.buffer.update(cx, |buffer, cx| {
8720 buffer.expand_excerpts([excerpt], lines, direction, cx)
8721 })
8722 }
8723
8724 fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
8725 self.go_to_diagnostic_impl(Direction::Next, cx)
8726 }
8727
8728 fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, cx: &mut ViewContext<Self>) {
8729 self.go_to_diagnostic_impl(Direction::Prev, cx)
8730 }
8731
8732 pub fn go_to_diagnostic_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
8733 let buffer = self.buffer.read(cx).snapshot(cx);
8734 let selection = self.selections.newest::<usize>(cx);
8735
8736 // If there is an active Diagnostic Popover jump to its diagnostic instead.
8737 if direction == Direction::Next {
8738 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
8739 let (group_id, jump_to) = popover.activation_info();
8740 if self.activate_diagnostics(group_id, cx) {
8741 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8742 let mut new_selection = s.newest_anchor().clone();
8743 new_selection.collapse_to(jump_to, SelectionGoal::None);
8744 s.select_anchors(vec![new_selection.clone()]);
8745 });
8746 }
8747 return;
8748 }
8749 }
8750
8751 let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
8752 active_diagnostics
8753 .primary_range
8754 .to_offset(&buffer)
8755 .to_inclusive()
8756 });
8757 let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
8758 if active_primary_range.contains(&selection.head()) {
8759 *active_primary_range.start()
8760 } else {
8761 selection.head()
8762 }
8763 } else {
8764 selection.head()
8765 };
8766 let snapshot = self.snapshot(cx);
8767 loop {
8768 let diagnostics = if direction == Direction::Prev {
8769 buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
8770 } else {
8771 buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
8772 }
8773 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
8774 let group = diagnostics
8775 // relies on diagnostics_in_range to return diagnostics with the same starting range to
8776 // be sorted in a stable way
8777 // skip until we are at current active diagnostic, if it exists
8778 .skip_while(|entry| {
8779 (match direction {
8780 Direction::Prev => entry.range.start >= search_start,
8781 Direction::Next => entry.range.start <= search_start,
8782 }) && self
8783 .active_diagnostics
8784 .as_ref()
8785 .is_some_and(|a| a.group_id != entry.diagnostic.group_id)
8786 })
8787 .find_map(|entry| {
8788 if entry.diagnostic.is_primary
8789 && entry.diagnostic.severity <= DiagnosticSeverity::WARNING
8790 && !entry.range.is_empty()
8791 // if we match with the active diagnostic, skip it
8792 && Some(entry.diagnostic.group_id)
8793 != self.active_diagnostics.as_ref().map(|d| d.group_id)
8794 {
8795 Some((entry.range, entry.diagnostic.group_id))
8796 } else {
8797 None
8798 }
8799 });
8800
8801 if let Some((primary_range, group_id)) = group {
8802 if self.activate_diagnostics(group_id, cx) {
8803 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8804 s.select(vec![Selection {
8805 id: selection.id,
8806 start: primary_range.start,
8807 end: primary_range.start,
8808 reversed: false,
8809 goal: SelectionGoal::None,
8810 }]);
8811 });
8812 }
8813 break;
8814 } else {
8815 // Cycle around to the start of the buffer, potentially moving back to the start of
8816 // the currently active diagnostic.
8817 active_primary_range.take();
8818 if direction == Direction::Prev {
8819 if search_start == buffer.len() {
8820 break;
8821 } else {
8822 search_start = buffer.len();
8823 }
8824 } else if search_start == 0 {
8825 break;
8826 } else {
8827 search_start = 0;
8828 }
8829 }
8830 }
8831 }
8832
8833 fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
8834 let snapshot = self
8835 .display_map
8836 .update(cx, |display_map, cx| display_map.snapshot(cx));
8837 let selection = self.selections.newest::<Point>(cx);
8838
8839 if !self.seek_in_direction(
8840 &snapshot,
8841 selection.head(),
8842 false,
8843 snapshot.buffer_snapshot.git_diff_hunks_in_range(
8844 MultiBufferRow(selection.head().row + 1)..MultiBufferRow::MAX,
8845 ),
8846 cx,
8847 ) {
8848 let wrapped_point = Point::zero();
8849 self.seek_in_direction(
8850 &snapshot,
8851 wrapped_point,
8852 true,
8853 snapshot.buffer_snapshot.git_diff_hunks_in_range(
8854 MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX,
8855 ),
8856 cx,
8857 );
8858 }
8859 }
8860
8861 fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
8862 let snapshot = self
8863 .display_map
8864 .update(cx, |display_map, cx| display_map.snapshot(cx));
8865 let selection = self.selections.newest::<Point>(cx);
8866
8867 if !self.seek_in_direction(
8868 &snapshot,
8869 selection.head(),
8870 false,
8871 snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
8872 MultiBufferRow(0)..MultiBufferRow(selection.head().row),
8873 ),
8874 cx,
8875 ) {
8876 let wrapped_point = snapshot.buffer_snapshot.max_point();
8877 self.seek_in_direction(
8878 &snapshot,
8879 wrapped_point,
8880 true,
8881 snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
8882 MultiBufferRow(0)..MultiBufferRow(wrapped_point.row),
8883 ),
8884 cx,
8885 );
8886 }
8887 }
8888
8889 fn seek_in_direction(
8890 &mut self,
8891 snapshot: &DisplaySnapshot,
8892 initial_point: Point,
8893 is_wrapped: bool,
8894 hunks: impl Iterator<Item = DiffHunk<MultiBufferRow>>,
8895 cx: &mut ViewContext<Editor>,
8896 ) -> bool {
8897 let display_point = initial_point.to_display_point(snapshot);
8898 let mut hunks = hunks
8899 .map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
8900 .filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row()))
8901 .dedup();
8902
8903 if let Some(hunk) = hunks.next() {
8904 self.change_selections(Some(Autoscroll::fit()), cx, |s| {
8905 let row = hunk.start_display_row();
8906 let point = DisplayPoint::new(row, 0);
8907 s.select_display_ranges([point..point]);
8908 });
8909
8910 true
8911 } else {
8912 false
8913 }
8914 }
8915
8916 pub fn go_to_definition(
8917 &mut self,
8918 _: &GoToDefinition,
8919 cx: &mut ViewContext<Self>,
8920 ) -> Task<Result<bool>> {
8921 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx)
8922 }
8923
8924 pub fn go_to_implementation(
8925 &mut self,
8926 _: &GoToImplementation,
8927 cx: &mut ViewContext<Self>,
8928 ) -> Task<Result<bool>> {
8929 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx)
8930 }
8931
8932 pub fn go_to_implementation_split(
8933 &mut self,
8934 _: &GoToImplementationSplit,
8935 cx: &mut ViewContext<Self>,
8936 ) -> Task<Result<bool>> {
8937 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx)
8938 }
8939
8940 pub fn go_to_type_definition(
8941 &mut self,
8942 _: &GoToTypeDefinition,
8943 cx: &mut ViewContext<Self>,
8944 ) -> Task<Result<bool>> {
8945 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx)
8946 }
8947
8948 pub fn go_to_definition_split(
8949 &mut self,
8950 _: &GoToDefinitionSplit,
8951 cx: &mut ViewContext<Self>,
8952 ) -> Task<Result<bool>> {
8953 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx)
8954 }
8955
8956 pub fn go_to_type_definition_split(
8957 &mut self,
8958 _: &GoToTypeDefinitionSplit,
8959 cx: &mut ViewContext<Self>,
8960 ) -> Task<Result<bool>> {
8961 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx)
8962 }
8963
8964 fn go_to_definition_of_kind(
8965 &mut self,
8966 kind: GotoDefinitionKind,
8967 split: bool,
8968 cx: &mut ViewContext<Self>,
8969 ) -> Task<Result<bool>> {
8970 let Some(workspace) = self.workspace() else {
8971 return Task::ready(Ok(false));
8972 };
8973 let buffer = self.buffer.read(cx);
8974 let head = self.selections.newest::<usize>(cx).head();
8975 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
8976 text_anchor
8977 } else {
8978 return Task::ready(Ok(false));
8979 };
8980
8981 let project = workspace.read(cx).project().clone();
8982 let definitions = project.update(cx, |project, cx| match kind {
8983 GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
8984 GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
8985 GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
8986 });
8987
8988 cx.spawn(|editor, mut cx| async move {
8989 let definitions = definitions.await?;
8990 let navigated = editor
8991 .update(&mut cx, |editor, cx| {
8992 editor.navigate_to_hover_links(
8993 Some(kind),
8994 definitions
8995 .into_iter()
8996 .filter(|location| {
8997 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
8998 })
8999 .map(HoverLink::Text)
9000 .collect::<Vec<_>>(),
9001 split,
9002 cx,
9003 )
9004 })?
9005 .await?;
9006 anyhow::Ok(navigated)
9007 })
9008 }
9009
9010 pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
9011 let position = self.selections.newest_anchor().head();
9012 let Some((buffer, buffer_position)) =
9013 self.buffer.read(cx).text_anchor_for_position(position, cx)
9014 else {
9015 return;
9016 };
9017
9018 cx.spawn(|editor, mut cx| async move {
9019 if let Some((_, url)) = find_url(&buffer, buffer_position, cx.clone()) {
9020 editor.update(&mut cx, |_, cx| {
9021 cx.open_url(&url);
9022 })
9023 } else {
9024 Ok(())
9025 }
9026 })
9027 .detach();
9028 }
9029
9030 pub(crate) fn navigate_to_hover_links(
9031 &mut self,
9032 kind: Option<GotoDefinitionKind>,
9033 mut definitions: Vec<HoverLink>,
9034 split: bool,
9035 cx: &mut ViewContext<Editor>,
9036 ) -> Task<Result<bool>> {
9037 // If there is one definition, just open it directly
9038 if definitions.len() == 1 {
9039 let definition = definitions.pop().unwrap();
9040 let target_task = match definition {
9041 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
9042 HoverLink::InlayHint(lsp_location, server_id) => {
9043 self.compute_target_location(lsp_location, server_id, cx)
9044 }
9045 HoverLink::Url(url) => {
9046 cx.open_url(&url);
9047 Task::ready(Ok(None))
9048 }
9049 };
9050 cx.spawn(|editor, mut cx| async move {
9051 let target = target_task.await.context("target resolution task")?;
9052 if let Some(target) = target {
9053 editor.update(&mut cx, |editor, cx| {
9054 let Some(workspace) = editor.workspace() else {
9055 return false;
9056 };
9057 let pane = workspace.read(cx).active_pane().clone();
9058
9059 let range = target.range.to_offset(target.buffer.read(cx));
9060 let range = editor.range_for_match(&range);
9061
9062 /// If select range has more than one line, we
9063 /// just point the cursor to range.start.
9064 fn check_multiline_range(
9065 buffer: &Buffer,
9066 range: Range<usize>,
9067 ) -> Range<usize> {
9068 if buffer.offset_to_point(range.start).row
9069 == buffer.offset_to_point(range.end).row
9070 {
9071 range
9072 } else {
9073 range.start..range.start
9074 }
9075 }
9076
9077 if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
9078 let buffer = target.buffer.read(cx);
9079 let range = check_multiline_range(buffer, range);
9080 editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
9081 s.select_ranges([range]);
9082 });
9083 } else {
9084 cx.window_context().defer(move |cx| {
9085 let target_editor: View<Self> =
9086 workspace.update(cx, |workspace, cx| {
9087 let pane = if split {
9088 workspace.adjacent_pane(cx)
9089 } else {
9090 workspace.active_pane().clone()
9091 };
9092
9093 workspace.open_project_item(pane, target.buffer.clone(), cx)
9094 });
9095 target_editor.update(cx, |target_editor, cx| {
9096 // When selecting a definition in a different buffer, disable the nav history
9097 // to avoid creating a history entry at the previous cursor location.
9098 pane.update(cx, |pane, _| pane.disable_history());
9099 let buffer = target.buffer.read(cx);
9100 let range = check_multiline_range(buffer, range);
9101 target_editor.change_selections(
9102 Some(Autoscroll::focused()),
9103 cx,
9104 |s| {
9105 s.select_ranges([range]);
9106 },
9107 );
9108 pane.update(cx, |pane, _| pane.enable_history());
9109 });
9110 });
9111 }
9112 true
9113 })
9114 } else {
9115 Ok(false)
9116 }
9117 })
9118 } else if !definitions.is_empty() {
9119 let replica_id = self.replica_id(cx);
9120 cx.spawn(|editor, mut cx| async move {
9121 let (title, location_tasks, workspace) = editor
9122 .update(&mut cx, |editor, cx| {
9123 let tab_kind = match kind {
9124 Some(GotoDefinitionKind::Implementation) => "Implementations",
9125 _ => "Definitions",
9126 };
9127 let title = definitions
9128 .iter()
9129 .find_map(|definition| match definition {
9130 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
9131 let buffer = origin.buffer.read(cx);
9132 format!(
9133 "{} for {}",
9134 tab_kind,
9135 buffer
9136 .text_for_range(origin.range.clone())
9137 .collect::<String>()
9138 )
9139 }),
9140 HoverLink::InlayHint(_, _) => None,
9141 HoverLink::Url(_) => None,
9142 })
9143 .unwrap_or(tab_kind.to_string());
9144 let location_tasks = definitions
9145 .into_iter()
9146 .map(|definition| match definition {
9147 HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
9148 HoverLink::InlayHint(lsp_location, server_id) => {
9149 editor.compute_target_location(lsp_location, server_id, cx)
9150 }
9151 HoverLink::Url(_) => Task::ready(Ok(None)),
9152 })
9153 .collect::<Vec<_>>();
9154 (title, location_tasks, editor.workspace().clone())
9155 })
9156 .context("location tasks preparation")?;
9157
9158 let locations = futures::future::join_all(location_tasks)
9159 .await
9160 .into_iter()
9161 .filter_map(|location| location.transpose())
9162 .collect::<Result<_>>()
9163 .context("location tasks")?;
9164
9165 let Some(workspace) = workspace else {
9166 return Ok(false);
9167 };
9168 let opened = workspace
9169 .update(&mut cx, |workspace, cx| {
9170 Self::open_locations_in_multibuffer(
9171 workspace, locations, replica_id, title, split, cx,
9172 )
9173 })
9174 .ok();
9175
9176 anyhow::Ok(opened.is_some())
9177 })
9178 } else {
9179 Task::ready(Ok(false))
9180 }
9181 }
9182
9183 fn compute_target_location(
9184 &self,
9185 lsp_location: lsp::Location,
9186 server_id: LanguageServerId,
9187 cx: &mut ViewContext<Editor>,
9188 ) -> Task<anyhow::Result<Option<Location>>> {
9189 let Some(project) = self.project.clone() else {
9190 return Task::Ready(Some(Ok(None)));
9191 };
9192
9193 cx.spawn(move |editor, mut cx| async move {
9194 let location_task = editor.update(&mut cx, |editor, cx| {
9195 project.update(cx, |project, cx| {
9196 let language_server_name =
9197 editor.buffer.read(cx).as_singleton().and_then(|buffer| {
9198 project
9199 .language_server_for_buffer(buffer.read(cx), server_id, cx)
9200 .map(|(lsp_adapter, _)| lsp_adapter.name.clone())
9201 });
9202 language_server_name.map(|language_server_name| {
9203 project.open_local_buffer_via_lsp(
9204 lsp_location.uri.clone(),
9205 server_id,
9206 language_server_name,
9207 cx,
9208 )
9209 })
9210 })
9211 })?;
9212 let location = match location_task {
9213 Some(task) => Some({
9214 let target_buffer_handle = task.await.context("open local buffer")?;
9215 let range = target_buffer_handle.update(&mut cx, |target_buffer, _| {
9216 let target_start = target_buffer
9217 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
9218 let target_end = target_buffer
9219 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
9220 target_buffer.anchor_after(target_start)
9221 ..target_buffer.anchor_before(target_end)
9222 })?;
9223 Location {
9224 buffer: target_buffer_handle,
9225 range,
9226 }
9227 }),
9228 None => None,
9229 };
9230 Ok(location)
9231 })
9232 }
9233
9234 pub fn find_all_references(
9235 &mut self,
9236 _: &FindAllReferences,
9237 cx: &mut ViewContext<Self>,
9238 ) -> Option<Task<Result<()>>> {
9239 let multi_buffer = self.buffer.read(cx);
9240 let selection = self.selections.newest::<usize>(cx);
9241 let head = selection.head();
9242
9243 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
9244 let head_anchor = multi_buffer_snapshot.anchor_at(
9245 head,
9246 if head < selection.tail() {
9247 Bias::Right
9248 } else {
9249 Bias::Left
9250 },
9251 );
9252
9253 match self
9254 .find_all_references_task_sources
9255 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
9256 {
9257 Ok(_) => {
9258 log::info!(
9259 "Ignoring repeated FindAllReferences invocation with the position of already running task"
9260 );
9261 return None;
9262 }
9263 Err(i) => {
9264 self.find_all_references_task_sources.insert(i, head_anchor);
9265 }
9266 }
9267
9268 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
9269 let replica_id = self.replica_id(cx);
9270 let workspace = self.workspace()?;
9271 let project = workspace.read(cx).project().clone();
9272 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
9273 Some(cx.spawn(|editor, mut cx| async move {
9274 let _cleanup = defer({
9275 let mut cx = cx.clone();
9276 move || {
9277 let _ = editor.update(&mut cx, |editor, _| {
9278 if let Ok(i) =
9279 editor
9280 .find_all_references_task_sources
9281 .binary_search_by(|anchor| {
9282 anchor.cmp(&head_anchor, &multi_buffer_snapshot)
9283 })
9284 {
9285 editor.find_all_references_task_sources.remove(i);
9286 }
9287 });
9288 }
9289 });
9290
9291 let locations = references.await?;
9292 if locations.is_empty() {
9293 return anyhow::Ok(());
9294 }
9295
9296 workspace.update(&mut cx, |workspace, cx| {
9297 let title = locations
9298 .first()
9299 .as_ref()
9300 .map(|location| {
9301 let buffer = location.buffer.read(cx);
9302 format!(
9303 "References to `{}`",
9304 buffer
9305 .text_for_range(location.range.clone())
9306 .collect::<String>()
9307 )
9308 })
9309 .unwrap();
9310 Self::open_locations_in_multibuffer(
9311 workspace, locations, replica_id, title, false, cx,
9312 );
9313 })
9314 }))
9315 }
9316
9317 /// Opens a multibuffer with the given project locations in it
9318 pub fn open_locations_in_multibuffer(
9319 workspace: &mut Workspace,
9320 mut locations: Vec<Location>,
9321 replica_id: ReplicaId,
9322 title: String,
9323 split: bool,
9324 cx: &mut ViewContext<Workspace>,
9325 ) {
9326 // If there are multiple definitions, open them in a multibuffer
9327 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
9328 let mut locations = locations.into_iter().peekable();
9329 let mut ranges_to_highlight = Vec::new();
9330 let capability = workspace.project().read(cx).capability();
9331
9332 let excerpt_buffer = cx.new_model(|cx| {
9333 let mut multibuffer = MultiBuffer::new(replica_id, capability);
9334 while let Some(location) = locations.next() {
9335 let buffer = location.buffer.read(cx);
9336 let mut ranges_for_buffer = Vec::new();
9337 let range = location.range.to_offset(buffer);
9338 ranges_for_buffer.push(range.clone());
9339
9340 while let Some(next_location) = locations.peek() {
9341 if next_location.buffer == location.buffer {
9342 ranges_for_buffer.push(next_location.range.to_offset(buffer));
9343 locations.next();
9344 } else {
9345 break;
9346 }
9347 }
9348
9349 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
9350 ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
9351 location.buffer.clone(),
9352 ranges_for_buffer,
9353 DEFAULT_MULTIBUFFER_CONTEXT,
9354 cx,
9355 ))
9356 }
9357
9358 multibuffer.with_title(title)
9359 });
9360
9361 let editor = cx.new_view(|cx| {
9362 Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), true, cx)
9363 });
9364 editor.update(cx, |editor, cx| {
9365 if let Some(first_range) = ranges_to_highlight.first() {
9366 editor.change_selections(None, cx, |selections| {
9367 selections.clear_disjoint();
9368 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
9369 });
9370 }
9371 editor.highlight_background::<Self>(
9372 &ranges_to_highlight,
9373 |theme| theme.editor_highlighted_line_background,
9374 cx,
9375 );
9376 });
9377
9378 let item = Box::new(editor);
9379 let item_id = item.item_id();
9380
9381 if split {
9382 workspace.split_item(SplitDirection::Right, item.clone(), cx);
9383 } else {
9384 let destination_index = workspace.active_pane().update(cx, |pane, cx| {
9385 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
9386 pane.close_current_preview_item(cx)
9387 } else {
9388 None
9389 }
9390 });
9391 workspace.add_item_to_active_pane(item.clone(), destination_index, cx);
9392 }
9393 workspace.active_pane().update(cx, |pane, cx| {
9394 pane.set_preview_item_id(Some(item_id), cx);
9395 });
9396 }
9397
9398 pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
9399 use language::ToOffset as _;
9400
9401 let project = self.project.clone()?;
9402 let selection = self.selections.newest_anchor().clone();
9403 let (cursor_buffer, cursor_buffer_position) = self
9404 .buffer
9405 .read(cx)
9406 .text_anchor_for_position(selection.head(), cx)?;
9407 let (tail_buffer, cursor_buffer_position_end) = self
9408 .buffer
9409 .read(cx)
9410 .text_anchor_for_position(selection.tail(), cx)?;
9411 if tail_buffer != cursor_buffer {
9412 return None;
9413 }
9414
9415 let snapshot = cursor_buffer.read(cx).snapshot();
9416 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
9417 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
9418 let prepare_rename = project.update(cx, |project, cx| {
9419 project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
9420 });
9421 drop(snapshot);
9422
9423 Some(cx.spawn(|this, mut cx| async move {
9424 let rename_range = if let Some(range) = prepare_rename.await? {
9425 Some(range)
9426 } else {
9427 this.update(&mut cx, |this, cx| {
9428 let buffer = this.buffer.read(cx).snapshot(cx);
9429 let mut buffer_highlights = this
9430 .document_highlights_for_position(selection.head(), &buffer)
9431 .filter(|highlight| {
9432 highlight.start.excerpt_id == selection.head().excerpt_id
9433 && highlight.end.excerpt_id == selection.head().excerpt_id
9434 });
9435 buffer_highlights
9436 .next()
9437 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
9438 })?
9439 };
9440 if let Some(rename_range) = rename_range {
9441 this.update(&mut cx, |this, cx| {
9442 let snapshot = cursor_buffer.read(cx).snapshot();
9443 let rename_buffer_range = rename_range.to_offset(&snapshot);
9444 let cursor_offset_in_rename_range =
9445 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
9446 let cursor_offset_in_rename_range_end =
9447 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
9448
9449 this.take_rename(false, cx);
9450 let buffer = this.buffer.read(cx).read(cx);
9451 let cursor_offset = selection.head().to_offset(&buffer);
9452 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
9453 let rename_end = rename_start + rename_buffer_range.len();
9454 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
9455 let mut old_highlight_id = None;
9456 let old_name: Arc<str> = buffer
9457 .chunks(rename_start..rename_end, true)
9458 .map(|chunk| {
9459 if old_highlight_id.is_none() {
9460 old_highlight_id = chunk.syntax_highlight_id;
9461 }
9462 chunk.text
9463 })
9464 .collect::<String>()
9465 .into();
9466
9467 drop(buffer);
9468
9469 // Position the selection in the rename editor so that it matches the current selection.
9470 this.show_local_selections = false;
9471 let rename_editor = cx.new_view(|cx| {
9472 let mut editor = Editor::single_line(cx);
9473 editor.buffer.update(cx, |buffer, cx| {
9474 buffer.edit([(0..0, old_name.clone())], None, cx)
9475 });
9476 let rename_selection_range = match cursor_offset_in_rename_range
9477 .cmp(&cursor_offset_in_rename_range_end)
9478 {
9479 Ordering::Equal => {
9480 editor.select_all(&SelectAll, cx);
9481 return editor;
9482 }
9483 Ordering::Less => {
9484 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
9485 }
9486 Ordering::Greater => {
9487 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
9488 }
9489 };
9490 if rename_selection_range.end > old_name.len() {
9491 editor.select_all(&SelectAll, cx);
9492 } else {
9493 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
9494 s.select_ranges([rename_selection_range]);
9495 });
9496 }
9497 editor
9498 });
9499 cx.subscribe(&rename_editor, |_, _, e, cx| match e {
9500 EditorEvent::Focused => cx.emit(EditorEvent::FocusedIn),
9501 _ => {}
9502 })
9503 .detach();
9504
9505 let write_highlights =
9506 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
9507 let read_highlights =
9508 this.clear_background_highlights::<DocumentHighlightRead>(cx);
9509 let ranges = write_highlights
9510 .iter()
9511 .flat_map(|(_, ranges)| ranges.iter())
9512 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
9513 .cloned()
9514 .collect();
9515
9516 this.highlight_text::<Rename>(
9517 ranges,
9518 HighlightStyle {
9519 fade_out: Some(0.6),
9520 ..Default::default()
9521 },
9522 cx,
9523 );
9524 let rename_focus_handle = rename_editor.focus_handle(cx);
9525 cx.focus(&rename_focus_handle);
9526 let block_id = this.insert_blocks(
9527 [BlockProperties {
9528 style: BlockStyle::Flex,
9529 position: range.start,
9530 height: 1,
9531 render: Box::new({
9532 let rename_editor = rename_editor.clone();
9533 move |cx: &mut BlockContext| {
9534 let mut text_style = cx.editor_style.text.clone();
9535 if let Some(highlight_style) = old_highlight_id
9536 .and_then(|h| h.style(&cx.editor_style.syntax))
9537 {
9538 text_style = text_style.highlight(highlight_style);
9539 }
9540 div()
9541 .pl(cx.anchor_x)
9542 .child(EditorElement::new(
9543 &rename_editor,
9544 EditorStyle {
9545 background: cx.theme().system().transparent,
9546 local_player: cx.editor_style.local_player,
9547 text: text_style,
9548 scrollbar_width: cx.editor_style.scrollbar_width,
9549 syntax: cx.editor_style.syntax.clone(),
9550 status: cx.editor_style.status.clone(),
9551 inlay_hints_style: HighlightStyle {
9552 color: Some(cx.theme().status().hint),
9553 font_weight: Some(FontWeight::BOLD),
9554 ..HighlightStyle::default()
9555 },
9556 suggestions_style: HighlightStyle {
9557 color: Some(cx.theme().status().predictive),
9558 ..HighlightStyle::default()
9559 },
9560 },
9561 ))
9562 .into_any_element()
9563 }
9564 }),
9565 disposition: BlockDisposition::Below,
9566 }],
9567 Some(Autoscroll::fit()),
9568 cx,
9569 )[0];
9570 this.pending_rename = Some(RenameState {
9571 range,
9572 old_name,
9573 editor: rename_editor,
9574 block_id,
9575 });
9576 })?;
9577 }
9578
9579 Ok(())
9580 }))
9581 }
9582
9583 pub fn confirm_rename(
9584 &mut self,
9585 _: &ConfirmRename,
9586 cx: &mut ViewContext<Self>,
9587 ) -> Option<Task<Result<()>>> {
9588 let rename = self.take_rename(false, cx)?;
9589 let workspace = self.workspace()?;
9590 let (start_buffer, start) = self
9591 .buffer
9592 .read(cx)
9593 .text_anchor_for_position(rename.range.start, cx)?;
9594 let (end_buffer, end) = self
9595 .buffer
9596 .read(cx)
9597 .text_anchor_for_position(rename.range.end, cx)?;
9598 if start_buffer != end_buffer {
9599 return None;
9600 }
9601
9602 let buffer = start_buffer;
9603 let range = start..end;
9604 let old_name = rename.old_name;
9605 let new_name = rename.editor.read(cx).text(cx);
9606
9607 let rename = workspace
9608 .read(cx)
9609 .project()
9610 .clone()
9611 .update(cx, |project, cx| {
9612 project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
9613 });
9614 let workspace = workspace.downgrade();
9615
9616 Some(cx.spawn(|editor, mut cx| async move {
9617 let project_transaction = rename.await?;
9618 Self::open_project_transaction(
9619 &editor,
9620 workspace,
9621 project_transaction,
9622 format!("Rename: {} → {}", old_name, new_name),
9623 cx.clone(),
9624 )
9625 .await?;
9626
9627 editor.update(&mut cx, |editor, cx| {
9628 editor.refresh_document_highlights(cx);
9629 })?;
9630 Ok(())
9631 }))
9632 }
9633
9634 fn take_rename(
9635 &mut self,
9636 moving_cursor: bool,
9637 cx: &mut ViewContext<Self>,
9638 ) -> Option<RenameState> {
9639 let rename = self.pending_rename.take()?;
9640 if rename.editor.focus_handle(cx).is_focused(cx) {
9641 cx.focus(&self.focus_handle);
9642 }
9643
9644 self.remove_blocks(
9645 [rename.block_id].into_iter().collect(),
9646 Some(Autoscroll::fit()),
9647 cx,
9648 );
9649 self.clear_highlights::<Rename>(cx);
9650 self.show_local_selections = true;
9651
9652 if moving_cursor {
9653 let rename_editor = rename.editor.read(cx);
9654 let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
9655
9656 // Update the selection to match the position of the selection inside
9657 // the rename editor.
9658 let snapshot = self.buffer.read(cx).read(cx);
9659 let rename_range = rename.range.to_offset(&snapshot);
9660 let cursor_in_editor = snapshot
9661 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
9662 .min(rename_range.end);
9663 drop(snapshot);
9664
9665 self.change_selections(None, cx, |s| {
9666 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
9667 });
9668 } else {
9669 self.refresh_document_highlights(cx);
9670 }
9671
9672 Some(rename)
9673 }
9674
9675 pub fn pending_rename(&self) -> Option<&RenameState> {
9676 self.pending_rename.as_ref()
9677 }
9678
9679 fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
9680 let project = match &self.project {
9681 Some(project) => project.clone(),
9682 None => return None,
9683 };
9684
9685 Some(self.perform_format(project, FormatTrigger::Manual, cx))
9686 }
9687
9688 fn perform_format(
9689 &mut self,
9690 project: Model<Project>,
9691 trigger: FormatTrigger,
9692 cx: &mut ViewContext<Self>,
9693 ) -> Task<Result<()>> {
9694 let buffer = self.buffer().clone();
9695 let mut buffers = buffer.read(cx).all_buffers();
9696 if trigger == FormatTrigger::Save {
9697 buffers.retain(|buffer| buffer.read(cx).is_dirty());
9698 }
9699
9700 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
9701 let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
9702
9703 cx.spawn(|_, mut cx| async move {
9704 let transaction = futures::select_biased! {
9705 () = timeout => {
9706 log::warn!("timed out waiting for formatting");
9707 None
9708 }
9709 transaction = format.log_err().fuse() => transaction,
9710 };
9711
9712 buffer
9713 .update(&mut cx, |buffer, cx| {
9714 if let Some(transaction) = transaction {
9715 if !buffer.is_singleton() {
9716 buffer.push_transaction(&transaction.0, cx);
9717 }
9718 }
9719
9720 cx.notify();
9721 })
9722 .ok();
9723
9724 Ok(())
9725 })
9726 }
9727
9728 fn restart_language_server(&mut self, _: &RestartLanguageServer, cx: &mut ViewContext<Self>) {
9729 if let Some(project) = self.project.clone() {
9730 self.buffer.update(cx, |multi_buffer, cx| {
9731 project.update(cx, |project, cx| {
9732 project.restart_language_servers_for_buffers(multi_buffer.all_buffers(), cx);
9733 });
9734 })
9735 }
9736 }
9737
9738 fn cancel_language_server_work(
9739 &mut self,
9740 _: &CancelLanguageServerWork,
9741 cx: &mut ViewContext<Self>,
9742 ) {
9743 if let Some(project) = self.project.clone() {
9744 self.buffer.update(cx, |multi_buffer, cx| {
9745 project.update(cx, |project, cx| {
9746 project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
9747 });
9748 })
9749 }
9750 }
9751
9752 fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
9753 cx.show_character_palette();
9754 }
9755
9756 fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
9757 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
9758 let buffer = self.buffer.read(cx).snapshot(cx);
9759 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
9760 let is_valid = buffer
9761 .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
9762 .any(|entry| {
9763 entry.diagnostic.is_primary
9764 && !entry.range.is_empty()
9765 && entry.range.start == primary_range_start
9766 && entry.diagnostic.message == active_diagnostics.primary_message
9767 });
9768
9769 if is_valid != active_diagnostics.is_valid {
9770 active_diagnostics.is_valid = is_valid;
9771 let mut new_styles = HashMap::default();
9772 for (block_id, diagnostic) in &active_diagnostics.blocks {
9773 new_styles.insert(
9774 *block_id,
9775 (
9776 None,
9777 diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid),
9778 ),
9779 );
9780 }
9781 self.display_map.update(cx, |display_map, cx| {
9782 display_map.replace_blocks(new_styles, cx)
9783 });
9784 }
9785 }
9786 }
9787
9788 fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
9789 self.dismiss_diagnostics(cx);
9790 let snapshot = self.snapshot(cx);
9791 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
9792 let buffer = self.buffer.read(cx).snapshot(cx);
9793
9794 let mut primary_range = None;
9795 let mut primary_message = None;
9796 let mut group_end = Point::zero();
9797 let diagnostic_group = buffer
9798 .diagnostic_group::<MultiBufferPoint>(group_id)
9799 .filter_map(|entry| {
9800 if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
9801 && (entry.range.start.row == entry.range.end.row
9802 || snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
9803 {
9804 return None;
9805 }
9806 if entry.range.end > group_end {
9807 group_end = entry.range.end;
9808 }
9809 if entry.diagnostic.is_primary {
9810 primary_range = Some(entry.range.clone());
9811 primary_message = Some(entry.diagnostic.message.clone());
9812 }
9813 Some(entry)
9814 })
9815 .collect::<Vec<_>>();
9816 let primary_range = primary_range?;
9817 let primary_message = primary_message?;
9818 let primary_range =
9819 buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
9820
9821 let blocks = display_map
9822 .insert_blocks(
9823 diagnostic_group.iter().map(|entry| {
9824 let diagnostic = entry.diagnostic.clone();
9825 let message_height = diagnostic.message.matches('\n').count() as u8 + 1;
9826 BlockProperties {
9827 style: BlockStyle::Fixed,
9828 position: buffer.anchor_after(entry.range.start),
9829 height: message_height,
9830 render: diagnostic_block_renderer(diagnostic, None, true, true),
9831 disposition: BlockDisposition::Below,
9832 }
9833 }),
9834 cx,
9835 )
9836 .into_iter()
9837 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
9838 .collect();
9839
9840 Some(ActiveDiagnosticGroup {
9841 primary_range,
9842 primary_message,
9843 group_id,
9844 blocks,
9845 is_valid: true,
9846 })
9847 });
9848 self.active_diagnostics.is_some()
9849 }
9850
9851 fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
9852 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
9853 self.display_map.update(cx, |display_map, cx| {
9854 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
9855 });
9856 cx.notify();
9857 }
9858 }
9859
9860 pub fn set_selections_from_remote(
9861 &mut self,
9862 selections: Vec<Selection<Anchor>>,
9863 pending_selection: Option<Selection<Anchor>>,
9864 cx: &mut ViewContext<Self>,
9865 ) {
9866 let old_cursor_position = self.selections.newest_anchor().head();
9867 self.selections.change_with(cx, |s| {
9868 s.select_anchors(selections);
9869 if let Some(pending_selection) = pending_selection {
9870 s.set_pending(pending_selection, SelectMode::Character);
9871 } else {
9872 s.clear_pending();
9873 }
9874 });
9875 self.selections_did_change(false, &old_cursor_position, true, cx);
9876 }
9877
9878 fn push_to_selection_history(&mut self) {
9879 self.selection_history.push(SelectionHistoryEntry {
9880 selections: self.selections.disjoint_anchors(),
9881 select_next_state: self.select_next_state.clone(),
9882 select_prev_state: self.select_prev_state.clone(),
9883 add_selections_state: self.add_selections_state.clone(),
9884 });
9885 }
9886
9887 pub fn transact(
9888 &mut self,
9889 cx: &mut ViewContext<Self>,
9890 update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
9891 ) -> Option<TransactionId> {
9892 self.start_transaction_at(Instant::now(), cx);
9893 update(self, cx);
9894 self.end_transaction_at(Instant::now(), cx)
9895 }
9896
9897 fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
9898 self.end_selection(cx);
9899 if let Some(tx_id) = self
9900 .buffer
9901 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
9902 {
9903 self.selection_history
9904 .insert_transaction(tx_id, self.selections.disjoint_anchors());
9905 cx.emit(EditorEvent::TransactionBegun {
9906 transaction_id: tx_id,
9907 })
9908 }
9909 }
9910
9911 fn end_transaction_at(
9912 &mut self,
9913 now: Instant,
9914 cx: &mut ViewContext<Self>,
9915 ) -> Option<TransactionId> {
9916 if let Some(transaction_id) = self
9917 .buffer
9918 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
9919 {
9920 if let Some((_, end_selections)) =
9921 self.selection_history.transaction_mut(transaction_id)
9922 {
9923 *end_selections = Some(self.selections.disjoint_anchors());
9924 } else {
9925 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
9926 }
9927
9928 cx.emit(EditorEvent::Edited { transaction_id });
9929 Some(transaction_id)
9930 } else {
9931 None
9932 }
9933 }
9934
9935 pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
9936 let mut fold_ranges = Vec::new();
9937
9938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9939
9940 let selections = self.selections.all_adjusted(cx);
9941 for selection in selections {
9942 let range = selection.range().sorted();
9943 let buffer_start_row = range.start.row;
9944
9945 for row in (0..=range.end.row).rev() {
9946 if let Some((foldable_range, fold_text)) =
9947 display_map.foldable_range(MultiBufferRow(row))
9948 {
9949 if foldable_range.end.row >= buffer_start_row {
9950 fold_ranges.push((foldable_range, fold_text));
9951 if row <= range.start.row {
9952 break;
9953 }
9954 }
9955 }
9956 }
9957 }
9958
9959 self.fold_ranges(fold_ranges, true, cx);
9960 }
9961
9962 pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
9963 let buffer_row = fold_at.buffer_row;
9964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9965
9966 if let Some((fold_range, placeholder)) = display_map.foldable_range(buffer_row) {
9967 let autoscroll = self
9968 .selections
9969 .all::<Point>(cx)
9970 .iter()
9971 .any(|selection| fold_range.overlaps(&selection.range()));
9972
9973 self.fold_ranges([(fold_range, placeholder)], autoscroll, cx);
9974 }
9975 }
9976
9977 pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
9978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9979 let buffer = &display_map.buffer_snapshot;
9980 let selections = self.selections.all::<Point>(cx);
9981 let ranges = selections
9982 .iter()
9983 .map(|s| {
9984 let range = s.display_range(&display_map).sorted();
9985 let mut start = range.start.to_point(&display_map);
9986 let mut end = range.end.to_point(&display_map);
9987 start.column = 0;
9988 end.column = buffer.line_len(MultiBufferRow(end.row));
9989 start..end
9990 })
9991 .collect::<Vec<_>>();
9992
9993 self.unfold_ranges(ranges, true, true, cx);
9994 }
9995
9996 pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
9997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9998
9999 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
10000 ..Point::new(
10001 unfold_at.buffer_row.0,
10002 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
10003 );
10004
10005 let autoscroll = self
10006 .selections
10007 .all::<Point>(cx)
10008 .iter()
10009 .any(|selection| selection.range().overlaps(&intersection_range));
10010
10011 self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
10012 }
10013
10014 pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
10015 let selections = self.selections.all::<Point>(cx);
10016 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10017 let line_mode = self.selections.line_mode;
10018 let ranges = selections.into_iter().map(|s| {
10019 if line_mode {
10020 let start = Point::new(s.start.row, 0);
10021 let end = Point::new(
10022 s.end.row,
10023 display_map
10024 .buffer_snapshot
10025 .line_len(MultiBufferRow(s.end.row)),
10026 );
10027 (start..end, display_map.fold_placeholder.clone())
10028 } else {
10029 (s.start..s.end, display_map.fold_placeholder.clone())
10030 }
10031 });
10032 self.fold_ranges(ranges, true, cx);
10033 }
10034
10035 pub fn fold_ranges<T: ToOffset + Clone>(
10036 &mut self,
10037 ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
10038 auto_scroll: bool,
10039 cx: &mut ViewContext<Self>,
10040 ) {
10041 let mut fold_ranges = Vec::new();
10042 let mut buffers_affected = HashMap::default();
10043 let multi_buffer = self.buffer().read(cx);
10044 for (fold_range, fold_text) in ranges {
10045 if let Some((_, buffer, _)) =
10046 multi_buffer.excerpt_containing(fold_range.start.clone(), cx)
10047 {
10048 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
10049 };
10050 fold_ranges.push((fold_range, fold_text));
10051 }
10052
10053 let mut ranges = fold_ranges.into_iter().peekable();
10054 if ranges.peek().is_some() {
10055 self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
10056
10057 if auto_scroll {
10058 self.request_autoscroll(Autoscroll::fit(), cx);
10059 }
10060
10061 for buffer in buffers_affected.into_values() {
10062 self.sync_expanded_diff_hunks(buffer, cx);
10063 }
10064
10065 cx.notify();
10066
10067 if let Some(active_diagnostics) = self.active_diagnostics.take() {
10068 // Clear diagnostics block when folding a range that contains it.
10069 let snapshot = self.snapshot(cx);
10070 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
10071 drop(snapshot);
10072 self.active_diagnostics = Some(active_diagnostics);
10073 self.dismiss_diagnostics(cx);
10074 } else {
10075 self.active_diagnostics = Some(active_diagnostics);
10076 }
10077 }
10078
10079 self.scrollbar_marker_state.dirty = true;
10080 }
10081 }
10082
10083 pub fn unfold_ranges<T: ToOffset + Clone>(
10084 &mut self,
10085 ranges: impl IntoIterator<Item = Range<T>>,
10086 inclusive: bool,
10087 auto_scroll: bool,
10088 cx: &mut ViewContext<Self>,
10089 ) {
10090 let mut unfold_ranges = Vec::new();
10091 let mut buffers_affected = HashMap::default();
10092 let multi_buffer = self.buffer().read(cx);
10093 for range in ranges {
10094 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
10095 buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
10096 };
10097 unfold_ranges.push(range);
10098 }
10099
10100 let mut ranges = unfold_ranges.into_iter().peekable();
10101 if ranges.peek().is_some() {
10102 self.display_map
10103 .update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
10104 if auto_scroll {
10105 self.request_autoscroll(Autoscroll::fit(), cx);
10106 }
10107
10108 for buffer in buffers_affected.into_values() {
10109 self.sync_expanded_diff_hunks(buffer, cx);
10110 }
10111
10112 cx.notify();
10113 self.scrollbar_marker_state.dirty = true;
10114 self.active_indent_guides_state.dirty = true;
10115 }
10116 }
10117
10118 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
10119 if hovered != self.gutter_hovered {
10120 self.gutter_hovered = hovered;
10121 cx.notify();
10122 }
10123 }
10124
10125 pub fn insert_blocks(
10126 &mut self,
10127 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
10128 autoscroll: Option<Autoscroll>,
10129 cx: &mut ViewContext<Self>,
10130 ) -> Vec<BlockId> {
10131 let blocks = self
10132 .display_map
10133 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
10134 if let Some(autoscroll) = autoscroll {
10135 self.request_autoscroll(autoscroll, cx);
10136 }
10137 blocks
10138 }
10139
10140 pub fn replace_blocks(
10141 &mut self,
10142 blocks: HashMap<BlockId, (Option<u8>, RenderBlock)>,
10143 autoscroll: Option<Autoscroll>,
10144 cx: &mut ViewContext<Self>,
10145 ) {
10146 self.display_map
10147 .update(cx, |display_map, cx| display_map.replace_blocks(blocks, cx));
10148 if let Some(autoscroll) = autoscroll {
10149 self.request_autoscroll(autoscroll, cx);
10150 }
10151 }
10152
10153 pub fn remove_blocks(
10154 &mut self,
10155 block_ids: HashSet<BlockId>,
10156 autoscroll: Option<Autoscroll>,
10157 cx: &mut ViewContext<Self>,
10158 ) {
10159 self.display_map.update(cx, |display_map, cx| {
10160 display_map.remove_blocks(block_ids, cx)
10161 });
10162 if let Some(autoscroll) = autoscroll {
10163 self.request_autoscroll(autoscroll, cx);
10164 }
10165 }
10166
10167 pub fn row_for_block(
10168 &self,
10169 block_id: BlockId,
10170 cx: &mut ViewContext<Self>,
10171 ) -> Option<DisplayRow> {
10172 self.display_map
10173 .update(cx, |map, cx| map.row_for_block(block_id, cx))
10174 }
10175
10176 pub fn insert_creases(
10177 &mut self,
10178 creases: impl IntoIterator<Item = Crease>,
10179 cx: &mut ViewContext<Self>,
10180 ) -> Vec<CreaseId> {
10181 self.display_map
10182 .update(cx, |map, cx| map.insert_creases(creases, cx))
10183 }
10184
10185 pub fn remove_creases(
10186 &mut self,
10187 ids: impl IntoIterator<Item = CreaseId>,
10188 cx: &mut ViewContext<Self>,
10189 ) {
10190 self.display_map
10191 .update(cx, |map, cx| map.remove_creases(ids, cx));
10192 }
10193
10194 pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
10195 self.display_map
10196 .update(cx, |map, cx| map.snapshot(cx))
10197 .longest_row()
10198 }
10199
10200 pub fn max_point(&self, cx: &mut AppContext) -> DisplayPoint {
10201 self.display_map
10202 .update(cx, |map, cx| map.snapshot(cx))
10203 .max_point()
10204 }
10205
10206 pub fn text(&self, cx: &AppContext) -> String {
10207 self.buffer.read(cx).read(cx).text()
10208 }
10209
10210 pub fn text_option(&self, cx: &AppContext) -> Option<String> {
10211 let text = self.text(cx);
10212 let text = text.trim();
10213
10214 if text.is_empty() {
10215 return None;
10216 }
10217
10218 Some(text.to_string())
10219 }
10220
10221 pub fn set_text(&mut self, text: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
10222 self.transact(cx, |this, cx| {
10223 this.buffer
10224 .read(cx)
10225 .as_singleton()
10226 .expect("you can only call set_text on editors for singleton buffers")
10227 .update(cx, |buffer, cx| buffer.set_text(text, cx));
10228 });
10229 }
10230
10231 pub fn display_text(&self, cx: &mut AppContext) -> String {
10232 self.display_map
10233 .update(cx, |map, cx| map.snapshot(cx))
10234 .text()
10235 }
10236
10237 pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
10238 let mut wrap_guides = smallvec::smallvec![];
10239
10240 if self.show_wrap_guides == Some(false) {
10241 return wrap_guides;
10242 }
10243
10244 let settings = self.buffer.read(cx).settings_at(0, cx);
10245 if settings.show_wrap_guides {
10246 if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
10247 wrap_guides.push((soft_wrap as usize, true));
10248 }
10249 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
10250 }
10251
10252 wrap_guides
10253 }
10254
10255 pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
10256 let settings = self.buffer.read(cx).settings_at(0, cx);
10257 let mode = self
10258 .soft_wrap_mode_override
10259 .unwrap_or_else(|| settings.soft_wrap);
10260 match mode {
10261 language_settings::SoftWrap::None => SoftWrap::None,
10262 language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine,
10263 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
10264 language_settings::SoftWrap::PreferredLineLength => {
10265 SoftWrap::Column(settings.preferred_line_length)
10266 }
10267 }
10268 }
10269
10270 pub fn set_soft_wrap_mode(
10271 &mut self,
10272 mode: language_settings::SoftWrap,
10273 cx: &mut ViewContext<Self>,
10274 ) {
10275 self.soft_wrap_mode_override = Some(mode);
10276 cx.notify();
10277 }
10278
10279 pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
10280 let rem_size = cx.rem_size();
10281 self.display_map.update(cx, |map, cx| {
10282 map.set_font(
10283 style.text.font(),
10284 style.text.font_size.to_pixels(rem_size),
10285 cx,
10286 )
10287 });
10288 self.style = Some(style);
10289 }
10290
10291 pub fn style(&self) -> Option<&EditorStyle> {
10292 self.style.as_ref()
10293 }
10294
10295 // Called by the element. This method is not designed to be called outside of the editor
10296 // element's layout code because it does not notify when rewrapping is computed synchronously.
10297 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
10298 self.display_map
10299 .update(cx, |map, cx| map.set_wrap_width(width, cx))
10300 }
10301
10302 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
10303 if self.soft_wrap_mode_override.is_some() {
10304 self.soft_wrap_mode_override.take();
10305 } else {
10306 let soft_wrap = match self.soft_wrap_mode(cx) {
10307 SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
10308 SoftWrap::EditorWidth | SoftWrap::Column(_) => {
10309 language_settings::SoftWrap::PreferLine
10310 }
10311 };
10312 self.soft_wrap_mode_override = Some(soft_wrap);
10313 }
10314 cx.notify();
10315 }
10316
10317 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, cx: &mut ViewContext<Self>) {
10318 let Some(workspace) = self.workspace() else {
10319 return;
10320 };
10321 let fs = workspace.read(cx).app_state().fs.clone();
10322 let current_show = TabBarSettings::get_global(cx).show;
10323 update_settings_file::<TabBarSettings>(fs, cx, move |setting| {
10324 setting.show = Some(!current_show);
10325 });
10326 }
10327
10328 pub fn toggle_indent_guides(&mut self, _: &ToggleIndentGuides, cx: &mut ViewContext<Self>) {
10329 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
10330 self.buffer
10331 .read(cx)
10332 .settings_at(0, cx)
10333 .indent_guides
10334 .enabled
10335 });
10336 self.show_indent_guides = Some(!currently_enabled);
10337 cx.notify();
10338 }
10339
10340 fn should_show_indent_guides(&self) -> Option<bool> {
10341 self.show_indent_guides
10342 }
10343
10344 pub fn toggle_line_numbers(&mut self, _: &ToggleLineNumbers, cx: &mut ViewContext<Self>) {
10345 let mut editor_settings = EditorSettings::get_global(cx).clone();
10346 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
10347 EditorSettings::override_global(editor_settings, cx);
10348 }
10349
10350 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
10351 self.show_gutter = show_gutter;
10352 cx.notify();
10353 }
10354
10355 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
10356 self.show_line_numbers = Some(show_line_numbers);
10357 cx.notify();
10358 }
10359
10360 pub fn set_show_git_diff_gutter(
10361 &mut self,
10362 show_git_diff_gutter: bool,
10363 cx: &mut ViewContext<Self>,
10364 ) {
10365 self.show_git_diff_gutter = Some(show_git_diff_gutter);
10366 cx.notify();
10367 }
10368
10369 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut ViewContext<Self>) {
10370 self.show_code_actions = Some(show_code_actions);
10371 cx.notify();
10372 }
10373
10374 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut ViewContext<Self>) {
10375 self.show_runnables = Some(show_runnables);
10376 cx.notify();
10377 }
10378
10379 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
10380 self.show_wrap_guides = Some(show_wrap_guides);
10381 cx.notify();
10382 }
10383
10384 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut ViewContext<Self>) {
10385 self.show_indent_guides = Some(show_indent_guides);
10386 cx.notify();
10387 }
10388
10389 pub fn working_directory(&self, cx: &WindowContext) -> Option<PathBuf> {
10390 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10391 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10392 if let Some(dir) = file.abs_path(cx).parent() {
10393 return Some(dir.to_owned());
10394 }
10395 }
10396
10397 if let Some(project_path) = buffer.read(cx).project_path(cx) {
10398 return Some(project_path.path.to_path_buf());
10399 }
10400 }
10401
10402 None
10403 }
10404
10405 pub fn reveal_in_finder(&mut self, _: &RevealInFileManager, cx: &mut ViewContext<Self>) {
10406 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10407 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10408 cx.reveal_path(&file.abs_path(cx));
10409 }
10410 }
10411 }
10412
10413 pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
10414 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10415 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10416 if let Some(path) = file.abs_path(cx).to_str() {
10417 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
10418 }
10419 }
10420 }
10421 }
10422
10423 pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
10424 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10425 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
10426 if let Some(path) = file.path().to_str() {
10427 cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
10428 }
10429 }
10430 }
10431 }
10432
10433 pub fn toggle_git_blame(&mut self, _: &ToggleGitBlame, cx: &mut ViewContext<Self>) {
10434 self.show_git_blame_gutter = !self.show_git_blame_gutter;
10435
10436 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
10437 self.start_git_blame(true, cx);
10438 }
10439
10440 cx.notify();
10441 }
10442
10443 pub fn toggle_git_blame_inline(
10444 &mut self,
10445 _: &ToggleGitBlameInline,
10446 cx: &mut ViewContext<Self>,
10447 ) {
10448 self.toggle_git_blame_inline_internal(true, cx);
10449 cx.notify();
10450 }
10451
10452 pub fn git_blame_inline_enabled(&self) -> bool {
10453 self.git_blame_inline_enabled
10454 }
10455
10456 pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
10457 self.show_selection_menu = self
10458 .show_selection_menu
10459 .map(|show_selections_menu| !show_selections_menu)
10460 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
10461
10462 cx.notify();
10463 }
10464
10465 pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool {
10466 self.show_selection_menu
10467 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
10468 }
10469
10470 fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
10471 if let Some(project) = self.project.as_ref() {
10472 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
10473 return;
10474 };
10475
10476 if buffer.read(cx).file().is_none() {
10477 return;
10478 }
10479
10480 let focused = self.focus_handle(cx).contains_focused(cx);
10481
10482 let project = project.clone();
10483 let blame =
10484 cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
10485 self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
10486 self.blame = Some(blame);
10487 }
10488 }
10489
10490 fn toggle_git_blame_inline_internal(
10491 &mut self,
10492 user_triggered: bool,
10493 cx: &mut ViewContext<Self>,
10494 ) {
10495 if self.git_blame_inline_enabled {
10496 self.git_blame_inline_enabled = false;
10497 self.show_git_blame_inline = false;
10498 self.show_git_blame_inline_delay_task.take();
10499 } else {
10500 self.git_blame_inline_enabled = true;
10501 self.start_git_blame_inline(user_triggered, cx);
10502 }
10503
10504 cx.notify();
10505 }
10506
10507 fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
10508 self.start_git_blame(user_triggered, cx);
10509
10510 if ProjectSettings::get_global(cx)
10511 .git
10512 .inline_blame_delay()
10513 .is_some()
10514 {
10515 self.start_inline_blame_timer(cx);
10516 } else {
10517 self.show_git_blame_inline = true
10518 }
10519 }
10520
10521 pub fn blame(&self) -> Option<&Model<GitBlame>> {
10522 self.blame.as_ref()
10523 }
10524
10525 pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
10526 self.show_git_blame_gutter && self.has_blame_entries(cx)
10527 }
10528
10529 pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
10530 self.show_git_blame_inline
10531 && self.focus_handle.is_focused(cx)
10532 && !self.newest_selection_head_on_empty_line(cx)
10533 && self.has_blame_entries(cx)
10534 }
10535
10536 fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
10537 self.blame()
10538 .map_or(false, |blame| blame.read(cx).has_generated_entries())
10539 }
10540
10541 fn newest_selection_head_on_empty_line(&mut self, cx: &mut WindowContext) -> bool {
10542 let cursor_anchor = self.selections.newest_anchor().head();
10543
10544 let snapshot = self.buffer.read(cx).snapshot(cx);
10545 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
10546
10547 snapshot.line_len(buffer_row) == 0
10548 }
10549
10550 fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Result<url::Url> {
10551 let (path, selection, repo) = maybe!({
10552 let project_handle = self.project.as_ref()?.clone();
10553 let project = project_handle.read(cx);
10554
10555 let selection = self.selections.newest::<Point>(cx);
10556 let selection_range = selection.range();
10557
10558 let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
10559 (buffer, selection_range.start.row..selection_range.end.row)
10560 } else {
10561 let buffer_ranges = self
10562 .buffer()
10563 .read(cx)
10564 .range_to_buffer_ranges(selection_range, cx);
10565
10566 let (buffer, range, _) = if selection.reversed {
10567 buffer_ranges.first()
10568 } else {
10569 buffer_ranges.last()
10570 }?;
10571
10572 let snapshot = buffer.read(cx).snapshot();
10573 let selection = text::ToPoint::to_point(&range.start, &snapshot).row
10574 ..text::ToPoint::to_point(&range.end, &snapshot).row;
10575 (buffer.clone(), selection)
10576 };
10577
10578 let path = buffer
10579 .read(cx)
10580 .file()?
10581 .as_local()?
10582 .path()
10583 .to_str()?
10584 .to_string();
10585 let repo = project.get_repo(&buffer.read(cx).project_path(cx)?, cx)?;
10586 Some((path, selection, repo))
10587 })
10588 .ok_or_else(|| anyhow!("unable to open git repository"))?;
10589
10590 const REMOTE_NAME: &str = "origin";
10591 let origin_url = repo
10592 .remote_url(REMOTE_NAME)
10593 .ok_or_else(|| anyhow!("remote \"{REMOTE_NAME}\" not found"))?;
10594 let sha = repo
10595 .head_sha()
10596 .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
10597
10598 let (provider, remote) =
10599 parse_git_remote_url(GitHostingProviderRegistry::default_global(cx), &origin_url)
10600 .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
10601
10602 Ok(provider.build_permalink(
10603 remote,
10604 BuildPermalinkParams {
10605 sha: &sha,
10606 path: &path,
10607 selection: Some(selection),
10608 },
10609 ))
10610 }
10611
10612 pub fn copy_permalink_to_line(&mut self, _: &CopyPermalinkToLine, cx: &mut ViewContext<Self>) {
10613 let permalink = self.get_permalink_to_line(cx);
10614
10615 match permalink {
10616 Ok(permalink) => {
10617 cx.write_to_clipboard(ClipboardItem::new(permalink.to_string()));
10618 }
10619 Err(err) => {
10620 let message = format!("Failed to copy permalink: {err}");
10621
10622 Err::<(), anyhow::Error>(err).log_err();
10623
10624 if let Some(workspace) = self.workspace() {
10625 workspace.update(cx, |workspace, cx| {
10626 struct CopyPermalinkToLine;
10627
10628 workspace.show_toast(
10629 Toast::new(NotificationId::unique::<CopyPermalinkToLine>(), message),
10630 cx,
10631 )
10632 })
10633 }
10634 }
10635 }
10636 }
10637
10638 pub fn open_permalink_to_line(&mut self, _: &OpenPermalinkToLine, cx: &mut ViewContext<Self>) {
10639 let permalink = self.get_permalink_to_line(cx);
10640
10641 match permalink {
10642 Ok(permalink) => {
10643 cx.open_url(permalink.as_ref());
10644 }
10645 Err(err) => {
10646 let message = format!("Failed to open permalink: {err}");
10647
10648 Err::<(), anyhow::Error>(err).log_err();
10649
10650 if let Some(workspace) = self.workspace() {
10651 workspace.update(cx, |workspace, cx| {
10652 struct OpenPermalinkToLine;
10653
10654 workspace.show_toast(
10655 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
10656 cx,
10657 )
10658 })
10659 }
10660 }
10661 }
10662 }
10663
10664 /// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
10665 /// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
10666 /// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
10667 pub fn highlight_rows<T: 'static>(
10668 &mut self,
10669 rows: RangeInclusive<Anchor>,
10670 color: Option<Hsla>,
10671 should_autoscroll: bool,
10672 cx: &mut ViewContext<Self>,
10673 ) {
10674 let snapshot = self.buffer().read(cx).snapshot(cx);
10675 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
10676 let existing_highlight_index = row_highlights.binary_search_by(|highlight| {
10677 highlight
10678 .range
10679 .start()
10680 .cmp(&rows.start(), &snapshot)
10681 .then(highlight.range.end().cmp(&rows.end(), &snapshot))
10682 });
10683 match (color, existing_highlight_index) {
10684 (Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert(
10685 ix,
10686 RowHighlight {
10687 index: post_inc(&mut self.highlight_order),
10688 range: rows,
10689 should_autoscroll,
10690 color,
10691 },
10692 ),
10693 (None, Ok(i)) => {
10694 row_highlights.remove(i);
10695 }
10696 }
10697 }
10698
10699 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
10700 pub fn clear_row_highlights<T: 'static>(&mut self) {
10701 self.highlighted_rows.remove(&TypeId::of::<T>());
10702 }
10703
10704 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
10705 pub fn highlighted_rows<T: 'static>(
10706 &self,
10707 ) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
10708 Some(
10709 self.highlighted_rows
10710 .get(&TypeId::of::<T>())?
10711 .iter()
10712 .map(|highlight| (&highlight.range, highlight.color.as_ref())),
10713 )
10714 }
10715
10716 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
10717 /// Rerturns a map of display rows that are highlighted and their corresponding highlight color.
10718 /// Allows to ignore certain kinds of highlights.
10719 pub fn highlighted_display_rows(
10720 &mut self,
10721 cx: &mut WindowContext,
10722 ) -> BTreeMap<DisplayRow, Hsla> {
10723 let snapshot = self.snapshot(cx);
10724 let mut used_highlight_orders = HashMap::default();
10725 self.highlighted_rows
10726 .iter()
10727 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
10728 .fold(
10729 BTreeMap::<DisplayRow, Hsla>::new(),
10730 |mut unique_rows, highlight| {
10731 let start_row = highlight.range.start().to_display_point(&snapshot).row();
10732 let end_row = highlight.range.end().to_display_point(&snapshot).row();
10733 for row in start_row.0..=end_row.0 {
10734 let used_index =
10735 used_highlight_orders.entry(row).or_insert(highlight.index);
10736 if highlight.index >= *used_index {
10737 *used_index = highlight.index;
10738 match highlight.color {
10739 Some(hsla) => unique_rows.insert(DisplayRow(row), hsla),
10740 None => unique_rows.remove(&DisplayRow(row)),
10741 };
10742 }
10743 }
10744 unique_rows
10745 },
10746 )
10747 }
10748
10749 pub fn highlighted_display_row_for_autoscroll(
10750 &self,
10751 snapshot: &DisplaySnapshot,
10752 ) -> Option<DisplayRow> {
10753 self.highlighted_rows
10754 .values()
10755 .flat_map(|highlighted_rows| highlighted_rows.iter())
10756 .filter_map(|highlight| {
10757 if highlight.color.is_none() || !highlight.should_autoscroll {
10758 return None;
10759 }
10760 Some(highlight.range.start().to_display_point(&snapshot).row())
10761 })
10762 .min()
10763 }
10764
10765 pub fn set_search_within_ranges(
10766 &mut self,
10767 ranges: &[Range<Anchor>],
10768 cx: &mut ViewContext<Self>,
10769 ) {
10770 self.highlight_background::<SearchWithinRange>(
10771 ranges,
10772 |colors| colors.editor_document_highlight_read_background,
10773 cx,
10774 )
10775 }
10776
10777 pub fn set_breadcrumb_header(&mut self, new_header: String) {
10778 self.breadcrumb_header = Some(new_header);
10779 }
10780
10781 pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
10782 self.clear_background_highlights::<SearchWithinRange>(cx);
10783 }
10784
10785 pub fn highlight_background<T: 'static>(
10786 &mut self,
10787 ranges: &[Range<Anchor>],
10788 color_fetcher: fn(&ThemeColors) -> Hsla,
10789 cx: &mut ViewContext<Self>,
10790 ) {
10791 let snapshot = self.snapshot(cx);
10792 // this is to try and catch a panic sooner
10793 for range in ranges {
10794 snapshot
10795 .buffer_snapshot
10796 .summary_for_anchor::<usize>(&range.start);
10797 snapshot
10798 .buffer_snapshot
10799 .summary_for_anchor::<usize>(&range.end);
10800 }
10801
10802 self.background_highlights
10803 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
10804 self.scrollbar_marker_state.dirty = true;
10805 cx.notify();
10806 }
10807
10808 pub fn clear_background_highlights<T: 'static>(
10809 &mut self,
10810 cx: &mut ViewContext<Self>,
10811 ) -> Option<BackgroundHighlight> {
10812 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
10813 if !text_highlights.1.is_empty() {
10814 self.scrollbar_marker_state.dirty = true;
10815 cx.notify();
10816 }
10817 Some(text_highlights)
10818 }
10819
10820 pub fn highlight_gutter<T: 'static>(
10821 &mut self,
10822 ranges: &[Range<Anchor>],
10823 color_fetcher: fn(&AppContext) -> Hsla,
10824 cx: &mut ViewContext<Self>,
10825 ) {
10826 self.gutter_highlights
10827 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
10828 cx.notify();
10829 }
10830
10831 pub fn clear_gutter_highlights<T: 'static>(
10832 &mut self,
10833 cx: &mut ViewContext<Self>,
10834 ) -> Option<GutterHighlight> {
10835 cx.notify();
10836 self.gutter_highlights.remove(&TypeId::of::<T>())
10837 }
10838
10839 #[cfg(feature = "test-support")]
10840 pub fn all_text_background_highlights(
10841 &mut self,
10842 cx: &mut ViewContext<Self>,
10843 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
10844 let snapshot = self.snapshot(cx);
10845 let buffer = &snapshot.buffer_snapshot;
10846 let start = buffer.anchor_before(0);
10847 let end = buffer.anchor_after(buffer.len());
10848 let theme = cx.theme().colors();
10849 self.background_highlights_in_range(start..end, &snapshot, theme)
10850 }
10851
10852 #[cfg(feature = "test-support")]
10853 pub fn search_background_highlights(
10854 &mut self,
10855 cx: &mut ViewContext<Self>,
10856 ) -> Vec<Range<Point>> {
10857 let snapshot = self.buffer().read(cx).snapshot(cx);
10858
10859 let highlights = self
10860 .background_highlights
10861 .get(&TypeId::of::<items::BufferSearchHighlights>());
10862
10863 if let Some((_color, ranges)) = highlights {
10864 ranges
10865 .iter()
10866 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
10867 .collect_vec()
10868 } else {
10869 vec![]
10870 }
10871 }
10872
10873 fn document_highlights_for_position<'a>(
10874 &'a self,
10875 position: Anchor,
10876 buffer: &'a MultiBufferSnapshot,
10877 ) -> impl 'a + Iterator<Item = &Range<Anchor>> {
10878 let read_highlights = self
10879 .background_highlights
10880 .get(&TypeId::of::<DocumentHighlightRead>())
10881 .map(|h| &h.1);
10882 let write_highlights = self
10883 .background_highlights
10884 .get(&TypeId::of::<DocumentHighlightWrite>())
10885 .map(|h| &h.1);
10886 let left_position = position.bias_left(buffer);
10887 let right_position = position.bias_right(buffer);
10888 read_highlights
10889 .into_iter()
10890 .chain(write_highlights)
10891 .flat_map(move |ranges| {
10892 let start_ix = match ranges.binary_search_by(|probe| {
10893 let cmp = probe.end.cmp(&left_position, buffer);
10894 if cmp.is_ge() {
10895 Ordering::Greater
10896 } else {
10897 Ordering::Less
10898 }
10899 }) {
10900 Ok(i) | Err(i) => i,
10901 };
10902
10903 ranges[start_ix..]
10904 .iter()
10905 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
10906 })
10907 }
10908
10909 pub fn has_background_highlights<T: 'static>(&self) -> bool {
10910 self.background_highlights
10911 .get(&TypeId::of::<T>())
10912 .map_or(false, |(_, highlights)| !highlights.is_empty())
10913 }
10914
10915 pub fn background_highlights_in_range(
10916 &self,
10917 search_range: Range<Anchor>,
10918 display_snapshot: &DisplaySnapshot,
10919 theme: &ThemeColors,
10920 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
10921 let mut results = Vec::new();
10922 for (color_fetcher, ranges) in self.background_highlights.values() {
10923 let color = color_fetcher(theme);
10924 let start_ix = match ranges.binary_search_by(|probe| {
10925 let cmp = probe
10926 .end
10927 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
10928 if cmp.is_gt() {
10929 Ordering::Greater
10930 } else {
10931 Ordering::Less
10932 }
10933 }) {
10934 Ok(i) | Err(i) => i,
10935 };
10936 for range in &ranges[start_ix..] {
10937 if range
10938 .start
10939 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
10940 .is_ge()
10941 {
10942 break;
10943 }
10944
10945 let start = range.start.to_display_point(&display_snapshot);
10946 let end = range.end.to_display_point(&display_snapshot);
10947 results.push((start..end, color))
10948 }
10949 }
10950 results
10951 }
10952
10953 pub fn background_highlight_row_ranges<T: 'static>(
10954 &self,
10955 search_range: Range<Anchor>,
10956 display_snapshot: &DisplaySnapshot,
10957 count: usize,
10958 ) -> Vec<RangeInclusive<DisplayPoint>> {
10959 let mut results = Vec::new();
10960 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
10961 return vec![];
10962 };
10963
10964 let start_ix = match ranges.binary_search_by(|probe| {
10965 let cmp = probe
10966 .end
10967 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
10968 if cmp.is_gt() {
10969 Ordering::Greater
10970 } else {
10971 Ordering::Less
10972 }
10973 }) {
10974 Ok(i) | Err(i) => i,
10975 };
10976 let mut push_region = |start: Option<Point>, end: Option<Point>| {
10977 if let (Some(start_display), Some(end_display)) = (start, end) {
10978 results.push(
10979 start_display.to_display_point(display_snapshot)
10980 ..=end_display.to_display_point(display_snapshot),
10981 );
10982 }
10983 };
10984 let mut start_row: Option<Point> = None;
10985 let mut end_row: Option<Point> = None;
10986 if ranges.len() > count {
10987 return Vec::new();
10988 }
10989 for range in &ranges[start_ix..] {
10990 if range
10991 .start
10992 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
10993 .is_ge()
10994 {
10995 break;
10996 }
10997 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
10998 if let Some(current_row) = &end_row {
10999 if end.row == current_row.row {
11000 continue;
11001 }
11002 }
11003 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
11004 if start_row.is_none() {
11005 assert_eq!(end_row, None);
11006 start_row = Some(start);
11007 end_row = Some(end);
11008 continue;
11009 }
11010 if let Some(current_end) = end_row.as_mut() {
11011 if start.row > current_end.row + 1 {
11012 push_region(start_row, end_row);
11013 start_row = Some(start);
11014 end_row = Some(end);
11015 } else {
11016 // Merge two hunks.
11017 *current_end = end;
11018 }
11019 } else {
11020 unreachable!();
11021 }
11022 }
11023 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
11024 push_region(start_row, end_row);
11025 results
11026 }
11027
11028 pub fn gutter_highlights_in_range(
11029 &self,
11030 search_range: Range<Anchor>,
11031 display_snapshot: &DisplaySnapshot,
11032 cx: &AppContext,
11033 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
11034 let mut results = Vec::new();
11035 for (color_fetcher, ranges) in self.gutter_highlights.values() {
11036 let color = color_fetcher(cx);
11037 let start_ix = match ranges.binary_search_by(|probe| {
11038 let cmp = probe
11039 .end
11040 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
11041 if cmp.is_gt() {
11042 Ordering::Greater
11043 } else {
11044 Ordering::Less
11045 }
11046 }) {
11047 Ok(i) | Err(i) => i,
11048 };
11049 for range in &ranges[start_ix..] {
11050 if range
11051 .start
11052 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
11053 .is_ge()
11054 {
11055 break;
11056 }
11057
11058 let start = range.start.to_display_point(&display_snapshot);
11059 let end = range.end.to_display_point(&display_snapshot);
11060 results.push((start..end, color))
11061 }
11062 }
11063 results
11064 }
11065
11066 /// Get the text ranges corresponding to the redaction query
11067 pub fn redacted_ranges(
11068 &self,
11069 search_range: Range<Anchor>,
11070 display_snapshot: &DisplaySnapshot,
11071 cx: &WindowContext,
11072 ) -> Vec<Range<DisplayPoint>> {
11073 display_snapshot
11074 .buffer_snapshot
11075 .redacted_ranges(search_range, |file| {
11076 if let Some(file) = file {
11077 file.is_private()
11078 && EditorSettings::get(Some(file.as_ref().into()), cx).redact_private_values
11079 } else {
11080 false
11081 }
11082 })
11083 .map(|range| {
11084 range.start.to_display_point(display_snapshot)
11085 ..range.end.to_display_point(display_snapshot)
11086 })
11087 .collect()
11088 }
11089
11090 pub fn highlight_text<T: 'static>(
11091 &mut self,
11092 ranges: Vec<Range<Anchor>>,
11093 style: HighlightStyle,
11094 cx: &mut ViewContext<Self>,
11095 ) {
11096 self.display_map.update(cx, |map, _| {
11097 map.highlight_text(TypeId::of::<T>(), ranges, style)
11098 });
11099 cx.notify();
11100 }
11101
11102 pub(crate) fn highlight_inlays<T: 'static>(
11103 &mut self,
11104 highlights: Vec<InlayHighlight>,
11105 style: HighlightStyle,
11106 cx: &mut ViewContext<Self>,
11107 ) {
11108 self.display_map.update(cx, |map, _| {
11109 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
11110 });
11111 cx.notify();
11112 }
11113
11114 pub fn text_highlights<'a, T: 'static>(
11115 &'a self,
11116 cx: &'a AppContext,
11117 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
11118 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
11119 }
11120
11121 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut ViewContext<Self>) {
11122 let cleared = self
11123 .display_map
11124 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
11125 if cleared {
11126 cx.notify();
11127 }
11128 }
11129
11130 pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
11131 (self.read_only(cx) || self.blink_manager.read(cx).visible())
11132 && self.focus_handle.is_focused(cx)
11133 }
11134
11135 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut ViewContext<Self>) {
11136 self.show_cursor_when_unfocused = is_enabled;
11137 cx.notify();
11138 }
11139
11140 fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
11141 cx.notify();
11142 }
11143
11144 fn on_buffer_event(
11145 &mut self,
11146 multibuffer: Model<MultiBuffer>,
11147 event: &multi_buffer::Event,
11148 cx: &mut ViewContext<Self>,
11149 ) {
11150 match event {
11151 multi_buffer::Event::Edited {
11152 singleton_buffer_edited,
11153 } => {
11154 self.scrollbar_marker_state.dirty = true;
11155 self.active_indent_guides_state.dirty = true;
11156 self.refresh_active_diagnostics(cx);
11157 self.refresh_code_actions(cx);
11158 if self.has_active_inline_completion(cx) {
11159 self.update_visible_inline_completion(cx);
11160 }
11161 cx.emit(EditorEvent::BufferEdited);
11162 cx.emit(SearchEvent::MatchesInvalidated);
11163 if *singleton_buffer_edited {
11164 if let Some(project) = &self.project {
11165 let project = project.read(cx);
11166 #[allow(clippy::mutable_key_type)]
11167 let languages_affected = multibuffer
11168 .read(cx)
11169 .all_buffers()
11170 .into_iter()
11171 .filter_map(|buffer| {
11172 let buffer = buffer.read(cx);
11173 let language = buffer.language()?;
11174 if project.is_local()
11175 && project.language_servers_for_buffer(buffer, cx).count() == 0
11176 {
11177 None
11178 } else {
11179 Some(language)
11180 }
11181 })
11182 .cloned()
11183 .collect::<HashSet<_>>();
11184 if !languages_affected.is_empty() {
11185 self.refresh_inlay_hints(
11186 InlayHintRefreshReason::BufferEdited(languages_affected),
11187 cx,
11188 );
11189 }
11190 }
11191 }
11192
11193 let Some(project) = &self.project else { return };
11194 let telemetry = project.read(cx).client().telemetry().clone();
11195 refresh_linked_ranges(self, cx);
11196 telemetry.log_edit_event("editor");
11197 }
11198 multi_buffer::Event::ExcerptsAdded {
11199 buffer,
11200 predecessor,
11201 excerpts,
11202 } => {
11203 self.tasks_update_task = Some(self.refresh_runnables(cx));
11204 cx.emit(EditorEvent::ExcerptsAdded {
11205 buffer: buffer.clone(),
11206 predecessor: *predecessor,
11207 excerpts: excerpts.clone(),
11208 });
11209 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
11210 }
11211 multi_buffer::Event::ExcerptsRemoved { ids } => {
11212 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
11213 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
11214 }
11215 multi_buffer::Event::ExcerptsEdited { ids } => {
11216 cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
11217 }
11218 multi_buffer::Event::ExcerptsExpanded { ids } => {
11219 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
11220 }
11221 multi_buffer::Event::Reparsed(buffer_id) => {
11222 self.tasks_update_task = Some(self.refresh_runnables(cx));
11223
11224 cx.emit(EditorEvent::Reparsed(*buffer_id));
11225 }
11226 multi_buffer::Event::LanguageChanged(buffer_id) => {
11227 linked_editing_ranges::refresh_linked_ranges(self, cx);
11228 cx.emit(EditorEvent::Reparsed(*buffer_id));
11229 cx.notify();
11230 }
11231 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
11232 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
11233 multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
11234 cx.emit(EditorEvent::TitleChanged)
11235 }
11236 multi_buffer::Event::DiffBaseChanged => {
11237 self.scrollbar_marker_state.dirty = true;
11238 cx.emit(EditorEvent::DiffBaseChanged);
11239 cx.notify();
11240 }
11241 multi_buffer::Event::DiffUpdated { buffer } => {
11242 self.sync_expanded_diff_hunks(buffer.clone(), cx);
11243 cx.notify();
11244 }
11245 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
11246 multi_buffer::Event::DiagnosticsUpdated => {
11247 self.refresh_active_diagnostics(cx);
11248 self.scrollbar_marker_state.dirty = true;
11249 cx.notify();
11250 }
11251 _ => {}
11252 };
11253 }
11254
11255 fn on_display_map_changed(&mut self, _: Model<DisplayMap>, cx: &mut ViewContext<Self>) {
11256 cx.notify();
11257 }
11258
11259 fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
11260 self.tasks_update_task = Some(self.refresh_runnables(cx));
11261 self.refresh_inline_completion(true, cx);
11262 self.refresh_inlay_hints(
11263 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
11264 self.selections.newest_anchor().head(),
11265 &self.buffer.read(cx).snapshot(cx),
11266 cx,
11267 )),
11268 cx,
11269 );
11270 let editor_settings = EditorSettings::get_global(cx);
11271 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
11272 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
11273
11274 let project_settings = ProjectSettings::get_global(cx);
11275 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
11276
11277 if self.mode == EditorMode::Full {
11278 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
11279 if self.git_blame_inline_enabled != inline_blame_enabled {
11280 self.toggle_git_blame_inline_internal(false, cx);
11281 }
11282 }
11283
11284 cx.notify();
11285 }
11286
11287 pub fn set_searchable(&mut self, searchable: bool) {
11288 self.searchable = searchable;
11289 }
11290
11291 pub fn searchable(&self) -> bool {
11292 self.searchable
11293 }
11294
11295 fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
11296 self.open_excerpts_common(true, cx)
11297 }
11298
11299 fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
11300 self.open_excerpts_common(false, cx)
11301 }
11302
11303 fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
11304 let buffer = self.buffer.read(cx);
11305 if buffer.is_singleton() {
11306 cx.propagate();
11307 return;
11308 }
11309
11310 let Some(workspace) = self.workspace() else {
11311 cx.propagate();
11312 return;
11313 };
11314
11315 let mut new_selections_by_buffer = HashMap::default();
11316 for selection in self.selections.all::<usize>(cx) {
11317 for (buffer, mut range, _) in
11318 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
11319 {
11320 if selection.reversed {
11321 mem::swap(&mut range.start, &mut range.end);
11322 }
11323 new_selections_by_buffer
11324 .entry(buffer)
11325 .or_insert(Vec::new())
11326 .push(range)
11327 }
11328 }
11329
11330 // We defer the pane interaction because we ourselves are a workspace item
11331 // and activating a new item causes the pane to call a method on us reentrantly,
11332 // which panics if we're on the stack.
11333 cx.window_context().defer(move |cx| {
11334 workspace.update(cx, |workspace, cx| {
11335 let pane = if split {
11336 workspace.adjacent_pane(cx)
11337 } else {
11338 workspace.active_pane().clone()
11339 };
11340
11341 for (buffer, ranges) in new_selections_by_buffer {
11342 let editor = workspace.open_project_item::<Self>(pane.clone(), buffer, cx);
11343 editor.update(cx, |editor, cx| {
11344 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
11345 s.select_ranges(ranges);
11346 });
11347 });
11348 }
11349 })
11350 });
11351 }
11352
11353 fn jump(
11354 &mut self,
11355 path: ProjectPath,
11356 position: Point,
11357 anchor: language::Anchor,
11358 offset_from_top: u32,
11359 cx: &mut ViewContext<Self>,
11360 ) {
11361 let workspace = self.workspace();
11362 cx.spawn(|_, mut cx| async move {
11363 let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
11364 let editor = workspace.update(&mut cx, |workspace, cx| {
11365 // Reset the preview item id before opening the new item
11366 workspace.active_pane().update(cx, |pane, cx| {
11367 pane.set_preview_item_id(None, cx);
11368 });
11369 workspace.open_path_preview(path, None, true, true, cx)
11370 })?;
11371 let editor = editor
11372 .await?
11373 .downcast::<Editor>()
11374 .ok_or_else(|| anyhow!("opened item was not an editor"))?
11375 .downgrade();
11376 editor.update(&mut cx, |editor, cx| {
11377 let buffer = editor
11378 .buffer()
11379 .read(cx)
11380 .as_singleton()
11381 .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
11382 let buffer = buffer.read(cx);
11383 let cursor = if buffer.can_resolve(&anchor) {
11384 language::ToPoint::to_point(&anchor, buffer)
11385 } else {
11386 buffer.clip_point(position, Bias::Left)
11387 };
11388
11389 let nav_history = editor.nav_history.take();
11390 editor.change_selections(
11391 Some(Autoscroll::top_relative(offset_from_top as usize)),
11392 cx,
11393 |s| {
11394 s.select_ranges([cursor..cursor]);
11395 },
11396 );
11397 editor.nav_history = nav_history;
11398
11399 anyhow::Ok(())
11400 })??;
11401
11402 anyhow::Ok(())
11403 })
11404 .detach_and_log_err(cx);
11405 }
11406
11407 fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
11408 let snapshot = self.buffer.read(cx).read(cx);
11409 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
11410 Some(
11411 ranges
11412 .iter()
11413 .map(move |range| {
11414 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
11415 })
11416 .collect(),
11417 )
11418 }
11419
11420 fn selection_replacement_ranges(
11421 &self,
11422 range: Range<OffsetUtf16>,
11423 cx: &AppContext,
11424 ) -> Vec<Range<OffsetUtf16>> {
11425 let selections = self.selections.all::<OffsetUtf16>(cx);
11426 let newest_selection = selections
11427 .iter()
11428 .max_by_key(|selection| selection.id)
11429 .unwrap();
11430 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
11431 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
11432 let snapshot = self.buffer.read(cx).read(cx);
11433 selections
11434 .into_iter()
11435 .map(|mut selection| {
11436 selection.start.0 =
11437 (selection.start.0 as isize).saturating_add(start_delta) as usize;
11438 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
11439 snapshot.clip_offset_utf16(selection.start, Bias::Left)
11440 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
11441 })
11442 .collect()
11443 }
11444
11445 fn report_editor_event(
11446 &self,
11447 operation: &'static str,
11448 file_extension: Option<String>,
11449 cx: &AppContext,
11450 ) {
11451 if cfg!(any(test, feature = "test-support")) {
11452 return;
11453 }
11454
11455 let Some(project) = &self.project else { return };
11456
11457 // If None, we are in a file without an extension
11458 let file = self
11459 .buffer
11460 .read(cx)
11461 .as_singleton()
11462 .and_then(|b| b.read(cx).file());
11463 let file_extension = file_extension.or(file
11464 .as_ref()
11465 .and_then(|file| Path::new(file.file_name(cx)).extension())
11466 .and_then(|e| e.to_str())
11467 .map(|a| a.to_string()));
11468
11469 let vim_mode = cx
11470 .global::<SettingsStore>()
11471 .raw_user_settings()
11472 .get("vim_mode")
11473 == Some(&serde_json::Value::Bool(true));
11474
11475 let copilot_enabled = all_language_settings(file, cx).inline_completions.provider
11476 == language::language_settings::InlineCompletionProvider::Copilot;
11477 let copilot_enabled_for_language = self
11478 .buffer
11479 .read(cx)
11480 .settings_at(0, cx)
11481 .show_inline_completions;
11482
11483 let telemetry = project.read(cx).client().telemetry().clone();
11484 telemetry.report_editor_event(
11485 file_extension,
11486 vim_mode,
11487 operation,
11488 copilot_enabled,
11489 copilot_enabled_for_language,
11490 )
11491 }
11492
11493 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
11494 /// with each line being an array of {text, highlight} objects.
11495 fn copy_highlight_json(&mut self, _: &CopyHighlightJson, cx: &mut ViewContext<Self>) {
11496 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
11497 return;
11498 };
11499
11500 #[derive(Serialize)]
11501 struct Chunk<'a> {
11502 text: String,
11503 highlight: Option<&'a str>,
11504 }
11505
11506 let snapshot = buffer.read(cx).snapshot();
11507 let range = self
11508 .selected_text_range(cx)
11509 .and_then(|selected_range| {
11510 if selected_range.is_empty() {
11511 None
11512 } else {
11513 Some(selected_range)
11514 }
11515 })
11516 .unwrap_or_else(|| 0..snapshot.len());
11517
11518 let chunks = snapshot.chunks(range, true);
11519 let mut lines = Vec::new();
11520 let mut line: VecDeque<Chunk> = VecDeque::new();
11521
11522 let Some(style) = self.style.as_ref() else {
11523 return;
11524 };
11525
11526 for chunk in chunks {
11527 let highlight = chunk
11528 .syntax_highlight_id
11529 .and_then(|id| id.name(&style.syntax));
11530 let mut chunk_lines = chunk.text.split('\n').peekable();
11531 while let Some(text) = chunk_lines.next() {
11532 let mut merged_with_last_token = false;
11533 if let Some(last_token) = line.back_mut() {
11534 if last_token.highlight == highlight {
11535 last_token.text.push_str(text);
11536 merged_with_last_token = true;
11537 }
11538 }
11539
11540 if !merged_with_last_token {
11541 line.push_back(Chunk {
11542 text: text.into(),
11543 highlight,
11544 });
11545 }
11546
11547 if chunk_lines.peek().is_some() {
11548 if line.len() > 1 && line.front().unwrap().text.is_empty() {
11549 line.pop_front();
11550 }
11551 if line.len() > 1 && line.back().unwrap().text.is_empty() {
11552 line.pop_back();
11553 }
11554
11555 lines.push(mem::take(&mut line));
11556 }
11557 }
11558 }
11559
11560 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
11561 return;
11562 };
11563 cx.write_to_clipboard(ClipboardItem::new(lines));
11564 }
11565
11566 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
11567 &self.inlay_hint_cache
11568 }
11569
11570 pub fn replay_insert_event(
11571 &mut self,
11572 text: &str,
11573 relative_utf16_range: Option<Range<isize>>,
11574 cx: &mut ViewContext<Self>,
11575 ) {
11576 if !self.input_enabled {
11577 cx.emit(EditorEvent::InputIgnored { text: text.into() });
11578 return;
11579 }
11580 if let Some(relative_utf16_range) = relative_utf16_range {
11581 let selections = self.selections.all::<OffsetUtf16>(cx);
11582 self.change_selections(None, cx, |s| {
11583 let new_ranges = selections.into_iter().map(|range| {
11584 let start = OffsetUtf16(
11585 range
11586 .head()
11587 .0
11588 .saturating_add_signed(relative_utf16_range.start),
11589 );
11590 let end = OffsetUtf16(
11591 range
11592 .head()
11593 .0
11594 .saturating_add_signed(relative_utf16_range.end),
11595 );
11596 start..end
11597 });
11598 s.select_ranges(new_ranges);
11599 });
11600 }
11601
11602 self.handle_input(text, cx);
11603 }
11604
11605 pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
11606 let Some(project) = self.project.as_ref() else {
11607 return false;
11608 };
11609 let project = project.read(cx);
11610
11611 let mut supports = false;
11612 self.buffer().read(cx).for_each_buffer(|buffer| {
11613 if !supports {
11614 supports = project
11615 .language_servers_for_buffer(buffer.read(cx), cx)
11616 .any(
11617 |(_, server)| match server.capabilities().inlay_hint_provider {
11618 Some(lsp::OneOf::Left(enabled)) => enabled,
11619 Some(lsp::OneOf::Right(_)) => true,
11620 None => false,
11621 },
11622 )
11623 }
11624 });
11625 supports
11626 }
11627
11628 pub fn focus(&self, cx: &mut WindowContext) {
11629 cx.focus(&self.focus_handle)
11630 }
11631
11632 pub fn is_focused(&self, cx: &WindowContext) -> bool {
11633 self.focus_handle.is_focused(cx)
11634 }
11635
11636 fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
11637 cx.emit(EditorEvent::Focused);
11638
11639 if let Some(descendant) = self
11640 .last_focused_descendant
11641 .take()
11642 .and_then(|descendant| descendant.upgrade())
11643 {
11644 cx.focus(&descendant);
11645 } else {
11646 if let Some(blame) = self.blame.as_ref() {
11647 blame.update(cx, GitBlame::focus)
11648 }
11649
11650 self.blink_manager.update(cx, BlinkManager::enable);
11651 self.show_cursor_names(cx);
11652 self.buffer.update(cx, |buffer, cx| {
11653 buffer.finalize_last_transaction(cx);
11654 if self.leader_peer_id.is_none() {
11655 buffer.set_active_selections(
11656 &self.selections.disjoint_anchors(),
11657 self.selections.line_mode,
11658 self.cursor_shape,
11659 cx,
11660 );
11661 }
11662 });
11663 }
11664 }
11665
11666 fn handle_focus_in(&mut self, cx: &mut ViewContext<Self>) {
11667 cx.emit(EditorEvent::FocusedIn)
11668 }
11669
11670 fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
11671 if event.blurred != self.focus_handle {
11672 self.last_focused_descendant = Some(event.blurred);
11673 }
11674 }
11675
11676 pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
11677 self.blink_manager.update(cx, BlinkManager::disable);
11678 self.buffer
11679 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
11680
11681 if let Some(blame) = self.blame.as_ref() {
11682 blame.update(cx, GitBlame::blur)
11683 }
11684 if !self.hover_state.focused(cx) {
11685 hide_hover(self, cx);
11686 }
11687
11688 self.hide_context_menu(cx);
11689 cx.emit(EditorEvent::Blurred);
11690 cx.notify();
11691 }
11692
11693 pub fn register_action<A: Action>(
11694 &mut self,
11695 listener: impl Fn(&A, &mut WindowContext) + 'static,
11696 ) -> Subscription {
11697 let id = self.next_editor_action_id.post_inc();
11698 let listener = Arc::new(listener);
11699 self.editor_actions.borrow_mut().insert(
11700 id,
11701 Box::new(move |cx| {
11702 let _view = cx.view().clone();
11703 let cx = cx.window_context();
11704 let listener = listener.clone();
11705 cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
11706 let action = action.downcast_ref().unwrap();
11707 if phase == DispatchPhase::Bubble {
11708 listener(action, cx)
11709 }
11710 })
11711 }),
11712 );
11713
11714 let editor_actions = self.editor_actions.clone();
11715 Subscription::new(move || {
11716 editor_actions.borrow_mut().remove(&id);
11717 })
11718 }
11719
11720 pub fn file_header_size(&self) -> u8 {
11721 self.file_header_size
11722 }
11723}
11724
11725fn hunks_for_selections(
11726 multi_buffer_snapshot: &MultiBufferSnapshot,
11727 selections: &[Selection<Anchor>],
11728) -> Vec<DiffHunk<MultiBufferRow>> {
11729 let mut hunks = Vec::with_capacity(selections.len());
11730 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
11731 HashMap::default();
11732 let buffer_rows_for_selections = selections.iter().map(|selection| {
11733 let head = selection.head();
11734 let tail = selection.tail();
11735 let start = MultiBufferRow(tail.to_point(&multi_buffer_snapshot).row);
11736 let end = MultiBufferRow(head.to_point(&multi_buffer_snapshot).row);
11737 if start > end {
11738 end..start
11739 } else {
11740 start..end
11741 }
11742 });
11743
11744 for selected_multi_buffer_rows in buffer_rows_for_selections {
11745 let query_rows =
11746 selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
11747 for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
11748 // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
11749 // when the caret is just above or just below the deleted hunk.
11750 let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
11751 let related_to_selection = if allow_adjacent {
11752 hunk.associated_range.overlaps(&query_rows)
11753 || hunk.associated_range.start == query_rows.end
11754 || hunk.associated_range.end == query_rows.start
11755 } else {
11756 // `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
11757 // `hunk.associated_range` is exclusive (e.g. [2..3] means 2nd row is selected)
11758 hunk.associated_range.overlaps(&selected_multi_buffer_rows)
11759 || selected_multi_buffer_rows.end == hunk.associated_range.start
11760 };
11761 if related_to_selection {
11762 if !processed_buffer_rows
11763 .entry(hunk.buffer_id)
11764 .or_default()
11765 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
11766 {
11767 continue;
11768 }
11769 hunks.push(hunk);
11770 }
11771 }
11772 }
11773
11774 hunks
11775}
11776
11777pub trait CollaborationHub {
11778 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
11779 fn user_participant_indices<'a>(
11780 &self,
11781 cx: &'a AppContext,
11782 ) -> &'a HashMap<u64, ParticipantIndex>;
11783 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
11784}
11785
11786impl CollaborationHub for Model<Project> {
11787 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
11788 self.read(cx).collaborators()
11789 }
11790
11791 fn user_participant_indices<'a>(
11792 &self,
11793 cx: &'a AppContext,
11794 ) -> &'a HashMap<u64, ParticipantIndex> {
11795 self.read(cx).user_store().read(cx).participant_indices()
11796 }
11797
11798 fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
11799 let this = self.read(cx);
11800 let user_ids = this.collaborators().values().map(|c| c.user_id);
11801 this.user_store().read_with(cx, |user_store, cx| {
11802 user_store.participant_names(user_ids, cx)
11803 })
11804 }
11805}
11806
11807pub trait CompletionProvider {
11808 fn completions(
11809 &self,
11810 buffer: &Model<Buffer>,
11811 buffer_position: text::Anchor,
11812 trigger: CompletionContext,
11813 cx: &mut ViewContext<Editor>,
11814 ) -> Task<Result<Vec<Completion>>>;
11815
11816 fn resolve_completions(
11817 &self,
11818 buffer: Model<Buffer>,
11819 completion_indices: Vec<usize>,
11820 completions: Arc<RwLock<Box<[Completion]>>>,
11821 cx: &mut ViewContext<Editor>,
11822 ) -> Task<Result<bool>>;
11823
11824 fn apply_additional_edits_for_completion(
11825 &self,
11826 buffer: Model<Buffer>,
11827 completion: Completion,
11828 push_to_history: bool,
11829 cx: &mut ViewContext<Editor>,
11830 ) -> Task<Result<Option<language::Transaction>>>;
11831
11832 fn is_completion_trigger(
11833 &self,
11834 buffer: &Model<Buffer>,
11835 position: language::Anchor,
11836 text: &str,
11837 trigger_in_words: bool,
11838 cx: &mut ViewContext<Editor>,
11839 ) -> bool;
11840}
11841
11842fn snippet_completions(
11843 project: &Project,
11844 buffer: &Model<Buffer>,
11845 buffer_position: text::Anchor,
11846 cx: &mut AppContext,
11847) -> Vec<Completion> {
11848 let language = buffer.read(cx).language_at(buffer_position);
11849 let language_name = language.as_ref().map(|language| language.lsp_id());
11850 let snippet_store = project.snippets().read(cx);
11851 let snippets = snippet_store.snippets_for(language_name, cx);
11852
11853 if snippets.is_empty() {
11854 return vec![];
11855 }
11856 let snapshot = buffer.read(cx).text_snapshot();
11857 let chunks = snapshot.reversed_chunks_in_range(text::Anchor::MIN..buffer_position);
11858
11859 let mut lines = chunks.lines();
11860 let Some(line_at) = lines.next().filter(|line| !line.is_empty()) else {
11861 return vec![];
11862 };
11863
11864 let scope = language.map(|language| language.default_scope());
11865 let mut last_word = line_at
11866 .chars()
11867 .rev()
11868 .take_while(|c| char_kind(&scope, *c) == CharKind::Word)
11869 .collect::<String>();
11870 last_word = last_word.chars().rev().collect();
11871 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
11872 let to_lsp = |point: &text::Anchor| {
11873 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
11874 point_to_lsp(end)
11875 };
11876 let lsp_end = to_lsp(&buffer_position);
11877 snippets
11878 .into_iter()
11879 .filter_map(|snippet| {
11880 let matching_prefix = snippet
11881 .prefix
11882 .iter()
11883 .find(|prefix| prefix.starts_with(&last_word))?;
11884 let start = as_offset - last_word.len();
11885 let start = snapshot.anchor_before(start);
11886 let range = start..buffer_position;
11887 let lsp_start = to_lsp(&start);
11888 let lsp_range = lsp::Range {
11889 start: lsp_start,
11890 end: lsp_end,
11891 };
11892 Some(Completion {
11893 old_range: range,
11894 new_text: snippet.body.clone(),
11895 label: CodeLabel {
11896 text: matching_prefix.clone(),
11897 runs: vec![],
11898 filter_range: 0..matching_prefix.len(),
11899 },
11900 server_id: LanguageServerId(usize::MAX),
11901 documentation: snippet
11902 .description
11903 .clone()
11904 .map(|description| Documentation::SingleLine(description)),
11905 lsp_completion: lsp::CompletionItem {
11906 label: snippet.prefix.first().unwrap().clone(),
11907 kind: Some(CompletionItemKind::SNIPPET),
11908 label_details: snippet.description.as_ref().map(|description| {
11909 lsp::CompletionItemLabelDetails {
11910 detail: Some(description.clone()),
11911 description: None,
11912 }
11913 }),
11914 insert_text_format: Some(InsertTextFormat::SNIPPET),
11915 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11916 lsp::InsertReplaceEdit {
11917 new_text: snippet.body.clone(),
11918 insert: lsp_range,
11919 replace: lsp_range,
11920 },
11921 )),
11922 filter_text: Some(snippet.body.clone()),
11923 sort_text: Some(char::MAX.to_string()),
11924 ..Default::default()
11925 },
11926 confirm: None,
11927 show_new_completions_on_confirm: false,
11928 })
11929 })
11930 .collect()
11931}
11932
11933impl CompletionProvider for Model<Project> {
11934 fn completions(
11935 &self,
11936 buffer: &Model<Buffer>,
11937 buffer_position: text::Anchor,
11938 options: CompletionContext,
11939 cx: &mut ViewContext<Editor>,
11940 ) -> Task<Result<Vec<Completion>>> {
11941 self.update(cx, |project, cx| {
11942 let snippets = snippet_completions(project, buffer, buffer_position, cx);
11943 let project_completions = project.completions(&buffer, buffer_position, options, cx);
11944 cx.background_executor().spawn(async move {
11945 let mut completions = project_completions.await?;
11946 //let snippets = snippets.into_iter().;
11947 completions.extend(snippets);
11948 Ok(completions)
11949 })
11950 })
11951 }
11952
11953 fn resolve_completions(
11954 &self,
11955 buffer: Model<Buffer>,
11956 completion_indices: Vec<usize>,
11957 completions: Arc<RwLock<Box<[Completion]>>>,
11958 cx: &mut ViewContext<Editor>,
11959 ) -> Task<Result<bool>> {
11960 self.update(cx, |project, cx| {
11961 project.resolve_completions(buffer, completion_indices, completions, cx)
11962 })
11963 }
11964
11965 fn apply_additional_edits_for_completion(
11966 &self,
11967 buffer: Model<Buffer>,
11968 completion: Completion,
11969 push_to_history: bool,
11970 cx: &mut ViewContext<Editor>,
11971 ) -> Task<Result<Option<language::Transaction>>> {
11972 self.update(cx, |project, cx| {
11973 project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
11974 })
11975 }
11976
11977 fn is_completion_trigger(
11978 &self,
11979 buffer: &Model<Buffer>,
11980 position: language::Anchor,
11981 text: &str,
11982 trigger_in_words: bool,
11983 cx: &mut ViewContext<Editor>,
11984 ) -> bool {
11985 if !EditorSettings::get_global(cx).show_completions_on_input {
11986 return false;
11987 }
11988
11989 let mut chars = text.chars();
11990 let char = if let Some(char) = chars.next() {
11991 char
11992 } else {
11993 return false;
11994 };
11995 if chars.next().is_some() {
11996 return false;
11997 }
11998
11999 let buffer = buffer.read(cx);
12000 let scope = buffer.snapshot().language_scope_at(position);
12001 if trigger_in_words && char_kind(&scope, char) == CharKind::Word {
12002 return true;
12003 }
12004
12005 buffer
12006 .completion_triggers()
12007 .iter()
12008 .any(|string| string == text)
12009 }
12010}
12011
12012fn inlay_hint_settings(
12013 location: Anchor,
12014 snapshot: &MultiBufferSnapshot,
12015 cx: &mut ViewContext<'_, Editor>,
12016) -> InlayHintSettings {
12017 let file = snapshot.file_at(location);
12018 let language = snapshot.language_at(location);
12019 let settings = all_language_settings(file, cx);
12020 settings
12021 .language(language.map(|l| l.name()).as_deref())
12022 .inlay_hints
12023}
12024
12025fn consume_contiguous_rows(
12026 contiguous_row_selections: &mut Vec<Selection<Point>>,
12027 selection: &Selection<Point>,
12028 display_map: &DisplaySnapshot,
12029 selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
12030) -> (MultiBufferRow, MultiBufferRow) {
12031 contiguous_row_selections.push(selection.clone());
12032 let start_row = MultiBufferRow(selection.start.row);
12033 let mut end_row = ending_row(selection, display_map);
12034
12035 while let Some(next_selection) = selections.peek() {
12036 if next_selection.start.row <= end_row.0 {
12037 end_row = ending_row(next_selection, display_map);
12038 contiguous_row_selections.push(selections.next().unwrap().clone());
12039 } else {
12040 break;
12041 }
12042 }
12043 (start_row, end_row)
12044}
12045
12046fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
12047 if next_selection.end.column > 0 || next_selection.is_empty() {
12048 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
12049 } else {
12050 MultiBufferRow(next_selection.end.row)
12051 }
12052}
12053
12054impl EditorSnapshot {
12055 pub fn remote_selections_in_range<'a>(
12056 &'a self,
12057 range: &'a Range<Anchor>,
12058 collaboration_hub: &dyn CollaborationHub,
12059 cx: &'a AppContext,
12060 ) -> impl 'a + Iterator<Item = RemoteSelection> {
12061 let participant_names = collaboration_hub.user_names(cx);
12062 let participant_indices = collaboration_hub.user_participant_indices(cx);
12063 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
12064 let collaborators_by_replica_id = collaborators_by_peer_id
12065 .iter()
12066 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
12067 .collect::<HashMap<_, _>>();
12068 self.buffer_snapshot
12069 .selections_in_range(range, false)
12070 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
12071 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
12072 let participant_index = participant_indices.get(&collaborator.user_id).copied();
12073 let user_name = participant_names.get(&collaborator.user_id).cloned();
12074 Some(RemoteSelection {
12075 replica_id,
12076 selection,
12077 cursor_shape,
12078 line_mode,
12079 participant_index,
12080 peer_id: collaborator.peer_id,
12081 user_name,
12082 })
12083 })
12084 }
12085
12086 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
12087 self.display_snapshot.buffer_snapshot.language_at(position)
12088 }
12089
12090 pub fn is_focused(&self) -> bool {
12091 self.is_focused
12092 }
12093
12094 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
12095 self.placeholder_text.as_ref()
12096 }
12097
12098 pub fn scroll_position(&self) -> gpui::Point<f32> {
12099 self.scroll_anchor.scroll_position(&self.display_snapshot)
12100 }
12101
12102 pub fn gutter_dimensions(
12103 &self,
12104 font_id: FontId,
12105 font_size: Pixels,
12106 em_width: Pixels,
12107 max_line_number_width: Pixels,
12108 cx: &AppContext,
12109 ) -> GutterDimensions {
12110 if !self.show_gutter {
12111 return GutterDimensions::default();
12112 }
12113 let descent = cx.text_system().descent(font_id, font_size);
12114
12115 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
12116 matches!(
12117 ProjectSettings::get_global(cx).git.git_gutter,
12118 Some(GitGutterSetting::TrackedFiles)
12119 )
12120 });
12121 let gutter_settings = EditorSettings::get_global(cx).gutter;
12122 let show_line_numbers = self
12123 .show_line_numbers
12124 .unwrap_or(gutter_settings.line_numbers);
12125 let line_gutter_width = if show_line_numbers {
12126 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
12127 let min_width_for_number_on_gutter = em_width * 4.0;
12128 max_line_number_width.max(min_width_for_number_on_gutter)
12129 } else {
12130 0.0.into()
12131 };
12132
12133 let show_code_actions = self
12134 .show_code_actions
12135 .unwrap_or(gutter_settings.code_actions);
12136
12137 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
12138
12139 let git_blame_entries_width = self
12140 .render_git_blame_gutter
12141 .then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
12142
12143 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
12144 left_padding += if show_code_actions || show_runnables {
12145 em_width * 3.0
12146 } else if show_git_gutter && show_line_numbers {
12147 em_width * 2.0
12148 } else if show_git_gutter || show_line_numbers {
12149 em_width
12150 } else {
12151 px(0.)
12152 };
12153
12154 let right_padding = if gutter_settings.folds && show_line_numbers {
12155 em_width * 4.0
12156 } else if gutter_settings.folds {
12157 em_width * 3.0
12158 } else if show_line_numbers {
12159 em_width
12160 } else {
12161 px(0.)
12162 };
12163
12164 GutterDimensions {
12165 left_padding,
12166 right_padding,
12167 width: line_gutter_width + left_padding + right_padding,
12168 margin: -descent,
12169 git_blame_entries_width,
12170 }
12171 }
12172
12173 pub fn render_fold_toggle(
12174 &self,
12175 buffer_row: MultiBufferRow,
12176 row_contains_cursor: bool,
12177 editor: View<Editor>,
12178 cx: &mut WindowContext,
12179 ) -> Option<AnyElement> {
12180 let folded = self.is_line_folded(buffer_row);
12181
12182 if let Some(crease) = self
12183 .crease_snapshot
12184 .query_row(buffer_row, &self.buffer_snapshot)
12185 {
12186 let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
12187 if folded {
12188 editor.update(cx, |editor, cx| {
12189 editor.fold_at(&crate::FoldAt { buffer_row }, cx)
12190 });
12191 } else {
12192 editor.update(cx, |editor, cx| {
12193 editor.unfold_at(&crate::UnfoldAt { buffer_row }, cx)
12194 });
12195 }
12196 });
12197
12198 Some((crease.render_toggle)(
12199 buffer_row,
12200 folded,
12201 toggle_callback,
12202 cx,
12203 ))
12204 } else if folded
12205 || (self.starts_indent(buffer_row) && (row_contains_cursor || self.gutter_hovered))
12206 {
12207 Some(
12208 Disclosure::new(("indent-fold-indicator", buffer_row.0), !folded)
12209 .selected(folded)
12210 .on_click(cx.listener_for(&editor, move |this, _e, cx| {
12211 if folded {
12212 this.unfold_at(&UnfoldAt { buffer_row }, cx);
12213 } else {
12214 this.fold_at(&FoldAt { buffer_row }, cx);
12215 }
12216 }))
12217 .into_any_element(),
12218 )
12219 } else {
12220 None
12221 }
12222 }
12223
12224 pub fn render_crease_trailer(
12225 &self,
12226 buffer_row: MultiBufferRow,
12227 cx: &mut WindowContext,
12228 ) -> Option<AnyElement> {
12229 let folded = self.is_line_folded(buffer_row);
12230 let crease = self
12231 .crease_snapshot
12232 .query_row(buffer_row, &self.buffer_snapshot)?;
12233 Some((crease.render_trailer)(buffer_row, folded, cx))
12234 }
12235}
12236
12237impl Deref for EditorSnapshot {
12238 type Target = DisplaySnapshot;
12239
12240 fn deref(&self) -> &Self::Target {
12241 &self.display_snapshot
12242 }
12243}
12244
12245#[derive(Clone, Debug, PartialEq, Eq)]
12246pub enum EditorEvent {
12247 InputIgnored {
12248 text: Arc<str>,
12249 },
12250 InputHandled {
12251 utf16_range_to_replace: Option<Range<isize>>,
12252 text: Arc<str>,
12253 },
12254 ExcerptsAdded {
12255 buffer: Model<Buffer>,
12256 predecessor: ExcerptId,
12257 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
12258 },
12259 ExcerptsRemoved {
12260 ids: Vec<ExcerptId>,
12261 },
12262 ExcerptsEdited {
12263 ids: Vec<ExcerptId>,
12264 },
12265 ExcerptsExpanded {
12266 ids: Vec<ExcerptId>,
12267 },
12268 BufferEdited,
12269 Edited {
12270 transaction_id: clock::Lamport,
12271 },
12272 Reparsed(BufferId),
12273 Focused,
12274 FocusedIn,
12275 Blurred,
12276 DirtyChanged,
12277 Saved,
12278 TitleChanged,
12279 DiffBaseChanged,
12280 SelectionsChanged {
12281 local: bool,
12282 },
12283 ScrollPositionChanged {
12284 local: bool,
12285 autoscroll: bool,
12286 },
12287 Closed,
12288 TransactionUndone {
12289 transaction_id: clock::Lamport,
12290 },
12291 TransactionBegun {
12292 transaction_id: clock::Lamport,
12293 },
12294}
12295
12296impl EventEmitter<EditorEvent> for Editor {}
12297
12298impl FocusableView for Editor {
12299 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
12300 self.focus_handle.clone()
12301 }
12302}
12303
12304impl Render for Editor {
12305 fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
12306 let settings = ThemeSettings::get_global(cx);
12307
12308 let text_style = match self.mode {
12309 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
12310 color: cx.theme().colors().editor_foreground,
12311 font_family: settings.ui_font.family.clone(),
12312 font_features: settings.ui_font.features.clone(),
12313 font_size: rems(0.875).into(),
12314 font_weight: settings.ui_font.weight,
12315 font_style: FontStyle::Normal,
12316 line_height: relative(settings.buffer_line_height.value()),
12317 background_color: None,
12318 underline: None,
12319 strikethrough: None,
12320 white_space: WhiteSpace::Normal,
12321 },
12322 EditorMode::Full => TextStyle {
12323 color: cx.theme().colors().editor_foreground,
12324 font_family: settings.buffer_font.family.clone(),
12325 font_features: settings.buffer_font.features.clone(),
12326 font_size: settings.buffer_font_size(cx).into(),
12327 font_weight: settings.buffer_font.weight,
12328 font_style: FontStyle::Normal,
12329 line_height: relative(settings.buffer_line_height.value()),
12330 background_color: None,
12331 underline: None,
12332 strikethrough: None,
12333 white_space: WhiteSpace::Normal,
12334 },
12335 };
12336
12337 let background = match self.mode {
12338 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
12339 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
12340 EditorMode::Full => cx.theme().colors().editor_background,
12341 };
12342
12343 EditorElement::new(
12344 cx.view(),
12345 EditorStyle {
12346 background,
12347 local_player: cx.theme().players().local(),
12348 text: text_style,
12349 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
12350 syntax: cx.theme().syntax().clone(),
12351 status: cx.theme().status().clone(),
12352 inlay_hints_style: HighlightStyle {
12353 color: Some(cx.theme().status().hint),
12354 ..HighlightStyle::default()
12355 },
12356 suggestions_style: HighlightStyle {
12357 color: Some(cx.theme().status().predictive),
12358 ..HighlightStyle::default()
12359 },
12360 },
12361 )
12362 }
12363}
12364
12365impl ViewInputHandler for Editor {
12366 fn text_for_range(
12367 &mut self,
12368 range_utf16: Range<usize>,
12369 cx: &mut ViewContext<Self>,
12370 ) -> Option<String> {
12371 Some(
12372 self.buffer
12373 .read(cx)
12374 .read(cx)
12375 .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
12376 .collect(),
12377 )
12378 }
12379
12380 fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
12381 // Prevent the IME menu from appearing when holding down an alphabetic key
12382 // while input is disabled.
12383 if !self.input_enabled {
12384 return None;
12385 }
12386
12387 let range = self.selections.newest::<OffsetUtf16>(cx).range();
12388 Some(range.start.0..range.end.0)
12389 }
12390
12391 fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
12392 let snapshot = self.buffer.read(cx).read(cx);
12393 let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
12394 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
12395 }
12396
12397 fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
12398 self.clear_highlights::<InputComposition>(cx);
12399 self.ime_transaction.take();
12400 }
12401
12402 fn replace_text_in_range(
12403 &mut self,
12404 range_utf16: Option<Range<usize>>,
12405 text: &str,
12406 cx: &mut ViewContext<Self>,
12407 ) {
12408 if !self.input_enabled {
12409 cx.emit(EditorEvent::InputIgnored { text: text.into() });
12410 return;
12411 }
12412
12413 self.transact(cx, |this, cx| {
12414 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
12415 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
12416 Some(this.selection_replacement_ranges(range_utf16, cx))
12417 } else {
12418 this.marked_text_ranges(cx)
12419 };
12420
12421 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
12422 let newest_selection_id = this.selections.newest_anchor().id;
12423 this.selections
12424 .all::<OffsetUtf16>(cx)
12425 .iter()
12426 .zip(ranges_to_replace.iter())
12427 .find_map(|(selection, range)| {
12428 if selection.id == newest_selection_id {
12429 Some(
12430 (range.start.0 as isize - selection.head().0 as isize)
12431 ..(range.end.0 as isize - selection.head().0 as isize),
12432 )
12433 } else {
12434 None
12435 }
12436 })
12437 });
12438
12439 cx.emit(EditorEvent::InputHandled {
12440 utf16_range_to_replace: range_to_replace,
12441 text: text.into(),
12442 });
12443
12444 if let Some(new_selected_ranges) = new_selected_ranges {
12445 this.change_selections(None, cx, |selections| {
12446 selections.select_ranges(new_selected_ranges)
12447 });
12448 this.backspace(&Default::default(), cx);
12449 }
12450
12451 this.handle_input(text, cx);
12452 });
12453
12454 if let Some(transaction) = self.ime_transaction {
12455 self.buffer.update(cx, |buffer, cx| {
12456 buffer.group_until_transaction(transaction, cx);
12457 });
12458 }
12459
12460 self.unmark_text(cx);
12461 }
12462
12463 fn replace_and_mark_text_in_range(
12464 &mut self,
12465 range_utf16: Option<Range<usize>>,
12466 text: &str,
12467 new_selected_range_utf16: Option<Range<usize>>,
12468 cx: &mut ViewContext<Self>,
12469 ) {
12470 if !self.input_enabled {
12471 return;
12472 }
12473
12474 let transaction = self.transact(cx, |this, cx| {
12475 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
12476 let snapshot = this.buffer.read(cx).read(cx);
12477 if let Some(relative_range_utf16) = range_utf16.as_ref() {
12478 for marked_range in &mut marked_ranges {
12479 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
12480 marked_range.start.0 += relative_range_utf16.start;
12481 marked_range.start =
12482 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
12483 marked_range.end =
12484 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
12485 }
12486 }
12487 Some(marked_ranges)
12488 } else if let Some(range_utf16) = range_utf16 {
12489 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
12490 Some(this.selection_replacement_ranges(range_utf16, cx))
12491 } else {
12492 None
12493 };
12494
12495 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
12496 let newest_selection_id = this.selections.newest_anchor().id;
12497 this.selections
12498 .all::<OffsetUtf16>(cx)
12499 .iter()
12500 .zip(ranges_to_replace.iter())
12501 .find_map(|(selection, range)| {
12502 if selection.id == newest_selection_id {
12503 Some(
12504 (range.start.0 as isize - selection.head().0 as isize)
12505 ..(range.end.0 as isize - selection.head().0 as isize),
12506 )
12507 } else {
12508 None
12509 }
12510 })
12511 });
12512
12513 cx.emit(EditorEvent::InputHandled {
12514 utf16_range_to_replace: range_to_replace,
12515 text: text.into(),
12516 });
12517
12518 if let Some(ranges) = ranges_to_replace {
12519 this.change_selections(None, cx, |s| s.select_ranges(ranges));
12520 }
12521
12522 let marked_ranges = {
12523 let snapshot = this.buffer.read(cx).read(cx);
12524 this.selections
12525 .disjoint_anchors()
12526 .iter()
12527 .map(|selection| {
12528 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
12529 })
12530 .collect::<Vec<_>>()
12531 };
12532
12533 if text.is_empty() {
12534 this.unmark_text(cx);
12535 } else {
12536 this.highlight_text::<InputComposition>(
12537 marked_ranges.clone(),
12538 HighlightStyle {
12539 underline: Some(UnderlineStyle {
12540 thickness: px(1.),
12541 color: None,
12542 wavy: false,
12543 }),
12544 ..Default::default()
12545 },
12546 cx,
12547 );
12548 }
12549
12550 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
12551 let use_autoclose = this.use_autoclose;
12552 let use_auto_surround = this.use_auto_surround;
12553 this.set_use_autoclose(false);
12554 this.set_use_auto_surround(false);
12555 this.handle_input(text, cx);
12556 this.set_use_autoclose(use_autoclose);
12557 this.set_use_auto_surround(use_auto_surround);
12558
12559 if let Some(new_selected_range) = new_selected_range_utf16 {
12560 let snapshot = this.buffer.read(cx).read(cx);
12561 let new_selected_ranges = marked_ranges
12562 .into_iter()
12563 .map(|marked_range| {
12564 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
12565 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
12566 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
12567 snapshot.clip_offset_utf16(new_start, Bias::Left)
12568 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
12569 })
12570 .collect::<Vec<_>>();
12571
12572 drop(snapshot);
12573 this.change_selections(None, cx, |selections| {
12574 selections.select_ranges(new_selected_ranges)
12575 });
12576 }
12577 });
12578
12579 self.ime_transaction = self.ime_transaction.or(transaction);
12580 if let Some(transaction) = self.ime_transaction {
12581 self.buffer.update(cx, |buffer, cx| {
12582 buffer.group_until_transaction(transaction, cx);
12583 });
12584 }
12585
12586 if self.text_highlights::<InputComposition>(cx).is_none() {
12587 self.ime_transaction.take();
12588 }
12589 }
12590
12591 fn bounds_for_range(
12592 &mut self,
12593 range_utf16: Range<usize>,
12594 element_bounds: gpui::Bounds<Pixels>,
12595 cx: &mut ViewContext<Self>,
12596 ) -> Option<gpui::Bounds<Pixels>> {
12597 let text_layout_details = self.text_layout_details(cx);
12598 let style = &text_layout_details.editor_style;
12599 let font_id = cx.text_system().resolve_font(&style.text.font());
12600 let font_size = style.text.font_size.to_pixels(cx.rem_size());
12601 let line_height = style.text.line_height_in_pixels(cx.rem_size());
12602
12603 let em_width = cx
12604 .text_system()
12605 .typographic_bounds(font_id, font_size, 'm')
12606 .unwrap()
12607 .size
12608 .width;
12609
12610 let snapshot = self.snapshot(cx);
12611 let scroll_position = snapshot.scroll_position();
12612 let scroll_left = scroll_position.x * em_width;
12613
12614 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
12615 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
12616 + self.gutter_dimensions.width;
12617 let y = line_height * (start.row().as_f32() - scroll_position.y);
12618
12619 Some(Bounds {
12620 origin: element_bounds.origin + point(x, y),
12621 size: size(em_width, line_height),
12622 })
12623 }
12624}
12625
12626trait SelectionExt {
12627 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
12628 fn spanned_rows(
12629 &self,
12630 include_end_if_at_line_start: bool,
12631 map: &DisplaySnapshot,
12632 ) -> Range<MultiBufferRow>;
12633}
12634
12635impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
12636 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
12637 let start = self
12638 .start
12639 .to_point(&map.buffer_snapshot)
12640 .to_display_point(map);
12641 let end = self
12642 .end
12643 .to_point(&map.buffer_snapshot)
12644 .to_display_point(map);
12645 if self.reversed {
12646 end..start
12647 } else {
12648 start..end
12649 }
12650 }
12651
12652 fn spanned_rows(
12653 &self,
12654 include_end_if_at_line_start: bool,
12655 map: &DisplaySnapshot,
12656 ) -> Range<MultiBufferRow> {
12657 let start = self.start.to_point(&map.buffer_snapshot);
12658 let mut end = self.end.to_point(&map.buffer_snapshot);
12659 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
12660 end.row -= 1;
12661 }
12662
12663 let buffer_start = map.prev_line_boundary(start).0;
12664 let buffer_end = map.next_line_boundary(end).0;
12665 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
12666 }
12667}
12668
12669impl<T: InvalidationRegion> InvalidationStack<T> {
12670 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
12671 where
12672 S: Clone + ToOffset,
12673 {
12674 while let Some(region) = self.last() {
12675 let all_selections_inside_invalidation_ranges =
12676 if selections.len() == region.ranges().len() {
12677 selections
12678 .iter()
12679 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
12680 .all(|(selection, invalidation_range)| {
12681 let head = selection.head().to_offset(buffer);
12682 invalidation_range.start <= head && invalidation_range.end >= head
12683 })
12684 } else {
12685 false
12686 };
12687
12688 if all_selections_inside_invalidation_ranges {
12689 break;
12690 } else {
12691 self.pop();
12692 }
12693 }
12694 }
12695}
12696
12697impl<T> Default for InvalidationStack<T> {
12698 fn default() -> Self {
12699 Self(Default::default())
12700 }
12701}
12702
12703impl<T> Deref for InvalidationStack<T> {
12704 type Target = Vec<T>;
12705
12706 fn deref(&self) -> &Self::Target {
12707 &self.0
12708 }
12709}
12710
12711impl<T> DerefMut for InvalidationStack<T> {
12712 fn deref_mut(&mut self) -> &mut Self::Target {
12713 &mut self.0
12714 }
12715}
12716
12717impl InvalidationRegion for SnippetState {
12718 fn ranges(&self) -> &[Range<Anchor>] {
12719 &self.ranges[self.active_index]
12720 }
12721}
12722
12723pub fn diagnostic_block_renderer(
12724 diagnostic: Diagnostic,
12725 max_message_rows: Option<u8>,
12726 allow_closing: bool,
12727 _is_valid: bool,
12728) -> RenderBlock {
12729 let (text_without_backticks, code_ranges) =
12730 highlight_diagnostic_message(&diagnostic, max_message_rows);
12731
12732 Box::new(move |cx: &mut BlockContext| {
12733 let group_id: SharedString = cx.transform_block_id.to_string().into();
12734
12735 let mut text_style = cx.text_style().clone();
12736 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
12737 let theme_settings = ThemeSettings::get_global(cx);
12738 text_style.font_family = theme_settings.buffer_font.family.clone();
12739 text_style.font_style = theme_settings.buffer_font.style;
12740 text_style.font_features = theme_settings.buffer_font.features.clone();
12741 text_style.font_weight = theme_settings.buffer_font.weight;
12742
12743 let multi_line_diagnostic = diagnostic.message.contains('\n');
12744
12745 let buttons = |diagnostic: &Diagnostic, block_id: TransformBlockId| {
12746 if multi_line_diagnostic {
12747 v_flex()
12748 } else {
12749 h_flex()
12750 }
12751 .when(allow_closing, |div| {
12752 div.children(diagnostic.is_primary.then(|| {
12753 IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
12754 .icon_color(Color::Muted)
12755 .size(ButtonSize::Compact)
12756 .style(ButtonStyle::Transparent)
12757 .visible_on_hover(group_id.clone())
12758 .on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
12759 .tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
12760 }))
12761 })
12762 .child(
12763 IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
12764 .icon_color(Color::Muted)
12765 .size(ButtonSize::Compact)
12766 .style(ButtonStyle::Transparent)
12767 .visible_on_hover(group_id.clone())
12768 .on_click({
12769 let message = diagnostic.message.clone();
12770 move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
12771 })
12772 .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
12773 )
12774 };
12775
12776 let icon_size = buttons(&diagnostic, cx.transform_block_id)
12777 .into_any_element()
12778 .layout_as_root(AvailableSpace::min_size(), cx);
12779
12780 h_flex()
12781 .id(cx.transform_block_id)
12782 .group(group_id.clone())
12783 .relative()
12784 .size_full()
12785 .pl(cx.gutter_dimensions.width)
12786 .w(cx.max_width + cx.gutter_dimensions.width)
12787 .child(
12788 div()
12789 .flex()
12790 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
12791 .flex_shrink(),
12792 )
12793 .child(buttons(&diagnostic, cx.transform_block_id))
12794 .child(div().flex().flex_shrink_0().child(
12795 StyledText::new(text_without_backticks.clone()).with_highlights(
12796 &text_style,
12797 code_ranges.iter().map(|range| {
12798 (
12799 range.clone(),
12800 HighlightStyle {
12801 font_weight: Some(FontWeight::BOLD),
12802 ..Default::default()
12803 },
12804 )
12805 }),
12806 ),
12807 ))
12808 .into_any_element()
12809 })
12810}
12811
12812pub fn highlight_diagnostic_message(
12813 diagnostic: &Diagnostic,
12814 mut max_message_rows: Option<u8>,
12815) -> (SharedString, Vec<Range<usize>>) {
12816 let mut text_without_backticks = String::new();
12817 let mut code_ranges = Vec::new();
12818
12819 if let Some(source) = &diagnostic.source {
12820 text_without_backticks.push_str(&source);
12821 code_ranges.push(0..source.len());
12822 text_without_backticks.push_str(": ");
12823 }
12824
12825 let mut prev_offset = 0;
12826 let mut in_code_block = false;
12827 let mut newline_indices = diagnostic
12828 .message
12829 .match_indices('\n')
12830 .map(|(ix, _)| ix)
12831 .fuse()
12832 .peekable();
12833 for (ix, _) in diagnostic
12834 .message
12835 .match_indices('`')
12836 .chain([(diagnostic.message.len(), "")])
12837 {
12838 let mut trimmed_ix = ix;
12839 while let Some(newline_index) = newline_indices.peek() {
12840 if *newline_index < ix {
12841 if let Some(rows_left) = &mut max_message_rows {
12842 if *rows_left == 0 {
12843 trimmed_ix = newline_index.saturating_sub(1);
12844 break;
12845 } else {
12846 *rows_left -= 1;
12847 }
12848 }
12849 let _ = newline_indices.next();
12850 } else {
12851 break;
12852 }
12853 }
12854 let prev_len = text_without_backticks.len();
12855 let new_text = &diagnostic.message[prev_offset..trimmed_ix];
12856 text_without_backticks.push_str(new_text);
12857 if in_code_block {
12858 code_ranges.push(prev_len..text_without_backticks.len());
12859 }
12860 prev_offset = trimmed_ix + 1;
12861 in_code_block = !in_code_block;
12862 if trimmed_ix != ix {
12863 text_without_backticks.push_str("...");
12864 break;
12865 }
12866 }
12867
12868 (text_without_backticks.into(), code_ranges)
12869}
12870
12871fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
12872 match severity {
12873 DiagnosticSeverity::ERROR => colors.error,
12874 DiagnosticSeverity::WARNING => colors.warning,
12875 DiagnosticSeverity::INFORMATION => colors.info,
12876 DiagnosticSeverity::HINT => colors.info,
12877 _ => colors.ignored,
12878 }
12879}
12880
12881pub fn styled_runs_for_code_label<'a>(
12882 label: &'a CodeLabel,
12883 syntax_theme: &'a theme::SyntaxTheme,
12884) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
12885 let fade_out = HighlightStyle {
12886 fade_out: Some(0.35),
12887 ..Default::default()
12888 };
12889
12890 let mut prev_end = label.filter_range.end;
12891 label
12892 .runs
12893 .iter()
12894 .enumerate()
12895 .flat_map(move |(ix, (range, highlight_id))| {
12896 let style = if let Some(style) = highlight_id.style(syntax_theme) {
12897 style
12898 } else {
12899 return Default::default();
12900 };
12901 let mut muted_style = style;
12902 muted_style.highlight(fade_out);
12903
12904 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
12905 if range.start >= label.filter_range.end {
12906 if range.start > prev_end {
12907 runs.push((prev_end..range.start, fade_out));
12908 }
12909 runs.push((range.clone(), muted_style));
12910 } else if range.end <= label.filter_range.end {
12911 runs.push((range.clone(), style));
12912 } else {
12913 runs.push((range.start..label.filter_range.end, style));
12914 runs.push((label.filter_range.end..range.end, muted_style));
12915 }
12916 prev_end = cmp::max(prev_end, range.end);
12917
12918 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
12919 runs.push((prev_end..label.text.len(), fade_out));
12920 }
12921
12922 runs
12923 })
12924}
12925
12926pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
12927 let mut prev_index = 0;
12928 let mut prev_codepoint: Option<char> = None;
12929 text.char_indices()
12930 .chain([(text.len(), '\0')])
12931 .filter_map(move |(index, codepoint)| {
12932 let prev_codepoint = prev_codepoint.replace(codepoint)?;
12933 let is_boundary = index == text.len()
12934 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
12935 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
12936 if is_boundary {
12937 let chunk = &text[prev_index..index];
12938 prev_index = index;
12939 Some(chunk)
12940 } else {
12941 None
12942 }
12943 })
12944}
12945
12946pub trait RangeToAnchorExt {
12947 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
12948}
12949
12950impl<T: ToOffset> RangeToAnchorExt for Range<T> {
12951 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
12952 let start_offset = self.start.to_offset(snapshot);
12953 let end_offset = self.end.to_offset(snapshot);
12954 if start_offset == end_offset {
12955 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
12956 } else {
12957 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
12958 }
12959 }
12960}
12961
12962pub trait RowExt {
12963 fn as_f32(&self) -> f32;
12964
12965 fn next_row(&self) -> Self;
12966
12967 fn previous_row(&self) -> Self;
12968
12969 fn minus(&self, other: Self) -> u32;
12970}
12971
12972impl RowExt for DisplayRow {
12973 fn as_f32(&self) -> f32 {
12974 self.0 as f32
12975 }
12976
12977 fn next_row(&self) -> Self {
12978 Self(self.0 + 1)
12979 }
12980
12981 fn previous_row(&self) -> Self {
12982 Self(self.0.saturating_sub(1))
12983 }
12984
12985 fn minus(&self, other: Self) -> u32 {
12986 self.0 - other.0
12987 }
12988}
12989
12990impl RowExt for MultiBufferRow {
12991 fn as_f32(&self) -> f32 {
12992 self.0 as f32
12993 }
12994
12995 fn next_row(&self) -> Self {
12996 Self(self.0 + 1)
12997 }
12998
12999 fn previous_row(&self) -> Self {
13000 Self(self.0.saturating_sub(1))
13001 }
13002
13003 fn minus(&self, other: Self) -> u32 {
13004 self.0 - other.0
13005 }
13006}
13007
13008trait RowRangeExt {
13009 type Row;
13010
13011 fn len(&self) -> usize;
13012
13013 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
13014}
13015
13016impl RowRangeExt for Range<MultiBufferRow> {
13017 type Row = MultiBufferRow;
13018
13019 fn len(&self) -> usize {
13020 (self.end.0 - self.start.0) as usize
13021 }
13022
13023 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
13024 (self.start.0..self.end.0).map(MultiBufferRow)
13025 }
13026}
13027
13028impl RowRangeExt for Range<DisplayRow> {
13029 type Row = DisplayRow;
13030
13031 fn len(&self) -> usize {
13032 (self.end.0 - self.start.0) as usize
13033 }
13034
13035 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
13036 (self.start.0..self.end.0).map(DisplayRow)
13037 }
13038}
13039
13040fn hunk_status(hunk: &DiffHunk<MultiBufferRow>) -> DiffHunkStatus {
13041 if hunk.diff_base_byte_range.is_empty() {
13042 DiffHunkStatus::Added
13043 } else if hunk.associated_range.is_empty() {
13044 DiffHunkStatus::Removed
13045 } else {
13046 DiffHunkStatus::Modified
13047 }
13048}